Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 222 additions & 0 deletions prisma-fmt/src/get_generators.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
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<Generator>,
errors: Vec<ValidationError<'a>>,
}

#[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<Item = &'a DatamodelError>,
files: &'a Files,
) -> Vec<ValidationError<'a>> {
errors
.map(move |error| {
let file_id = error.span().file_id;
let (file_name, source, _) = &files[file_id];
let mut message_pretty: Vec<u8> = 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::<Result<Vec<_>, 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,

Check warning on line 88 in prisma-fmt/src/get_generators.rs

View workflow job for this annotation

GitHub Actions / run lints and formatting checks

Diff in /home/runner/work/prisma-engines/prisma-engines/prisma-fmt/src/get_generators.rs
});

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,

Check warning on line 126 in prisma-fmt/src/get_generators.rs

View workflow job for this annotation

GitHub Actions / run lints and formatting checks

Diff in /home/runner/work/prisma-engines/prisma-engines/prisma-fmt/src/get_generators.rs
});

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,

Check warning on line 147 in prisma-fmt/src/get_generators.rs

View workflow job for this annotation

GitHub Actions / run lints and formatting checks

Diff in /home/runner/work/prisma-engines/prisma-engines/prisma-fmt/src/get_generators.rs
});

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,

Check warning on line 166 in prisma-fmt/src/get_generators.rs

View workflow job for this annotation

GitHub Actions / run lints and formatting checks

Diff in /home/runner/work/prisma-engines/prisma-engines/prisma-fmt/src/get_generators.rs
});

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!({

Check warning on line 215 in prisma-fmt/src/get_generators.rs

View workflow job for this annotation

GitHub Actions / run lints and formatting checks

Diff in /home/runner/work/prisma-engines/prisma-engines/prisma-fmt/src/get_generators.rs
"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);
}
}
37 changes: 37 additions & 0 deletions prisma-fmt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 prisma schema>
/// }
/// ```
///
/// 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(&params)
}

/// This is the same command as get_dmmf()
///
/// Params is a JSON string with the following shape:
Expand Down
6 changes: 6 additions & 0 deletions prisma-schema-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, JsError> {
Expand Down
7 changes: 7 additions & 0 deletions psl/diagnostics/src/warning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
Expand Down
17 changes: 17 additions & 0 deletions psl/psl-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Generator>, 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<'_>,
Expand Down
38 changes: 29 additions & 9 deletions psl/psl-core/src/validate/datasource_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -160,21 +166,35 @@ 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,
},

_ => None,
};

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),
};
Expand Down
1 change: 1 addition & 0 deletions psl/psl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading