Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP(schema-wasm): support schema split into multiple files #4787

Merged
merged 15 commits into from
Apr 8, 2024
Merged
33 changes: 22 additions & 11 deletions prisma-fmt/src/code_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,13 @@ pub(crate) fn available_actions(schema: String, params: CodeActionParams) -> Vec

let datasource = config.datasources.first();

for source in validated_schema.db.ast().sources() {
relation_mode::edit_referential_integrity(&mut actions, &params, validated_schema.db.source(), source)
for source in validated_schema.db.ast_assert_single().sources() {
relation_mode::edit_referential_integrity(
&mut actions,
&params,
validated_schema.db.source_assert_single(),
source,
)
}

// models AND views
Expand All @@ -45,21 +50,27 @@ pub(crate) fn available_actions(schema: String, params: CodeActionParams) -> Vec
multi_schema::add_schema_block_attribute_model(
&mut actions,
&params,
validated_schema.db.source(),
validated_schema.db.source_assert_single(),
config,
model,
);

multi_schema::add_schema_to_schemas(&mut actions, &params, validated_schema.db.source(), config, model);
multi_schema::add_schema_to_schemas(
&mut actions,
&params,
validated_schema.db.source_assert_single(),
config,
model,
);
}

if matches!(datasource, Some(ds) if ds.active_provider == "mongodb") {
mongodb::add_at_map_for_id(&mut actions, &params, validated_schema.db.source(), model);
mongodb::add_at_map_for_id(&mut actions, &params, validated_schema.db.source_assert_single(), model);

mongodb::add_native_for_auto_id(
&mut actions,
&params,
validated_schema.db.source(),
validated_schema.db.source_assert_single(),
model,
datasource.unwrap(),
);
Expand All @@ -71,7 +82,7 @@ pub(crate) fn available_actions(schema: String, params: CodeActionParams) -> Vec
multi_schema::add_schema_block_attribute_enum(
&mut actions,
&params,
validated_schema.db.source(),
validated_schema.db.source_assert_single(),
config,
enumerator,
)
Expand All @@ -88,15 +99,15 @@ pub(crate) fn available_actions(schema: String, params: CodeActionParams) -> Vec
relations::add_referenced_side_unique(
&mut actions,
&params,
validated_schema.db.source(),
validated_schema.db.source_assert_single(),
complete_relation,
);

if relation.is_one_to_one() {
relations::add_referencing_side_unique(
&mut actions,
&params,
validated_schema.db.source(),
validated_schema.db.source_assert_single(),
complete_relation,
);
}
Expand All @@ -105,7 +116,7 @@ pub(crate) fn available_actions(schema: String, params: CodeActionParams) -> Vec
relations::add_index_for_relation_fields(
&mut actions,
&params,
validated_schema.db.source(),
validated_schema.db.source_assert_single(),
complete_relation.referencing_field(),
);
}
Expand All @@ -114,7 +125,7 @@ pub(crate) fn available_actions(schema: String, params: CodeActionParams) -> Vec
relation_mode::replace_set_default_mysql(
&mut actions,
&params,
validated_schema.db.source(),
validated_schema.db.source_assert_single(),
complete_relation,
config,
)
Expand Down
5 changes: 2 additions & 3 deletions prisma-fmt/src/code_actions/multi_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,12 @@ pub(super) fn add_schema_to_schemas(
formatted_attribute,
true,
// todo: update spans so that we can just append to the end of the _inside_ of the array. Instead of needing to re-append the `]` or taking the span end -1
Span::new(span.start, span.end - 1),
Span::new(span.start, span.end - 1, psl::parser_database::FileId::ZERO),
params,
)
}
None => {
let has_properties = datasource.provider_defined()
|| datasource.url_defined()
let has_properties = datasource.provider_defined() | datasource.url_defined()
|| datasource.direct_url_defined()
|| datasource.shadow_url_defined()
|| datasource.relation_mode_defined()
Expand Down
49 changes: 29 additions & 20 deletions prisma-fmt/src/get_config.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use psl::Diagnostics;
use psl::{Diagnostics, ValidatedSchema};
use serde::Deserialize;
use serde_json::json;
use std::collections::HashMap;

use crate::validate::SCHEMA_PARSER_ERROR_CODE;
use crate::{schema_file_input::SchemaFileInput, validate::SCHEMA_PARSER_ERROR_CODE};

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct GetConfigParams {
prisma_schema: String,
prisma_schema: SchemaFileInput,
#[serde(default)]
ignore_env_var_errors: bool,
#[serde(default)]
Expand Down Expand Up @@ -43,29 +43,38 @@ pub(crate) fn get_config(params: &str) -> Result<String, String> {
}

fn get_config_impl(params: GetConfigParams) -> Result<serde_json::Value, GetConfigError> {
let wrap_get_config_err = |errors: Diagnostics| -> GetConfigError {
use std::fmt::Write as _;

let mut full_error = errors.to_pretty_string("schema.prisma", &params.prisma_schema);
write!(full_error, "\nValidation Error Count: {}", errors.errors().len()).unwrap();

GetConfigError {
// this mirrors user_facing_errors::common::SchemaParserError
error_code: Some(SCHEMA_PARSER_ERROR_CODE),
message: full_error,
}
};

let mut config = psl::parse_configuration(&params.prisma_schema).map_err(wrap_get_config_err)?;
let mut schema = psl::validate_multi_file(params.prisma_schema.into());
if schema.diagnostics.has_errors() {
return Err(create_get_config_error(&schema, &schema.diagnostics));
}

if !params.ignore_env_var_errors {
let overrides: Vec<(_, _)> = params.datasource_overrides.into_iter().collect();
config
schema
.configuration
.resolve_datasource_urls_prisma_fmt(&overrides, |key| params.env.get(key).map(String::from))
.map_err(wrap_get_config_err)?;
.map_err(|diagnostics| create_get_config_error(&schema, &diagnostics))?;
}

Ok(psl::get_config(&config))
Ok(psl::get_config(&schema.configuration))
}

fn create_get_config_error(schema: &ValidatedSchema, diagnostics: &Diagnostics) -> GetConfigError {
use std::fmt::Write as _;

let mut rendered_diagnostics = schema.render_diagnostics(diagnostics);
write!(
rendered_diagnostics,
"\nValidation Error Count: {}",
diagnostics.errors().len()
)
.unwrap();

GetConfigError {
// this mirrors user_facing_errors::common::SchemaParserError
error_code: Some(SCHEMA_PARSER_ERROR_CODE),
message: rendered_diagnostics,
}
}

#[cfg(test)]
Expand Down
47 changes: 44 additions & 3 deletions prisma-fmt/src/get_dmmf.rs

Large diffs are not rendered by default.

11 changes: 10 additions & 1 deletion prisma-fmt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ mod code_actions;
mod get_config;
mod get_dmmf;
mod lint;
mod merge_schemas;
mod native;
mod preview;
mod schema_file_input;
mod text_document_completion;
mod validate;

Expand Down Expand Up @@ -42,6 +44,13 @@ pub fn code_actions(schema: String, params: &str) -> String {
serde_json::to_string(&actions).unwrap()
}

/// Given a list of Prisma schema files (and their locations), returns the merged schema.
/// This is useful for `@prisma/client` generation, where the client needs a single - potentially large - schema,
/// while still allowing the user to split their schema copies into multiple files.
pub fn merge_schemas(params: String) -> String {
merge_schemas::merge_schemas(&params)
}

/// The two parameters are:
/// - The Prisma schema to reformat, as a string.
/// - An LSP
Expand Down Expand Up @@ -225,7 +234,7 @@ pub(crate) fn range_to_span(range: Range, document: &str) -> ast::Span {
let start = position_to_offset(&range.start, document).unwrap();
let end = position_to_offset(&range.end, document).unwrap();

ast::Span::new(start, end)
ast::Span::new(start, end, psl::parser_database::FileId::ZERO)
}

/// Gives the LSP position right after the given span.
Expand Down
127 changes: 127 additions & 0 deletions prisma-fmt/src/merge_schemas.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
use psl::reformat_validated_schema_into_single;
use serde::Deserialize;

use crate::schema_file_input::SchemaFileInput;

#[derive(Debug, Deserialize)]
pub struct MergeSchemasParams {
schema: SchemaFileInput,
}

pub(crate) fn merge_schemas(params: &str) -> String {
let params: MergeSchemasParams = match serde_json::from_str(params) {
Ok(params) => params,
Err(serde_err) => {
panic!("Failed to deserialize GetDmmfParams: {serde_err}");
jkomyno marked this conversation as resolved.
Show resolved Hide resolved
}
};

let params_as_vec: Vec<_> = params.schema.into();
let validated_schema = psl::validate_multi_file(params_as_vec.clone());

// diagnostics aren't supposed to have errors, as they should be validated before-hand.
if validated_schema.diagnostics.has_errors() {
panic!("Invalid schemas.");
jkomyno marked this conversation as resolved.
Show resolved Hide resolved
}

let indent_width = 2usize;
reformat_validated_schema_into_single(validated_schema, indent_width).unwrap()
}

#[cfg(test)]
mod tests {
use super::*;
use expect_test::expect;
use serde_json::json;

#[test]
fn merge_two_valid_schemas_succeeds() {
let schema = vec![
(
"b.prisma",
r#"
model B {
id String @id
a A?
}
"#,
),
(
"a.prisma",
r#"
datasource db {
provider = "postgresql"
url = env("DBURL")
}

model A {
id String @id
b_id String @unique
b B @relation(fields: [b_id], references: [id])
}
"#,
),
];

let request = json!({
"schema": schema,
});

let expected = expect![[r#"
model B {
id String @id
a A?
}

datasource db {
provider = "postgresql"
url = env("DBURL")
}

model A {
id String @id
b_id String @unique
b B @relation(fields: [b_id], references: [id])
}
"#]];

let response = merge_schemas(&request.to_string());
expected.assert_eq(&response);
}

#[test]
#[should_panic]
fn merge_two_invalid_schemas_panics() {
let schema = vec![
(
"b.prisma",
r#"
model B {
id String @id
a A?
}
"#,
),
(
"a.prisma",
r#"
datasource db {
provider = "postgresql"
url = env("DBURL")
}

model A {
id String @id
b_id String @unique
}
"#,
),
];

let request = json!({
"schema": schema,
});

merge_schemas(&request.to_string());
}
}
26 changes: 26 additions & 0 deletions prisma-fmt/src/schema_file_input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use psl::SourceFile;
use serde::Deserialize;

/// Struct for supporting multiple files
/// in a backward-compatible way: can either accept
/// a single file contents or vector of (filePath, content) tuples.
/// Can be converted to the input for `psl::validate_multi_file` from
/// any of the variants.
#[derive(Deserialize, Debug)]
#[serde(untagged)]
pub(crate) enum SchemaFileInput {
Single(String),
Multiple(Vec<(String, String)>),
}

impl From<SchemaFileInput> for Vec<(String, SourceFile)> {
fn from(value: SchemaFileInput) -> Self {
match value {
SchemaFileInput::Single(content) => vec![("schema.prisma".to_owned(), content.into())],
SchemaFileInput::Multiple(file_list) => file_list
.into_iter()
.map(|(filename, content)| (filename, content.into()))
.collect(),
}
}
}
6 changes: 3 additions & 3 deletions prisma-fmt/src/text_document_completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub(crate) fn completion(schema: String, params: CompletionParams) -> Completion

let db = {
let mut diag = Diagnostics::new();
ParserDatabase::new(source_file, &mut diag)
ParserDatabase::new_single_file(source_file, &mut diag)
};

let ctx = CompletionContext {
Expand Down Expand Up @@ -91,7 +91,7 @@ impl<'a> CompletionContext<'a> {
}

fn push_ast_completions(ctx: CompletionContext<'_>, completion_list: &mut CompletionList) {
match ctx.db.ast().find_at_position(ctx.position) {
match ctx.db.ast_assert_single().find_at_position(ctx.position) {
ast::SchemaPosition::Model(
_model_id,
ast::ModelPosition::Field(_, ast::FieldPosition::Attribute("relation", _, Some(attr_name))),
Expand Down Expand Up @@ -190,7 +190,7 @@ fn ds_has_prop(ctx: CompletionContext<'_>, prop: &str) -> bool {

fn push_namespaces(ctx: CompletionContext<'_>, completion_list: &mut CompletionList) {
for (namespace, _) in ctx.namespaces() {
let insert_text = if add_quotes(ctx.params, ctx.db.source()) {
let insert_text = if add_quotes(ctx.params, ctx.db.source_assert_single()) {
format!(r#""{namespace}""#)
} else {
namespace.to_string()
Expand Down
Loading
Loading