diff --git a/juniper/src/tests/query_tests.rs b/juniper/src/tests/query_tests.rs index 56eae4682..cc0f66616 100644 --- a/juniper/src/tests/query_tests.rs +++ b/juniper/src/tests/query_tests.rs @@ -1,5 +1,6 @@ use crate::{ - graphql, + GraphQLError, RuleError, graphql, + parser::SourcePosition, schema::model::RootNode, tests::fixtures::starwars::schema::{Database, Query}, types::scalars::{EmptyMutation, EmptySubscription}, @@ -290,6 +291,26 @@ async fn test_query_name_invalid_variable() { ); } +#[tokio::test] +async fn test_rejects_unknown_argument_on_query_field_without_args() { + let doc = "{ hero { name(unknownArg: true) } }"; + let database = Database::new(); + let schema = RootNode::new( + Query, + EmptyMutation::::new(), + EmptySubscription::::new(), + ); + + assert_eq!( + crate::execute(doc, None, &schema, &graphql::vars! {}, &database).await, + Err(GraphQLError::ValidationError(vec![RuleError::new( + r#"Unknown argument "unknownArg" on field "name" of type "Character""#, + &[SourcePosition::new(14, 0, 14)], + )])), + "expected validation error, got successful response", + ); +} + #[tokio::test] async fn test_query_friends_names() { let doc = r#"{ human(id: "1000") { friends { name } } }"#; diff --git a/juniper/src/tests/subscriptions.rs b/juniper/src/tests/subscriptions.rs index 9edab8f4b..4b8618fba 100644 --- a/juniper/src/tests/subscriptions.rs +++ b/juniper/src/tests/subscriptions.rs @@ -148,7 +148,7 @@ fn create_and_execute( #[test] fn returns_requested_object() { let query = r#"subscription { - asyncHuman(id: "1") { + asyncHuman { id name } @@ -176,7 +176,7 @@ fn returns_requested_object() { #[test] fn returns_error() { let query = r#"subscription { - errorHuman(id: "1") { + errorHuman { id name } @@ -227,7 +227,7 @@ fn can_access_context() { fn resolves_typed_inline_fragments() { let query = r#"subscription { ... on MySubscription { - asyncHuman(id: "32") { + asyncHuman { id } } @@ -255,7 +255,7 @@ fn resolves_typed_inline_fragments() { fn resolves_nontyped_inline_fragments() { let query = r#"subscription { ... { - asyncHuman(id: "32") { + asyncHuman { id } } @@ -310,7 +310,7 @@ fn can_access_arguments() { #[test] fn type_alias() { let query = r#"subscription { - aliasedHuman: asyncHuman(id: "1") { + aliasedHuman: asyncHuman { id name } diff --git a/juniper/src/validation/rules/known_argument_names.rs b/juniper/src/validation/rules/known_argument_names.rs index 7aecf42e3..02756d5a4 100644 --- a/juniper/src/validation/rules/known_argument_names.rs +++ b/juniper/src/validation/rules/known_argument_names.rs @@ -14,7 +14,7 @@ enum ArgumentPosition<'a> { } pub struct KnownArgumentNames<'a, S: Debug + 'a> { - current_args: Option<(ArgumentPosition<'a>, &'a Vec>)>, + current_args: Option<(ArgumentPosition<'a>, &'a [Argument])>, } pub fn factory<'a, S: Debug>() -> KnownArgumentNames<'a, S> { @@ -36,7 +36,7 @@ where .map(|d| { ( ArgumentPosition::Directive(directive.item.name.item), - &d.arguments, + d.arguments.as_slice(), ) }); } @@ -48,18 +48,14 @@ where fn enter_field(&mut self, ctx: &mut ValidatorContext<'a, S>, field: &'a Spanning>) { self.current_args = ctx .parent_type() - .and_then(|t| t.field_by_name(field.item.name.item)) - .and_then(|f| f.arguments.as_ref()) - .map(|args| { + .and_then(|t| t.field_by_name(field.item.name.item).map(|f| (t, f))) + .map(|(t, f)| { ( ArgumentPosition::Field( field.item.name.item, - ctx.parent_type() - .expect("Parent type should exist") - .name() - .expect("Parent type should be named"), + t.name().expect("Parent type should be named"), ), - args, + f.arguments.as_deref().unwrap_or(&[]), ) }); } @@ -144,6 +140,18 @@ mod tests { ); } + #[test] + fn field_with_no_args_fails_on_provided_args() { + expect_fails_rule::<_, _, DefaultScalarValue>( + factory, + r#"{ dog { nickname(unknownArg: SIT) } }"#, + &[RuleError::new( + &field_error_message("unknownArg", "nickname", "Dog"), + &[SourcePosition::new(17, 0, 17)], + )], + ); + } + #[test] fn multiple_args_in_reverse_order_are_known() { expect_passes_rule::<_, _, DefaultScalarValue>(