diff --git a/prisma-fmt/src/get_config.rs b/prisma-fmt/src/get_config.rs index 6bf3d4d96f04..564e67f41fe9 100644 --- a/prisma-fmt/src/get_config.rs +++ b/prisma-fmt/src/get_config.rs @@ -222,9 +222,7 @@ mod tests { "prismaSchema": schemas, }); - let expected = expect![[ - r#"{"config":{"generators":[{"name":"js","provider":{"fromEnvVar":null,"value":"prisma-client"},"output":null,"config":{},"binaryTargets":[],"previewFeatures":[],"sourceFilePath":"generator.prisma"}],"datasources":[{"name":"db","provider":"postgresql","activeProvider":"postgresql","url":{"fromEnvVar":null,"value":"postgresql://example.com/db"},"schemas":[],"sourceFilePath":"datasource.prisma"}],"warnings":[]},"errors":[]}"# - ]]; + let expected = expect![[r#"{"config":{"generators":[{"name":"js","provider":{"fromEnvVar":null,"value":"prisma-client"},"output":null,"config":{},"binaryTargets":[],"previewFeatures":[],"sourceFilePath":"generator.prisma"}],"datasources":[{"name":"db","provider":"postgresql","activeProvider":"postgresql","url":{"fromEnvVar":null,"value":"postgresql://example.com/db"},"schemas":[],"sourceFilePath":"datasource.prisma"}],"warnings":["The datasource attribute \"url\" is also available to the Prisma Config file, and will be removed from the datasource in Prisma 7.0.0. See https://pris.ly/prisma-config for more information."]},"errors":[]}"#]]; let response = get_config(&request.to_string()); expected.assert_eq(&response); } @@ -241,9 +239,7 @@ mod tests { let request = json!({ "prismaSchema": schema, }); - let expected = expect![[ - r#"{"config":{"generators":[],"datasources":[{"name":"thedb","provider":"postgresql","activeProvider":"postgresql","url":{"fromEnvVar":"NON_EXISTING_ENV_VAR_WE_COUNT_ON_IT_AT_LEAST","value":null},"schemas":[],"sourceFilePath":"schema.prisma"}],"warnings":[]},"errors":[{"file_name":"schema.prisma","message":"\u001b[1;91merror\u001b[0m: \u001b[1mEnvironment variable not found: NON_EXISTING_ENV_VAR_WE_COUNT_ON_IT_AT_LEAST.\u001b[0m\n \u001b[1;94m-->\u001b[0m \u001b[4mschema.prisma:4\u001b[0m\n\u001b[1;94m | \u001b[0m\n\u001b[1;94m 3 | \u001b[0m provider = \"postgresql\"\n\u001b[1;94m 4 | \u001b[0m url = \u001b[1;91menv(\"NON_EXISTING_ENV_VAR_WE_COUNT_ON_IT_AT_LEAST\")\u001b[0m\n\u001b[1;94m | \u001b[0m\n"}]}"# - ]]; + let expected = expect![[r#"{"config":{"generators":[],"datasources":[{"name":"thedb","provider":"postgresql","activeProvider":"postgresql","url":{"fromEnvVar":"NON_EXISTING_ENV_VAR_WE_COUNT_ON_IT_AT_LEAST","value":null},"schemas":[],"sourceFilePath":"schema.prisma"}],"warnings":["The datasource attribute \"url\" is also available to the Prisma Config file, and will be removed from the datasource in Prisma 7.0.0. See https://pris.ly/prisma-config for more information."]},"errors":[{"file_name":"schema.prisma","message":"\u001b[1;91merror\u001b[0m: \u001b[1mEnvironment variable not found: NON_EXISTING_ENV_VAR_WE_COUNT_ON_IT_AT_LEAST.\u001b[0m\n \u001b[1;94m-->\u001b[0m \u001b[4mschema.prisma:4\u001b[0m\n\u001b[1;94m | \u001b[0m\n\u001b[1;94m 3 | \u001b[0m provider = \"postgresql\"\n\u001b[1;94m 4 | \u001b[0m url = \u001b[1;91menv(\"NON_EXISTING_ENV_VAR_WE_COUNT_ON_IT_AT_LEAST\")\u001b[0m\n\u001b[1;94m | \u001b[0m\n"}]}"#]]; let response = get_config(&request.to_string()); expected.assert_eq(&response); } @@ -261,9 +257,7 @@ mod tests { "prismaSchema": schema, "ignoreEnvVarErrors": true, }); - let expected = expect![[ - r#"{"config":{"generators":[],"datasources":[{"name":"thedb","provider":"postgresql","activeProvider":"postgresql","url":{"fromEnvVar":"NON_EXISTING_ENV_VAR_WE_COUNT_ON_IT_AT_LEAST","value":null},"schemas":[],"sourceFilePath":"schema.prisma"}],"warnings":[]},"errors":[]}"# - ]]; + let expected = expect![[r#"{"config":{"generators":[],"datasources":[{"name":"thedb","provider":"postgresql","activeProvider":"postgresql","url":{"fromEnvVar":"NON_EXISTING_ENV_VAR_WE_COUNT_ON_IT_AT_LEAST","value":null},"schemas":[],"sourceFilePath":"schema.prisma"}],"warnings":["The datasource attribute \"url\" is also available to the Prisma Config file, and will be removed from the datasource in Prisma 7.0.0. See https://pris.ly/prisma-config for more information."]},"errors":[]}"#]]; let response = get_config(&request.to_string()); expected.assert_eq(&response); } @@ -283,9 +277,7 @@ mod tests { "DBURL": "postgresql://example.com/mydb" } }); - let expected = expect![[ - r#"{"config":{"generators":[],"datasources":[{"name":"thedb","provider":"postgresql","activeProvider":"postgresql","url":{"fromEnvVar":"DBURL","value":"postgresql://example.com/mydb"},"schemas":[],"sourceFilePath":"schema.prisma"}],"warnings":[]},"errors":[]}"# - ]]; + let expected = expect![[r#"{"config":{"generators":[],"datasources":[{"name":"thedb","provider":"postgresql","activeProvider":"postgresql","url":{"fromEnvVar":"DBURL","value":"postgresql://example.com/mydb"},"schemas":[],"sourceFilePath":"schema.prisma"}],"warnings":["The datasource attribute \"url\" is also available to the Prisma Config file, and will be removed from the datasource in Prisma 7.0.0. See https://pris.ly/prisma-config for more information."]},"errors":[]}"#]]; let response = get_config(&request.to_string()); expected.assert_eq(&response); } @@ -306,9 +298,7 @@ mod tests { "DBURL": "postgresql://example.com/mydb" } }); - let expected = expect![[ - r#"{"config":{"generators":[],"datasources":[{"name":"thedb","provider":"postgresql","activeProvider":"postgresql","url":{"fromEnvVar":"DBURL","value":"postgresql://example.com/mydb"},"directUrl":{"fromEnvVar":null,"value":"postgresql://example.com/direct"},"schemas":[],"sourceFilePath":"schema.prisma"}],"warnings":[]},"errors":[]}"# - ]]; + let expected = expect![[r#"{"config":{"generators":[],"datasources":[{"name":"thedb","provider":"postgresql","activeProvider":"postgresql","url":{"fromEnvVar":"DBURL","value":"postgresql://example.com/mydb"},"directUrl":{"fromEnvVar":null,"value":"postgresql://example.com/direct"},"schemas":[],"sourceFilePath":"schema.prisma"}],"warnings":["The datasource attribute \"url\" is also available to the Prisma Config file, and will be removed from the datasource in Prisma 7.0.0. See https://pris.ly/prisma-config for more information.","The datasource attribute \"directUrl\" is also available to the Prisma Config file, and will be removed from the datasource in Prisma 7.0.0. See https://pris.ly/prisma-config for more information."]},"errors":[]}"#]]; let response = get_config(&request.to_string()); expected.assert_eq(&response); } @@ -330,9 +320,7 @@ mod tests { "DBDIRURL": "postgresql://example.com/direct" } }); - let expected = expect![[ - r#"{"config":{"generators":[],"datasources":[{"name":"thedb","provider":"postgresql","activeProvider":"postgresql","url":{"fromEnvVar":"DBURL","value":"postgresql://example.com/mydb"},"directUrl":{"fromEnvVar":"DBDIRURL","value":"postgresql://example.com/direct"},"schemas":[],"sourceFilePath":"schema.prisma"}],"warnings":[]},"errors":[]}"# - ]]; + let expected = expect![[r#"{"config":{"generators":[],"datasources":[{"name":"thedb","provider":"postgresql","activeProvider":"postgresql","url":{"fromEnvVar":"DBURL","value":"postgresql://example.com/mydb"},"directUrl":{"fromEnvVar":"DBDIRURL","value":"postgresql://example.com/direct"},"schemas":[],"sourceFilePath":"schema.prisma"}],"warnings":["The datasource attribute \"url\" is also available to the Prisma Config file, and will be removed from the datasource in Prisma 7.0.0. See https://pris.ly/prisma-config for more information.","The datasource attribute \"directUrl\" is also available to the Prisma Config file, and will be removed from the datasource in Prisma 7.0.0. See https://pris.ly/prisma-config for more information."]},"errors":[]}"#]]; let response = get_config(&request.to_string()); expected.assert_eq(&response); } @@ -353,9 +341,7 @@ mod tests { "DBURL": "postgresql://example.com/mydb", } }); - let expected = expect![[ - r#"{"config":{"generators":[],"datasources":[{"name":"thedb","provider":"postgresql","activeProvider":"postgresql","url":{"fromEnvVar":"DBURL","value":null},"directUrl":{"fromEnvVar":null,"value":""},"schemas":[],"sourceFilePath":"schema.prisma"}],"warnings":[]},"errors":[{"file_name":"schema.prisma","message":"\u001b[1;91merror\u001b[0m: \u001b[1mError validating datasource `thedb`: You must provide a nonempty direct URL\u001b[0m\n \u001b[1;94m-->\u001b[0m \u001b[4mschema.prisma:5\u001b[0m\n\u001b[1;94m | \u001b[0m\n\u001b[1;94m 4 | \u001b[0m url = env(\"DBURL\")\n\u001b[1;94m 5 | \u001b[0m directUrl = \u001b[1;91m\"\"\u001b[0m\n\u001b[1;94m | \u001b[0m\n"}]}"# - ]]; + let expected = expect![[r#"{"config":{"generators":[],"datasources":[{"name":"thedb","provider":"postgresql","activeProvider":"postgresql","url":{"fromEnvVar":"DBURL","value":null},"directUrl":{"fromEnvVar":null,"value":""},"schemas":[],"sourceFilePath":"schema.prisma"}],"warnings":["The datasource attribute \"url\" is also available to the Prisma Config file, and will be removed from the datasource in Prisma 7.0.0. See https://pris.ly/prisma-config for more information.","The datasource attribute \"directUrl\" is also available to the Prisma Config file, and will be removed from the datasource in Prisma 7.0.0. See https://pris.ly/prisma-config for more information."]},"errors":[{"file_name":"schema.prisma","message":"\u001b[1;91merror\u001b[0m: \u001b[1mError validating datasource `thedb`: You must provide a nonempty direct URL\u001b[0m\n \u001b[1;94m-->\u001b[0m \u001b[4mschema.prisma:5\u001b[0m\n\u001b[1;94m | \u001b[0m\n\u001b[1;94m 4 | \u001b[0m url = env(\"DBURL\")\n\u001b[1;94m 5 | \u001b[0m directUrl = \u001b[1;91m\"\"\u001b[0m\n\u001b[1;94m | \u001b[0m\n"}]}"#]]; let response = get_config(&request.to_string()); expected.assert_eq(&response); } @@ -376,9 +362,7 @@ mod tests { "DBURL": "postgresql://example.com/mydb", } }); - let expected = expect![[ - r#"{"config":{"generators":[],"datasources":[{"name":"thedb","provider":"postgresql","activeProvider":"postgresql","url":{"fromEnvVar":"DBURL","value":null},"directUrl":{"fromEnvVar":"DOES_NOT_EXIST","value":null},"schemas":[],"sourceFilePath":"schema.prisma"}],"warnings":[]},"errors":[{"file_name":"schema.prisma","message":"\u001b[1;91merror\u001b[0m: \u001b[1mEnvironment variable not found: DOES_NOT_EXIST.\u001b[0m\n \u001b[1;94m-->\u001b[0m \u001b[4mschema.prisma:5\u001b[0m\n\u001b[1;94m | \u001b[0m\n\u001b[1;94m 4 | \u001b[0m url = env(\"DBURL\")\n\u001b[1;94m 5 | \u001b[0m directUrl = \u001b[1;91menv(\"DOES_NOT_EXIST\")\u001b[0m\n\u001b[1;94m | \u001b[0m\n"}]}"# - ]]; + let expected = expect![[r#"{"config":{"generators":[],"datasources":[{"name":"thedb","provider":"postgresql","activeProvider":"postgresql","url":{"fromEnvVar":"DBURL","value":null},"directUrl":{"fromEnvVar":"DOES_NOT_EXIST","value":null},"schemas":[],"sourceFilePath":"schema.prisma"}],"warnings":["The datasource attribute \"url\" is also available to the Prisma Config file, and will be removed from the datasource in Prisma 7.0.0. See https://pris.ly/prisma-config for more information.","The datasource attribute \"directUrl\" is also available to the Prisma Config file, and will be removed from the datasource in Prisma 7.0.0. See https://pris.ly/prisma-config for more information."]},"errors":[{"file_name":"schema.prisma","message":"\u001b[1;91merror\u001b[0m: \u001b[1mEnvironment variable not found: DOES_NOT_EXIST.\u001b[0m\n \u001b[1;94m-->\u001b[0m \u001b[4mschema.prisma:5\u001b[0m\n\u001b[1;94m | \u001b[0m\n\u001b[1;94m 4 | \u001b[0m url = env(\"DBURL\")\n\u001b[1;94m 5 | \u001b[0m directUrl = \u001b[1;91menv(\"DOES_NOT_EXIST\")\u001b[0m\n\u001b[1;94m | \u001b[0m\n"}]}"#]]; let response = get_config(&request.to_string()); expected.assert_eq(&response); } @@ -400,9 +384,7 @@ mod tests { "DOES_NOT_EXIST": "", } }); - let expected = expect![[ - r#"{"config":{"generators":[],"datasources":[{"name":"thedb","provider":"postgresql","activeProvider":"postgresql","url":{"fromEnvVar":"DBURL","value":null},"directUrl":{"fromEnvVar":"DOES_NOT_EXIST","value":null},"schemas":[],"sourceFilePath":"schema.prisma"}],"warnings":[]},"errors":[{"file_name":"schema.prisma","message":"\u001b[1;91merror\u001b[0m: \u001b[1mError validating datasource `thedb`: You must provide a nonempty direct URL. The environment variable `DOES_NOT_EXIST` resolved to an empty string.\u001b[0m\n \u001b[1;94m-->\u001b[0m \u001b[4mschema.prisma:5\u001b[0m\n\u001b[1;94m | \u001b[0m\n\u001b[1;94m 4 | \u001b[0m url = env(\"DBURL\")\n\u001b[1;94m 5 | \u001b[0m directUrl = \u001b[1;91menv(\"DOES_NOT_EXIST\")\u001b[0m\n\u001b[1;94m | \u001b[0m\n"}]}"# - ]]; + let expected = expect![[r#"{"config":{"generators":[],"datasources":[{"name":"thedb","provider":"postgresql","activeProvider":"postgresql","url":{"fromEnvVar":"DBURL","value":null},"directUrl":{"fromEnvVar":"DOES_NOT_EXIST","value":null},"schemas":[],"sourceFilePath":"schema.prisma"}],"warnings":["The datasource attribute \"url\" is also available to the Prisma Config file, and will be removed from the datasource in Prisma 7.0.0. See https://pris.ly/prisma-config for more information.","The datasource attribute \"directUrl\" is also available to the Prisma Config file, and will be removed from the datasource in Prisma 7.0.0. See https://pris.ly/prisma-config for more information."]},"errors":[{"file_name":"schema.prisma","message":"\u001b[1;91merror\u001b[0m: \u001b[1mError validating datasource `thedb`: You must provide a nonempty direct URL. The environment variable `DOES_NOT_EXIST` resolved to an empty string.\u001b[0m\n \u001b[1;94m-->\u001b[0m \u001b[4mschema.prisma:5\u001b[0m\n\u001b[1;94m | \u001b[0m\n\u001b[1;94m 4 | \u001b[0m url = env(\"DBURL\")\n\u001b[1;94m 5 | \u001b[0m directUrl = \u001b[1;91menv(\"DOES_NOT_EXIST\")\u001b[0m\n\u001b[1;94m | \u001b[0m\n"}]}"#]]; let response = get_config(&request.to_string()); expected.assert_eq(&response); } diff --git a/prisma-fmt/src/get_generators.rs b/prisma-fmt/src/get_generators.rs new file mode 100644 index 000000000000..0fed81648151 --- /dev/null +++ b/prisma-fmt/src/get_generators.rs @@ -0,0 +1,232 @@ +use psl::{Generator, diagnostics::DatamodelError, parse_generators, parser_database::Files}; +use serde::{Deserialize, Serialize}; + +use crate::schema_file_input::SchemaFileInput; + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct GetGeneratorsParams { + prisma_schema: SchemaFileInput, +} + +#[derive(Serialize)] +struct GetGeneratorsResult<'a> { + generators: Vec, + errors: Vec>, +} + +#[derive(Serialize)] +struct ValidationError<'a> { + file_name: Option<&'a str>, + message: String, +} + +pub(crate) fn get_generators(params: &str) -> String { + let params: GetGeneratorsParams = match serde_json::from_str(params) { + Ok(params) => params, + Err(serde_err) => { + panic!("Failed to deserialize GetGeneratorsParams: {serde_err}",); + } + }; + + let schema: Vec<_> = params.prisma_schema.into(); + + let (files, generators, diagnostics) = parse_generators(&schema); + + let all_errors = diagnostics.errors().iter(); + + let result = GetGeneratorsResult { + generators, + errors: serialize_errors(all_errors, &files), + }; + + serde_json::to_string(&result).unwrap() +} + +fn serialize_errors<'a>( + errors: impl Iterator, + files: &'a Files, +) -> Vec> { + errors + .map(move |error| { + let file_id = error.span().file_id; + let (file_name, source, _) = &files[file_id]; + let mut message_pretty: Vec = vec![]; + error.pretty_print(&mut message_pretty, file_name, source.as_str())?; + + Ok(ValidationError { + file_name: Some(file_name), + message: String::from_utf8_lossy(&message_pretty).into_owned(), + }) + }) + .collect::, std::io::Error>>() + .unwrap_or_else(|error| { + vec![ValidationError { + file_name: None, + message: format!("Could not serialize validation errors: {error}"), + }] + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use expect_test::expect; + use serde_json::json; + + #[test] + fn invalid_schema() { + let schema = r#" + generator js { + } + + datasøurce yolo { + } + "#; + + let request = json!({ + "prismaSchema": schema, + }); + + let expected = expect![[ + r#"{"generators":[],"errors":[{"file_name":"schema.prisma","message":"\u001b[1;91merror\u001b[0m: \u001b[1mError validating: This line is invalid. It does not start with any known Prisma schema keyword.\u001b[0m\n \u001b[1;94m-->\u001b[0m \u001b[4mschema.prisma:5\u001b[0m\n\u001b[1;94m | \u001b[0m\n\u001b[1;94m 4 | \u001b[0m\n\u001b[1;94m 5 | \u001b[0m \u001b[1;91mdatasøurce yolo {\u001b[0m\n\u001b[1;94m 6 | \u001b[0m }\n\u001b[1;94m | \u001b[0m\n"},{"file_name":"schema.prisma","message":"\u001b[1;91merror\u001b[0m: \u001b[1mError validating: This line is invalid. It does not start with any known Prisma schema keyword.\u001b[0m\n \u001b[1;94m-->\u001b[0m \u001b[4mschema.prisma:6\u001b[0m\n\u001b[1;94m | \u001b[0m\n\u001b[1;94m 5 | \u001b[0m datasøurce yolo {\n\u001b[1;94m 6 | \u001b[0m \u001b[1;91m}\u001b[0m\n\u001b[1;94m 7 | \u001b[0m \n\u001b[1;94m | \u001b[0m\n"},{"file_name":"schema.prisma","message":"\u001b[1;91merror\u001b[0m: \u001b[1mArgument \"provider\" is missing in generator block \"js\".\u001b[0m\n \u001b[1;94m-->\u001b[0m \u001b[4mschema.prisma:2\u001b[0m\n\u001b[1;94m | \u001b[0m\n\u001b[1;94m 1 | \u001b[0m\n\u001b[1;94m 2 | \u001b[0m \u001b[1;91mgenerator js {\u001b[0m\n\u001b[1;94m 3 | \u001b[0m }\n\u001b[1;94m | \u001b[0m\n"}]}"# + ]]; + let response = get_generators(&request.to_string()); + expected.assert_eq(&response); + } + + #[test] + fn valid_generator_block() { + let schema = r#" + generator js { + provider = "prisma-client" + } + "#; + + let request = json!({ + "prismaSchema": schema, + }); + + let expected = expect![[ + r#"{"generators":[{"name":"js","provider":{"fromEnvVar":null,"value":"prisma-client"},"output":null,"config":{},"binaryTargets":[],"previewFeatures":[]}],"errors":[]}"# + ]]; + let response = get_generators(&request.to_string()); + expected.assert_eq(&response); + } + + #[test] + fn valid_generator_block_invalid_model() { + let schema = r#" + generator js { + provider = "prisma-client" + } + + model M { + "#; + + let request = json!({ + "prismaSchema": schema, + }); + + let expected = expect![[ + r#"{"generators":[{"name":"js","provider":{"fromEnvVar":null,"value":"prisma-client"},"output":null,"config":{},"binaryTargets":[],"previewFeatures":[]}],"errors":[{"file_name":"schema.prisma","message":"\u001b[1;91merror\u001b[0m: \u001b[1mError validating: This line is invalid. It does not start with any known Prisma schema keyword.\u001b[0m\n \u001b[1;94m-->\u001b[0m \u001b[4mschema.prisma:6\u001b[0m\n\u001b[1;94m | \u001b[0m\n\u001b[1;94m 5 | \u001b[0m\n\u001b[1;94m 6 | \u001b[0m \u001b[1;91mmodel M {\u001b[0m\n\u001b[1;94m 7 | \u001b[0m \n\u001b[1;94m | \u001b[0m\n"}]}"# + ]]; + let response = get_generators(&request.to_string()); + expected.assert_eq(&response); + } + + #[test] + fn valid_generator_block_invalid_model_field() { + let schema = r#" + generator js { + provider = "prisma-client" + } + + model M { + field + } + "#; + + let request = json!({ + "prismaSchema": schema, + }); + + let expected = expect![[ + r#"{"generators":[{"name":"js","provider":{"fromEnvVar":null,"value":"prisma-client"},"output":null,"config":{},"binaryTargets":[],"previewFeatures":[]}],"errors":[{"file_name":"schema.prisma","message":"\u001b[1;91merror\u001b[0m: \u001b[1mError validating model \"M\": This field declaration is invalid. It is either missing a name or a type.\u001b[0m\n \u001b[1;94m-->\u001b[0m \u001b[4mschema.prisma:7\u001b[0m\n\u001b[1;94m | \u001b[0m\n\u001b[1;94m 6 | \u001b[0m model M {\n\u001b[1;94m 7 | \u001b[0m \u001b[1;91mfield\u001b[0m\n\u001b[1;94m 8 | \u001b[0m }\n\u001b[1;94m | \u001b[0m\n"}]}"# + ]]; + let response = get_generators(&request.to_string()); + expected.assert_eq(&response); + } + + #[test] + fn valid_generator_block_invalid_datasource() { + let schema = r#" + generator js { + provider = "prisma-client" + } + + datasource D { + "#; + + let request = json!({ + "prismaSchema": schema, + }); + + let expected = expect![[ + r#"{"generators":[{"name":"js","provider":{"fromEnvVar":null,"value":"prisma-client"},"output":null,"config":{},"binaryTargets":[],"previewFeatures":[]}],"errors":[{"file_name":"schema.prisma","message":"\u001b[1;91merror\u001b[0m: \u001b[1mError validating: This line is invalid. It does not start with any known Prisma schema keyword.\u001b[0m\n \u001b[1;94m-->\u001b[0m \u001b[4mschema.prisma:6\u001b[0m\n\u001b[1;94m | \u001b[0m\n\u001b[1;94m 5 | \u001b[0m\n\u001b[1;94m 6 | \u001b[0m \u001b[1;91mdatasource D {\u001b[0m\n\u001b[1;94m 7 | \u001b[0m \n\u001b[1;94m | \u001b[0m\n"}]}"# + ]]; + let response = get_generators(&request.to_string()); + expected.assert_eq(&response); + } + + #[test] + fn multifile() { + let schemas = &[ + ( + "generator.prisma", + r#"generator js { + provider = "prisma-client" + }"#, + ), + ( + "datasource.prisma", + r#"datasource db { + provider = "postgresql" + }"#, + ), + ]; + + let request = json!({ + "prismaSchema": schemas, + }); + + let expected = expect![[ + r#"{"generators":[{"name":"js","provider":{"fromEnvVar":null,"value":"prisma-client"},"output":null,"config":{},"binaryTargets":[],"previewFeatures":[]}],"errors":[]}"# + ]]; + let response = get_generators(&request.to_string()); + expected.assert_eq(&response); + } + + #[test] + fn get_generators_datasource_no_url() { + let schema = r#" + datasource thedb { + provider = "postgresql" + } + + generator js { + provider = "prisma-client" + engineType = "client" + } + "#; + + let request = json!({ + "prismaSchema": schema, + }); + let expected = expect![[ + r#"{"generators":[{"name":"js","provider":{"fromEnvVar":null,"value":"prisma-client"},"output":null,"config":{"engineType":"client"},"binaryTargets":[],"previewFeatures":[]}],"errors":[]}"# + ]]; + let response = get_generators(&request.to_string()); + expected.assert_eq(&response); + } +} diff --git a/prisma-fmt/src/lib.rs b/prisma-fmt/src/lib.rs index d70c5832da46..c5f0124d82bd 100644 --- a/prisma-fmt/src/lib.rs +++ b/prisma-fmt/src/lib.rs @@ -3,6 +3,7 @@ mod code_actions; mod get_config; mod get_datamodel; mod get_dmmf; +mod get_generators; mod hover; mod lint; mod merge_schemas; @@ -267,6 +268,42 @@ pub fn get_config(get_config_params: String) -> String { get_config::get_config(&get_config_params) } +/// This command returns the list of generators defined in the schema, with their provider and output. +/// +/// Params is a JSON string with the following shape: +/// ```typescript +/// interface GetPrismaGeneratorsParams { +/// prismaSchema: string +/// } +/// ``` +/// +/// Params example: +/// +/// ```json +/// { +/// "prismaSchema": +/// } +/// ``` +/// +/// The response is a array of JSON strings with the following shape: +/// +/// ```typescript +/// interface GeneratorConfig { +/// name: string; // The name of the generator +/// provider: string; // The provider of the generator +/// config: { [key: string]: string }; // The config of the generator +/// } +/// +/// type GetGeneratorsResult = { +/// generators: GeneratorConfig[]; +/// errors: { message: string; file_name: string }[]; +/// } +/// ``` +/// +pub fn get_generators(params: String) -> String { + get_generators::get_generators(¶ms) +} + /// This is the same command as get_dmmf() /// /// Params is a JSON string with the following shape: diff --git a/prisma-schema-wasm/src/lib.rs b/prisma-schema-wasm/src/lib.rs index 8ef24cbcdfb3..a1cec4bcf256 100644 --- a/prisma-schema-wasm/src/lib.rs +++ b/prisma-schema-wasm/src/lib.rs @@ -42,6 +42,12 @@ pub fn get_config(params: String) -> String { prisma_fmt::get_config(params) } +#[wasm_bindgen] +pub fn get_generators(params: String) -> String { + register_panic_hook(); + prisma_fmt::get_generators(params) +} + /// Docs: https://prisma.github.io/prisma-engines/doc/prisma_fmt/fn.get_dmmf.html #[wasm_bindgen] pub fn get_dmmf(params: String) -> Result { diff --git a/psl/diagnostics/src/warning.rs b/psl/diagnostics/src/warning.rs index 3d4b94b05980..06308cd99eb5 100644 --- a/psl/diagnostics/src/warning.rs +++ b/psl/diagnostics/src/warning.rs @@ -21,6 +21,13 @@ impl DatamodelWarning { DatamodelWarning { message, span } } + pub fn new_datasource_attr_moved_to_prisma_config(attr_name: &str, span: Span) -> DatamodelWarning { + let message = format!( + "The datasource attribute \"{attr_name}\" is also available to the Prisma Config file, and will be removed from the datasource in Prisma 7.0.0. See https://pris.ly/prisma-config for more information." + ); + Self::new(message, span) + } + pub fn new_preview_feature_deprecated(feature: &str, span: Span) -> DatamodelWarning { let message = format!("Preview feature \"{feature}\" is deprecated. It will be removed in a future version of Prisma."); diff --git a/psl/psl-core/src/lib.rs b/psl/psl-core/src/lib.rs index 92255cc58aa0..39ef5239df1e 100644 --- a/psl/psl-core/src/lib.rs +++ b/psl/psl-core/src/lib.rs @@ -175,6 +175,23 @@ pub fn parse_configuration_multi_file( } } +/// Loads all `generator` blocks from a datamodel using the built-in source definitions. +/// It completely disregards any errors in the rest of the schema and returns them as part of the diagnostics. +pub fn parse_generators(files: &[(String, SourceFile)]) -> (Files, Vec, diagnostics::Diagnostics) { + let mut diagnostics = Diagnostics::new(); + let asts = Files::new(files, &mut diagnostics); + + let mut generators = Vec::new(); + let feature_map_with_provider = (*ALL_PREVIEW_FEATURES).clone(); + + for (_, _, _, ast) in asts.iter() { + let mut out = generator_loader::load_generators_from_ast(ast, &mut diagnostics, &feature_map_with_provider); + generators.append(&mut out); + } + + (asts, generators, diagnostics) +} + pub fn error_tolerant_parse_configuration( files: &[(String, SourceFile)], connectors: ConnectorRegistry<'_>, @@ -197,7 +214,6 @@ fn validate_configuration( diagnostics: &mut Diagnostics, connectors: ConnectorRegistry<'_>, ) -> Configuration { - // TODO: set `is_using_driver_adapters` to the `true` constant for Prisma 7.0.0. let is_using_schema_engine_driver_adapters = has_preview_feature_schema_engine_driver_adapters(schema_ast); let datasources = datasource_loader::load_datasources_from_ast( diff --git a/psl/psl-core/src/validate/datasource_loader.rs b/psl/psl-core/src/validate/datasource_loader.rs index e08d973708ce..3c295e0afab5 100644 --- a/psl/psl-core/src/validate/datasource_loader.rs +++ b/psl/psl-core/src/validate/datasource_loader.rs @@ -142,7 +142,13 @@ fn lift_datasource( let connector_data = active_connector.parse_datasource_properties(&mut args, diagnostics); let (url, url_span) = match args.remove(URL_KEY) { - Some((_span, url_arg)) => (StringFromEnvVar::coerce(url_arg, diagnostics)?, url_arg.span()), + Some((span, url_arg)) => { + diagnostics.push_warning(DatamodelWarning::new_datasource_attr_moved_to_prisma_config( + URL_KEY, span, + )); + + (StringFromEnvVar::coerce(url_arg, diagnostics)?, url_arg.span()) + } None => { if is_using_driver_adapters { @@ -160,10 +166,17 @@ fn lift_datasource( }; let shadow_database_url = match args.remove(SHADOW_DATABASE_URL_KEY) { - Some((_span, shadow_db_url_arg)) => match StringFromEnvVar::coerce(shadow_db_url_arg, diagnostics) { - Some(shadow_db_url) => Some(shadow_db_url) - .filter(|s| !s.as_literal().map(|literal| literal.is_empty()).unwrap_or(false)) - .map(|url| (url, shadow_db_url_arg.span())), + Some((span, shadow_db_url_arg)) => match StringFromEnvVar::coerce(shadow_db_url_arg, diagnostics) { + Some(shadow_db_url) => { + diagnostics.push_warning(DatamodelWarning::new_datasource_attr_moved_to_prisma_config( + SHADOW_DATABASE_URL_KEY, + span, + )); + + Some(shadow_db_url) + .filter(|s| !s.as_literal().map(|literal| literal.is_empty()).unwrap_or(false)) + .map(|url| (url, shadow_db_url_arg.span())) + } None => None, }, @@ -171,10 +184,17 @@ fn lift_datasource( }; let (direct_url, direct_url_span) = match args.remove(DIRECT_URL_KEY) { - Some((_, direct_url)) => ( - StringFromEnvVar::coerce(direct_url, diagnostics), - Some(direct_url.span()), - ), + Some((span, direct_url)) => { + diagnostics.push_warning(DatamodelWarning::new_datasource_attr_moved_to_prisma_config( + DIRECT_URL_KEY, + span, + )); + + ( + StringFromEnvVar::coerce(direct_url, diagnostics), + Some(direct_url.span()), + ) + } None => (None, None), }; diff --git a/psl/psl/src/lib.rs b/psl/psl/src/lib.rs index 78e98fa7f26c..ce8f884438e0 100644 --- a/psl/psl/src/lib.rs +++ b/psl/psl/src/lib.rs @@ -24,6 +24,7 @@ pub use psl_core::{ is_reserved_type_name, mcf::config_to_mcf_json_value as get_config, mcf::{generators_to_json, render_sources_to_json}, // for tests + parse_generators, parser_database::{self, SourceFile}, reachable_only_with_capability, reformat,