diff --git a/README.md b/README.md index 1cf7f7f9..98f5cf5f 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ Open `http://localhost:8000` in browser - [X] NoUnusedVariables - [ ] OverlappingFieldsCanBeMerged - [X] PossibleFragmentSpreads - - [ ] ProvidedNonNullArguments + - [X] ProvidedNonNullArguments - [X] ScalarLeafs - [X] UniqueArgumentNames - [X] UniqueFragmentNames diff --git a/src/registry.rs b/src/registry.rs index bedd315c..f3ef8189 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -41,6 +41,14 @@ impl<'a> TypeName<'a> { TypeName::Named(type_name) => type_name, } } + + pub fn is_non_null(&self) -> bool { + if let TypeName::NonNull(_) = self { + true + } else { + false + } + } } pub struct InputValue { diff --git a/src/validation/mod.rs b/src/validation/mod.rs index d3b18dd0..aee449fe 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -32,7 +32,8 @@ pub fn check_rules(registry: &Registry, doc: &Document) -> Result<()> { .with(rules::VariablesAreInputTypes) .with(rules::VariableInAllowedPosition::default()) .with(rules::ScalarLeafs) - .with(rules::PossibleFragmentSpreads::default()); + .with(rules::PossibleFragmentSpreads::default()) + .with(rules::ProvidedNonNullArguments); visit(&mut visitor, &mut ctx, doc); if !ctx.errors.is_empty() { diff --git a/src/validation/rules/mod.rs b/src/validation/rules/mod.rs index e8666f18..3bb37f26 100644 --- a/src/validation/rules/mod.rs +++ b/src/validation/rules/mod.rs @@ -11,6 +11,7 @@ mod no_undefined_variables; mod no_unused_fragments; mod no_unused_variables; mod possible_fragment_spreads; +mod provided_non_null_arguments; mod scalar_leafs; mod unique_argument_names; mod unique_fragment_names; @@ -32,6 +33,7 @@ pub use no_undefined_variables::NoUndefinedVariables; pub use no_unused_fragments::NoUnusedFragments; pub use no_unused_variables::NoUnusedVariables; pub use possible_fragment_spreads::PossibleFragmentSpreads; +pub use provided_non_null_arguments::ProvidedNonNullArguments; pub use scalar_leafs::ScalarLeafs; pub use unique_argument_names::UniqueArgumentNames; pub use unique_fragment_names::UniqueFragmentNames; diff --git a/src/validation/rules/provided_non_null_arguments.rs b/src/validation/rules/provided_non_null_arguments.rs new file mode 100644 index 00000000..212b8214 --- /dev/null +++ b/src/validation/rules/provided_non_null_arguments.rs @@ -0,0 +1,54 @@ +use crate::registry::TypeName; +use crate::validation::context::ValidatorContext; +use crate::validation::visitor::Visitor; +use graphql_parser::query::Field; +use graphql_parser::schema::Directive; + +#[derive(Default)] +pub struct ProvidedNonNullArguments; + +impl<'a> Visitor<'a> for ProvidedNonNullArguments { + fn enter_directive(&mut self, ctx: &mut ValidatorContext<'a>, directive: &'a Directive) { + if let Some(schema_directive) = ctx.registry.directives.get(&directive.name) { + for arg in schema_directive.args.values() { + if TypeName::create(&arg.ty).is_non_null() { + if directive + .arguments + .iter() + .find(|(name, _)| name == arg.name) + .is_none() + { + ctx.report_error(vec![directive.position], + format!( + "Directive \"@{}\" argument \"{}\" of type \"{}\" is required but not provided", + directive.name, arg.name, arg.ty + )); + } + } + } + } + } + + fn enter_field(&mut self, ctx: &mut ValidatorContext<'a>, field: &'a Field) { + if let Some(parent_type) = ctx.parent_type() { + if let Some(schema_field) = parent_type.field_by_name(&field.name) { + for arg in schema_field.args.values() { + if TypeName::create(&arg.ty).is_non_null() { + if field + .arguments + .iter() + .find(|(name, _)| name == arg.name) + .is_none() + { + ctx.report_error(vec![field.position], + format!( + r#"Field "{}" argument "{}" of type "{}" is required but not provided"#, + field.name, arg.name, parent_type.name() + )); + } + } + } + } + } + } +}