diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 3ab147d6..836472fb 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -23,6 +23,7 @@ - Add `specified_by_url` attribute argument to `#[derive(GraphQLScalarValue)]` and `#[graphql_scalar]` macros. ([#1003](https://github.com/graphql-rust/juniper/pull/1003), [#1000](https://github.com/graphql-rust/juniper/pull/1000)) - Support `isRepeatable` field on directives. ([#1003](https://github.com/graphql-rust/juniper/pull/1003), [#1000](https://github.com/graphql-rust/juniper/pull/1000)) - Support `__Schema.description`, `__Type.specifiedByURL` and `__Directive.isRepeatable` fields in introspection. ([#1003](https://github.com/graphql-rust/juniper/pull/1003), [#1000](https://github.com/graphql-rust/juniper/pull/1000)) +- Support directives on variables definitions. ([#1005](https://github.com/graphql-rust/juniper/pull/1005)) ## Fixes diff --git a/juniper/src/ast.rs b/juniper/src/ast.rs index a3f1d803..f7805eeb 100644 --- a/juniper/src/ast.rs +++ b/juniper/src/ast.rs @@ -49,6 +49,7 @@ pub enum InputValue { pub struct VariableDefinition<'a, S> { pub var_type: Spanning>, pub default_value: Option>>, + pub directives: Option>>>, } #[derive(Clone, PartialEq, Debug)] diff --git a/juniper/src/parser/document.rs b/juniper/src/parser/document.rs index 23063ce3..cd891883 100644 --- a/juniper/src/parser/document.rs +++ b/juniper/src/parser/document.rs @@ -451,6 +451,8 @@ where None }; + let directives = parse_directives(parser, schema)?; + Ok(Spanning::start_end( &start_pos, &default_value @@ -462,6 +464,7 @@ where VariableDefinition { var_type, default_value, + directives: directives.map(|s| s.item), }, ), )) diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs index 39990d1f..0576174e 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -87,6 +87,8 @@ pub enum DirectiveLocation { FragmentDefinition, #[graphql(name = "FIELD_DEFINITION")] FieldDefinition, + #[graphql(name = "VARIABLE_DEFINITION")] + VariableDefinition, #[graphql(name = "FRAGMENT_SPREAD")] FragmentSpread, #[graphql(name = "INLINE_FRAGMENT")] @@ -588,27 +590,28 @@ where impl fmt::Display for DirectiveLocation { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(match *self { - DirectiveLocation::Query => "query", - DirectiveLocation::Mutation => "mutation", - DirectiveLocation::Subscription => "subscription", - DirectiveLocation::Field => "field", - DirectiveLocation::FieldDefinition => "field definition", - DirectiveLocation::FragmentDefinition => "fragment definition", - DirectiveLocation::FragmentSpread => "fragment spread", - DirectiveLocation::InlineFragment => "inline fragment", - DirectiveLocation::Scalar => "scalar", - DirectiveLocation::EnumValue => "enum value", + f.write_str(match self { + Self::Query => "query", + Self::Mutation => "mutation", + Self::Subscription => "subscription", + Self::Field => "field", + Self::FieldDefinition => "field definition", + Self::FragmentDefinition => "fragment definition", + Self::FragmentSpread => "fragment spread", + Self::InlineFragment => "inline fragment", + Self::VariableDefinition => "variable definition", + Self::Scalar => "scalar", + Self::EnumValue => "enum value", }) } } impl<'a, S> fmt::Display for TypeType<'a, S> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - TypeType::Concrete(t) => f.write_str(t.name().unwrap()), - TypeType::List(ref i, _) => write!(f, "[{}]", i), - TypeType::NonNull(ref i) => write!(f, "{}!", i), + match self { + Self::Concrete(t) => f.write_str(t.name().unwrap()), + Self::List(i, _) => write!(f, "[{}]", i), + Self::NonNull(i) => write!(f, "{}!", i), } } } diff --git a/juniper/src/tests/schema_introspection.rs b/juniper/src/tests/schema_introspection.rs index d71b8dc8..1e1b03e0 100644 --- a/juniper/src/tests/schema_introspection.rs +++ b/juniper/src/tests/schema_introspection.rs @@ -1056,6 +1056,12 @@ pub(crate) fn schema_introspection_result() -> Value { "isDeprecated": false, "deprecationReason": null }, + { + "name": "VARIABLE_DEFINITION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "FRAGMENT_SPREAD", "description": null, @@ -2387,6 +2393,11 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { "isDeprecated": false, "deprecationReason": null }, + { + "name": "VARIABLE_DEFINITION", + "isDeprecated": false, + "deprecationReason": null + }, { "name": "FRAGMENT_SPREAD", "isDeprecated": false, diff --git a/juniper/src/validation/rules/known_directives.rs b/juniper/src/validation/rules/known_directives.rs index 012ae1c7..291e17db 100644 --- a/juniper/src/validation/rules/known_directives.rs +++ b/juniper/src/validation/rules/known_directives.rs @@ -1,5 +1,8 @@ use crate::{ - ast::{Directive, Field, Fragment, FragmentSpread, InlineFragment, Operation, OperationType}, + ast::{ + Directive, Field, Fragment, FragmentSpread, InlineFragment, Operation, OperationType, + VariableDefinition, + }, parser::Spanning, schema::model::DirectiveLocation, validation::{ValidatorContext, Visitor}, @@ -106,6 +109,24 @@ where assert_eq!(top, Some(DirectiveLocation::InlineFragment)); } + fn enter_variable_definition( + &mut self, + _: &mut ValidatorContext<'a, S>, + _: &'a (Spanning<&'a str>, VariableDefinition), + ) { + self.location_stack + .push(DirectiveLocation::VariableDefinition); + } + + fn exit_variable_definition( + &mut self, + _: &mut ValidatorContext<'a, S>, + _: &'a (Spanning<&'a str>, VariableDefinition), + ) { + let top = self.location_stack.pop(); + assert_eq!(top, Some(DirectiveLocation::VariableDefinition)); + } + fn enter_directive( &mut self, ctx: &mut ValidatorContext<'a, S>, @@ -206,6 +227,33 @@ mod tests { ); } + #[test] + fn with_unknown_directive_on_var_definition() { + expect_fails_rule::<_, _, DefaultScalarValue>( + factory, + r#"query Foo( + $var1: Int = 1 @skip(if: true) @unknown, + $var2: String @deprecated + ) { + name + }"#, + &[ + RuleError::new( + &misplaced_error_message("skip", &DirectiveLocation::VariableDefinition), + &[SourcePosition::new(42, 1, 31)], + ), + RuleError::new( + &unknown_error_message("unknown"), + &[SourcePosition::new(58, 1, 47)], + ), + RuleError::new( + &misplaced_error_message("deprecated", &DirectiveLocation::VariableDefinition), + &[SourcePosition::new(98, 2, 30)], + ), + ], + ); + } + #[test] fn with_many_unknown_directives() { expect_fails_rule::<_, _, DefaultScalarValue>( diff --git a/juniper/src/validation/visitor.rs b/juniper/src/validation/visitor.rs index 21c31112..488e65ab 100644 --- a/juniper/src/validation/visitor.rs +++ b/juniper/src/validation/visitor.rs @@ -141,6 +141,19 @@ fn visit_variable_definitions<'a, S, V>( visit_input_value(v, ctx, default_value); } + if let Some(dirs) = &def.1.directives { + for directive in dirs { + let directive_arguments = ctx + .schema + .directive_by_name(directive.item.name.item) + .map(|d| &d.arguments); + + v.enter_directive(ctx, directive); + visit_arguments(v, ctx, directive_arguments, &directive.item.arguments); + v.exit_directive(ctx, directive); + } + } + v.exit_variable_definition(ctx, def); }) }