Skip to content

Commit

Permalink
feat(psl): Recommend type in error message if schema validation fails…
Browse files Browse the repository at this point in the history
… because of case (#4137)

* feat(psl): error for ignore case validation

* feat(psl): fix formatting

* feat(psl): ignore Source & Generator when recommending type in error

* feat(psl): enclose recommended type in quotes

Closes #15174

---------

Co-authored-by: Sophie <29753584+Druue@users.noreply.github.com>
  • Loading branch information
pranayat and Druue committed Apr 5, 2024
1 parent 3d92748 commit e66d30d
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 18 deletions.
7 changes: 7 additions & 0 deletions psl/diagnostics/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,13 @@ impl DatamodelError {
Self::new(msg, span)
}

pub fn new_type_for_case_not_found_error(type_name: &str, suggestion: &str, span: Span) -> DatamodelError {
let msg = format!(
"Type \"{type_name}\" is neither a built-in type, nor refers to another model, custom type, or enum. Did you mean \"{suggestion}\"?"
);
Self::new(msg, span)
}

pub fn new_scalar_type_not_found_error(type_name: &str, span: Span) -> DatamodelError {
Self::new(format!("Type \"{type_name}\" is not a built-in type."), span)
}
Expand Down
2 changes: 1 addition & 1 deletion psl/parser-database/src/names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ fn duplicate_top_error(existing: &ast::Top, duplicate: &ast::Top) -> DatamodelEr
}

fn assert_is_not_a_reserved_scalar_type(ident: &ast::Identifier, ctx: &mut Context<'_>) {
if ScalarType::try_from_str(&ident.name).is_some() {
if ScalarType::try_from_str(&ident.name, false).is_some() {
ctx.push_error(DatamodelError::new_reserved_scalar_type_error(&ident.name, ident.span));
}
}
Expand Down
79 changes: 62 additions & 17 deletions psl/parser-database/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -648,10 +648,41 @@ fn visit_model<'db>(model_id: ast::ModelId, ast_model: &'db ast::Model, ctx: &mu
native_type: None,
});
}
Err(supported) => ctx.push_error(DatamodelError::new_type_not_found_error(
supported,
ast_field.field_type.span(),
)),
Err(supported) => {
let top_names: Vec<_> = ctx
.ast
.iter_tops()
.filter_map(|(_, top)| match top {
ast::Top::Source(_) | ast::Top::Generator(_) => None,
_ => Some(&top.identifier().name),
})
.collect();

match top_names.iter().find(|&name| name.to_lowercase() == supported) {
Some(ignore_case_match) => {
ctx.push_error(DatamodelError::new_type_for_case_not_found_error(
supported,
ignore_case_match.as_str(),
ast_field.field_type.span(),
));
}
None => match ScalarType::try_from_str(supported, true) {
Some(ignore_case_match) => {
ctx.push_error(DatamodelError::new_type_for_case_not_found_error(
supported,
ignore_case_match.as_str(),
ast_field.field_type.span(),
));
}
None => {
ctx.push_error(DatamodelError::new_type_not_found_error(
supported,
ast_field.field_type.span(),
));
}
},
}
}
}
}
}
Expand Down Expand Up @@ -699,7 +730,7 @@ fn field_type<'db>(field: &'db ast::Field, ctx: &mut Context<'db>) -> Result<Fie
};
let supported_string_id = ctx.interner.intern(supported);

if let Some(tpe) = ScalarType::try_from_str(supported) {
if let Some(tpe) = ScalarType::try_from_str(supported, false) {
return Ok(FieldType::Scalar(ScalarFieldType::BuiltInScalar(tpe)));
}

Expand Down Expand Up @@ -1423,18 +1454,32 @@ impl ScalarType {
matches!(self, ScalarType::Bytes)
}

pub(crate) fn try_from_str(s: &str) -> Option<ScalarType> {
match s {
"Int" => Some(ScalarType::Int),
"BigInt" => Some(ScalarType::BigInt),
"Float" => Some(ScalarType::Float),
"Boolean" => Some(ScalarType::Boolean),
"String" => Some(ScalarType::String),
"DateTime" => Some(ScalarType::DateTime),
"Json" => Some(ScalarType::Json),
"Bytes" => Some(ScalarType::Bytes),
"Decimal" => Some(ScalarType::Decimal),
_ => None,
pub(crate) fn try_from_str(s: &str, ignore_case: bool) -> Option<ScalarType> {
match ignore_case {
true => match s.to_lowercase().as_str() {
"int" => Some(ScalarType::Int),
"bigint" => Some(ScalarType::BigInt),
"float" => Some(ScalarType::Float),
"boolean" => Some(ScalarType::Boolean),
"string" => Some(ScalarType::String),
"datetime" => Some(ScalarType::DateTime),
"json" => Some(ScalarType::Json),
"bytes" => Some(ScalarType::Bytes),
"decimal" => Some(ScalarType::Decimal),
_ => None,
},
_ => match s {
"Int" => Some(ScalarType::Int),
"BigInt" => Some(ScalarType::BigInt),
"Float" => Some(ScalarType::Float),
"Boolean" => Some(ScalarType::Boolean),
"String" => Some(ScalarType::String),
"DateTime" => Some(ScalarType::DateTime),
"Json" => Some(ScalarType::Json),
"Bytes" => Some(ScalarType::Bytes),
"Decimal" => Some(ScalarType::Decimal),
_ => None,
},
}
}
}
Expand Down
64 changes: 64 additions & 0 deletions psl/psl/tests/base/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,3 +239,67 @@ fn type_aliases_must_error() {

expectation.assert_eq(&error);
}

#[test]
fn must_return_good_error_message_for_type_match() {
let dml = indoc! {r#"
model User {
firstName String
}
model B {
a datetime
b footime
c user
d DB
e JS
}
datasource db {
provider = "postgresql"
url = env("TEST_DATABASE_URL")
extensions = [citext, pg_trgm]
}
generator js {
provider = "prisma-client-js"
previewFeatures = ["postgresqlExtensions"]
}
"#};

let error = parse_unwrap_err(dml);

let expected = expect![[r#"
error: Type "datetime" is neither a built-in type, nor refers to another model, custom type, or enum. Did you mean "DateTime"?
--> schema.prisma:5
 | 
 4 | model B {
 5 |  a datetime
 | 
error: Type "footime" is neither a built-in type, nor refers to another model, custom type, or enum.
--> schema.prisma:6
 | 
 5 |  a datetime
 6 |  b footime
 | 
error: Type "user" is neither a built-in type, nor refers to another model, custom type, or enum. Did you mean "User"?
--> schema.prisma:7
 | 
 6 |  b footime
 7 |  c user
 | 
error: Type "DB" is neither a built-in type, nor refers to another model, custom type, or enum.
--> schema.prisma:8
 | 
 7 |  c user
 8 |  d DB
 | 
error: Type "JS" is neither a built-in type, nor refers to another model, custom type, or enum.
--> schema.prisma:9
 | 
 8 |  d DB
 9 |  e JS
 | 
"#]];

expected.assert_eq(&error);
}

0 comments on commit e66d30d

Please sign in to comment.