diff --git a/psl/parser-database/src/attributes.rs b/psl/parser-database/src/attributes.rs index 271f27d5457..a075a1b2126 100644 --- a/psl/parser-database/src/attributes.rs +++ b/psl/parser-database/src/attributes.rs @@ -99,11 +99,15 @@ fn resolve_enum_attributes<'db>(enum_id: crate::EnumId, ast_enum: &'db ast::Enum } ctx.validate_visited_arguments(); } + + // @ignore + if ctx.visit_optional_single_attr("ignore") { + enum_attributes.ignored_values.insert(value_id); + ctx.validate_visited_arguments(); + } ctx.validate_visited_attributes(); } - // Now validate the enum attributes. - ctx.visit_attributes(enum_id); // @@map diff --git a/psl/parser-database/src/types.rs b/psl/parser-database/src/types.rs index 55b6665a2c5..bb33410fa6a 100644 --- a/psl/parser-database/src/types.rs +++ b/psl/parser-database/src/types.rs @@ -738,6 +738,8 @@ pub(super) struct EnumAttributes { pub(super) mapped_name: Option, /// @map on enum values. pub(super) mapped_values: HashMap, + /// @ignore on enum values. + pub(super) ignored_values: std::collections::HashSet, /// ```ignore /// @@schema("public") /// ^^^^^^^^ diff --git a/psl/parser-database/src/walkers/enum.rs b/psl/parser-database/src/walkers/enum.rs index 455bea664ea..37fa7ec13b2 100644 --- a/psl/parser-database/src/walkers/enum.rs +++ b/psl/parser-database/src/walkers/enum.rs @@ -122,4 +122,11 @@ impl<'db> EnumValueWalker<'db> { .get(&(self.id.1)) .map(|id| &self.db[*id]) } + + /// True if the enum value is ignored. + pub fn is_ignored(self) -> bool { + self.db.types.enum_attributes[&self.id.0] + .ignored_values + .contains(&self.id.1) + } } diff --git a/psl/psl/Cargo.toml b/psl/psl/Cargo.toml index d61422ddef1..8539b8e5eb1 100644 --- a/psl/psl/Cargo.toml +++ b/psl/psl/Cargo.toml @@ -16,6 +16,7 @@ all = ["postgresql", "sqlite", "mysql", "cockroachdb", "mssql", "mongodb"] psl-core.workspace = true [dev-dependencies] +psl-core = { workspace = true, features = ["postgresql", "sqlite", "mysql", "cockroachdb", "mssql", "mongodb"] } base64.workspace = true dissimilar.workspace = true expect-test.workspace = true diff --git a/psl/psl/tests/attributes/ignore_positive.rs b/psl/psl/tests/attributes/ignore_positive.rs index 588d6b29c5e..a1ac96cc1fd 100644 --- a/psl/psl/tests/attributes/ignore_positive.rs +++ b/psl/psl/tests/attributes/ignore_positive.rs @@ -302,3 +302,21 @@ fn allow_ignore_on_relation_fields_on_valid_models() { .assert_has_relation_field("rel_e") .assert_ignored(true); } + +#[test] +fn allow_ignore_on_enum_values() { + let dml = r#" + enum SupportedCarTypes { + COUPE @ignore + SEDAN + VAN + } + "#; + + let datamodel = parse_schema(dml); + let enum_walker = datamodel.assert_has_enum("SupportedCarTypes"); + + enum_walker.assert_has_value("COUPE").assert_ignored(true); + enum_walker.assert_has_value("SEDAN").assert_ignored(false); + enum_walker.assert_has_value("VAN").assert_ignored(false); +} diff --git a/psl/psl/tests/common/asserts.rs b/psl/psl/tests/common/asserts.rs index 523cb5cc555..1aecc88d4f2 100644 --- a/psl/psl/tests/common/asserts.rs +++ b/psl/psl/tests/common/asserts.rs @@ -15,6 +15,7 @@ use psl::schema_ast::ast::{self, FieldArity}; pub(crate) trait DatamodelAssert<'a> { fn assert_has_model(&'a self, name: &str) -> walkers::ModelWalker<'a>; fn assert_has_type(&'a self, name: &str) -> walkers::CompositeTypeWalker<'a>; + fn assert_has_enum(&'a self, name: &str) -> walkers::EnumWalker<'a>; } pub(crate) trait WarningAsserts { @@ -139,6 +140,14 @@ impl<'a> DatamodelAssert<'a> for psl::ValidatedSchema { .find(|m| m.name() == name) .expect("Type {name} not found") } + + #[track_caller] + fn assert_has_enum(&'a self, name: &str) -> walkers::EnumWalker<'a> { + self.db + .walk_enums() + .find(|e| e.name() == name) + .expect("Enum {name} not found") + } } impl RelationFieldAssert for walkers::RelationFieldWalker<'_> { @@ -792,3 +801,27 @@ impl IndexAssert for walkers::PrimaryKeyWalker<'_> { self } } +pub(crate) trait EnumAssert<'a> { + fn assert_has_value(&'a self, name: &str) -> walkers::EnumValueWalker<'a>; +} + +impl<'a> EnumAssert<'a> for walkers::EnumWalker<'a> { + #[track_caller] + fn assert_has_value(&'a self, name: &str) -> walkers::EnumValueWalker<'a> { + self.values() + .find(|v| v.name() == name) + .expect("Enum value {name} not found") + } +} + +pub(crate) trait EnumValueAssert { + fn assert_ignored(&self, ignored: bool) -> &Self; +} + +impl EnumValueAssert for walkers::EnumValueWalker<'_> { + #[track_caller] + fn assert_ignored(&self, ignored: bool) -> &Self { + assert_eq!(self.is_ignored(), ignored); + self + } +} diff --git a/query-compiler/core/src/query_document/parser.rs b/query-compiler/core/src/query_document/parser.rs index 2a7e147a4e1..bdc89f6d8f7 100644 --- a/query-compiler/core/src/query_document/parser.rs +++ b/query-compiler/core/src/query_document/parser.rs @@ -182,19 +182,18 @@ impl QueryDocumentParser { .iter() .filter_map(|input_field| { // Match schema argument field to an argument field in the incoming document. - let selection_arg = given_arguments + let selection_arg: Option<(String, ArgumentValue)> = given_arguments .iter() .find(|given_argument| given_argument.0 == input_field.name) - .map(|(_, value)| value) .cloned(); let argument_path = argument_path.add(input_field.name.clone().into_owned()); - let validate_other_required_args = || { + let validate_other_required_args = |name: &str| { for req_name in input_field.requires_other_fields() { if !given_arguments.iter().any(|(name, _)| name == req_name) { let Some(req_field) = schema_field.arguments().iter().find(|f| f.name == *req_name) else { - panic!("argument {} requires unknown argument {req_name}", input_field.name) + panic!("argument {name} requires unknown argument {req_name}") }; return Err(ValidationError::conditionally_required_argument_missing( &selection_path.segments(), @@ -210,14 +209,8 @@ impl QueryDocumentParser { // If optional and not present ignore the field. // If present or has a default, process the value. // If not present but required, throw a validation error. - match selection_arg.or_else(|| { - input_field - .default_value - .as_ref() - .and_then(DefaultKind::get) - .map(ArgumentValue::from) - }) { - Some(value) => Some(validate_other_required_args().and_then(|_| { + match selection_arg { + Some((name, value)) => Some(validate_other_required_args(&name).and_then(|_| { self.parse_input_value( selection_path.clone(), argument_path, diff --git a/query-compiler/dmmf/src/ast_builders/datamodel_ast_builder.rs b/query-compiler/dmmf/src/ast_builders/datamodel_ast_builder.rs index b8c7bcd0f43..3576bc488ca 100644 --- a/query-compiler/dmmf/src/ast_builders/datamodel_ast_builder.rs +++ b/query-compiler/dmmf/src/ast_builders/datamodel_ast_builder.rs @@ -48,6 +48,9 @@ fn enum_to_dmmf(en: walkers::EnumWalker<'_>) -> Enum { }; for enum_value in en.values() { + if enum_value.is_ignored() { + continue; + } enm.values.push(enum_value_to_dmmf(enum_value)); } diff --git a/schema-engine/datamodel-renderer/src/datamodel/enumerator.rs b/schema-engine/datamodel-renderer/src/datamodel/enumerator.rs index 8a517fc612d..ea788d18472 100644 --- a/schema-engine/datamodel-renderer/src/datamodel/enumerator.rs +++ b/schema-engine/datamodel-renderer/src/datamodel/enumerator.rs @@ -9,6 +9,7 @@ pub struct EnumVariant<'a> { comment_out: bool, map: Option>, documentation: Option>, + ignore: Option>, } impl<'a> EnumVariant<'a> { @@ -26,6 +27,7 @@ impl<'a> EnumVariant<'a> { comment_out: false, map: None, documentation: None, + ignore: None, } } @@ -55,6 +57,18 @@ impl<'a> EnumVariant<'a> { self.comment_out = true; } + /// Ignores the variant. + /// + /// ```ignore + /// enum Foo { + /// Bar @ignore + /// ^^^^^ this + /// } + /// ``` + pub fn ignore(&mut self) { + self.ignore = Some(FieldAttribute::new(Function::new("ignore"))); + } + /// Documentation of a variant. /// /// ```ignore @@ -91,6 +105,11 @@ impl fmt::Display for EnumVariant<'_> { map.fmt(f)?; } + if let Some(ref ignore) = self.ignore { + f.write_str(" ")?; + ignore.fmt(f)?; + } + Ok(()) } }