graphql_tools/validation/rules/
known_argument_names.rs

1use super::ValidationRule;
2use crate::ast::ext::TypeDefinitionExtension;
3use crate::ast::{
4    visit_document, FieldByNameExtension, OperationVisitor, OperationVisitorContext,
5    SchemaDocumentExtension,
6};
7use crate::static_graphql::query::Directive;
8use crate::static_graphql::schema::{InputValue, TypeDefinition};
9use crate::validation::utils::{ValidationError, ValidationErrorContext};
10/// Known argument names
11///
12/// A GraphQL field/directive is only valid if all supplied arguments are defined by
13/// that field.
14///
15/// See https://spec.graphql.org/draft/#sec-Argument-Names
16/// See https://spec.graphql.org/draft/#sec-Directives-Are-In-Valid-Locations
17pub struct KnownArgumentNames<'a> {
18    current_known_arguments: Option<(ArgumentParent<'a>, &'a Vec<InputValue>)>,
19}
20
21#[derive(Debug)]
22enum ArgumentParent<'a> {
23    Field(&'a str, &'a TypeDefinition),
24    Directive(&'a str),
25}
26
27impl<'a> Default for KnownArgumentNames<'a> {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33impl<'a> KnownArgumentNames<'a> {
34    pub fn new() -> Self {
35        KnownArgumentNames {
36            current_known_arguments: None,
37        }
38    }
39}
40
41impl<'a> OperationVisitor<'a, ValidationErrorContext> for KnownArgumentNames<'a> {
42    fn enter_directive(
43        &mut self,
44        visitor_context: &mut OperationVisitorContext<'a>,
45        _: &mut ValidationErrorContext,
46        directive: &Directive,
47    ) {
48        if let Some(directive_def) = visitor_context.schema.directive_by_name(&directive.name) {
49            self.current_known_arguments = Some((
50                ArgumentParent::Directive(&directive_def.name),
51                &directive_def.arguments,
52            ));
53        }
54    }
55
56    fn leave_directive(
57        &mut self,
58        _: &mut OperationVisitorContext,
59        _: &mut ValidationErrorContext,
60        _: &crate::static_graphql::query::Directive,
61    ) {
62        self.current_known_arguments = None;
63    }
64
65    fn enter_field(
66        &mut self,
67        visitor_context: &mut OperationVisitorContext<'a>,
68        _: &mut ValidationErrorContext,
69        field: &crate::static_graphql::query::Field,
70    ) {
71        if let Some(parent_type) = visitor_context.current_parent_type() {
72            if let Some(field_def) = parent_type.field_by_name(&field.name) {
73                self.current_known_arguments = Some((
74                    ArgumentParent::Field(
75                        &field_def.name,
76                        visitor_context
77                            .current_parent_type()
78                            .expect("Missing parent type"),
79                    ),
80                    &field_def.arguments,
81                ));
82            }
83        }
84    }
85
86    fn leave_field(
87        &mut self,
88        _: &mut OperationVisitorContext,
89        _: &mut ValidationErrorContext,
90        _: &crate::static_graphql::query::Field,
91    ) {
92        self.current_known_arguments = None;
93    }
94
95    fn enter_argument(
96        &mut self,
97        _: &mut OperationVisitorContext,
98        user_context: &mut ValidationErrorContext,
99        (argument_name, _argument_value): &(String, crate::static_graphql::query::Value),
100    ) {
101        if let Some((arg_position, args)) = &self.current_known_arguments {
102            if !args.iter().any(|a| a.name.eq(argument_name)) {
103                match arg_position {
104                    ArgumentParent::Field(field_name, type_name) => {
105                        user_context.report_error(ValidationError {
106                            error_code: self.error_code(),
107                            message: format!(
108                                "Unknown argument \"{}\" on field \"{}.{}\".",
109                                argument_name,
110                                type_name.name(),
111                                field_name
112                            ),
113                            locations: vec![],
114                        })
115                    }
116                    ArgumentParent::Directive(directive_name) => {
117                        user_context.report_error(ValidationError {
118                            error_code: self.error_code(),
119                            message: format!(
120                                "Unknown argument \"{}\" on directive \"@{}\".",
121                                argument_name, directive_name
122                            ),
123                            locations: vec![],
124                        })
125                    }
126                };
127            }
128        }
129    }
130}
131
132impl<'k> ValidationRule for KnownArgumentNames<'k> {
133    fn error_code<'a>(&self) -> &'a str {
134        "KnownArgumentNames"
135    }
136
137    fn validate(
138        &self,
139        ctx: &mut OperationVisitorContext,
140        error_collector: &mut ValidationErrorContext,
141    ) {
142        visit_document(
143            &mut KnownArgumentNames::new(),
144            ctx.operation,
145            ctx,
146            error_collector,
147        );
148    }
149}
150
151#[test]
152fn single_arg_is_known() {
153    use crate::validation::test_utils::*;
154
155    let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new()));
156    let errors = test_operation_with_schema(
157        "fragment argOnRequiredArg on Dog {
158          doesKnowCommand(dogCommand: SIT)
159        }",
160        TEST_SCHEMA,
161        &mut plan,
162    );
163
164    assert_eq!(get_messages(&errors).len(), 0);
165}
166
167#[test]
168fn multple_args_are_known() {
169    use crate::validation::test_utils::*;
170
171    let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new()));
172    let errors = test_operation_with_schema(
173        "fragment multipleArgs on ComplicatedArgs {
174          multipleReqs(req1: 1, req2: 2)
175        }",
176        TEST_SCHEMA,
177        &mut plan,
178    );
179
180    assert_eq!(get_messages(&errors).len(), 0);
181}
182
183#[test]
184fn ignores_args_of_unknown_fields() {
185    use crate::validation::test_utils::*;
186
187    let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new()));
188    let errors = test_operation_with_schema(
189        "fragment argOnUnknownField on Dog {
190          unknownField(unknownArg: SIT)
191        }",
192        TEST_SCHEMA,
193        &mut plan,
194    );
195
196    assert_eq!(get_messages(&errors).len(), 0);
197}
198
199#[test]
200fn multiple_args_in_reverse_order_are_known() {
201    use crate::validation::test_utils::*;
202
203    let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new()));
204    let errors = test_operation_with_schema(
205        "fragment multipleArgsReverseOrder on ComplicatedArgs {
206          multipleReqs(req2: 2, req1: 1)
207        }",
208        TEST_SCHEMA,
209        &mut plan,
210    );
211
212    assert_eq!(get_messages(&errors).len(), 0);
213}
214
215#[test]
216fn no_args_on_optional_arg() {
217    use crate::validation::test_utils::*;
218
219    let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new()));
220    let errors = test_operation_with_schema(
221        "fragment noArgOnOptionalArg on Dog {
222          isHouseTrained
223        }",
224        TEST_SCHEMA,
225        &mut plan,
226    );
227
228    assert_eq!(get_messages(&errors).len(), 0);
229}
230
231#[test]
232fn args_are_known_deeply() {
233    use crate::validation::test_utils::*;
234
235    let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new()));
236    let errors = test_operation_with_schema(
237        "{
238          dog {
239            doesKnowCommand(dogCommand: SIT)
240          }
241          human {
242            pet {
243              ... on Dog {
244                doesKnowCommand(dogCommand: SIT)
245              }
246            }
247          }
248        }",
249        TEST_SCHEMA,
250        &mut plan,
251    );
252
253    assert_eq!(get_messages(&errors).len(), 0);
254}
255
256#[test]
257fn directive_args_are_known() {
258    use crate::validation::test_utils::*;
259
260    let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new()));
261    let errors = test_operation_with_schema(
262        "{
263          dog @skip(if: true)
264        }",
265        TEST_SCHEMA,
266        &mut plan,
267    );
268
269    assert_eq!(get_messages(&errors).len(), 0);
270}
271
272#[test]
273fn field_args_are_invalid() {
274    use crate::validation::test_utils::*;
275
276    let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new()));
277    let errors = test_operation_with_schema(
278        "{
279          dog @skip(unless: true)
280        }",
281        TEST_SCHEMA,
282        &mut plan,
283    );
284
285    let messages = get_messages(&errors);
286    assert_eq!(messages.len(), 1);
287    assert_eq!(
288        messages,
289        vec!["Unknown argument \"unless\" on directive \"@skip\"."]
290    );
291}
292
293#[test]
294fn directive_without_args_is_valid() {
295    use crate::validation::test_utils::*;
296
297    let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new()));
298    let errors = test_operation_with_schema(
299        " {
300          dog @onField
301        }",
302        TEST_SCHEMA,
303        &mut plan,
304    );
305
306    let messages = get_messages(&errors);
307    assert_eq!(messages.len(), 0);
308}
309
310#[test]
311fn arg_passed_to_directive_without_arg_is_reported() {
312    use crate::validation::test_utils::*;
313
314    let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new()));
315    let errors = test_operation_with_schema(
316        " {
317          dog @onField(if: true)
318        }",
319        TEST_SCHEMA,
320        &mut plan,
321    );
322
323    let messages = get_messages(&errors);
324    assert_eq!(messages.len(), 1);
325    assert_eq!(
326        messages,
327        vec!["Unknown argument \"if\" on directive \"@onField\"."]
328    );
329}
330
331#[test]
332#[ignore = "Suggestions are not yet supported"]
333fn misspelled_directive_args_are_reported() {
334    use crate::validation::test_utils::*;
335
336    let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new()));
337    let errors = test_operation_with_schema(
338        "{
339          dog @skip(iff: true)
340        }",
341        TEST_SCHEMA,
342        &mut plan,
343    );
344
345    let messages = get_messages(&errors);
346    assert_eq!(messages.len(), 1);
347    assert_eq!(
348        messages,
349        vec!["Unknown argument \"iff\" on directive \"@onField\". Did you mean \"if\"?"]
350    );
351}
352
353#[test]
354fn invalid_arg_name() {
355    use crate::validation::test_utils::*;
356
357    let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new()));
358    let errors = test_operation_with_schema(
359        "fragment invalidArgName on Dog {
360          doesKnowCommand(unknown: true)
361        }",
362        TEST_SCHEMA,
363        &mut plan,
364    );
365
366    let messages = get_messages(&errors);
367    assert_eq!(messages.len(), 1);
368    assert_eq!(
369        messages,
370        vec!["Unknown argument \"unknown\" on field \"Dog.doesKnowCommand\"."]
371    );
372}
373
374#[test]
375#[ignore = "Suggestions are not yet supported"]
376fn misspelled_arg_name_is_reported() {
377    use crate::validation::test_utils::*;
378
379    let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new()));
380    let errors = test_operation_with_schema(
381        "fragment invalidArgName on Dog {
382          doesKnowCommand(DogCommand: true)
383        }",
384        TEST_SCHEMA,
385        &mut plan,
386    );
387
388    let messages = get_messages(&errors);
389    assert_eq!(messages.len(), 1);
390    assert_eq!(
391        messages,
392        vec!["Unknown argument \"DogCommand\" on field \"Dog.doesKnowCommand\". Did you mean \"dogCommand\"?"]
393    );
394}
395
396#[test]
397fn unknown_args_amongst_known_args() {
398    use crate::validation::test_utils::*;
399
400    let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new()));
401    let errors = test_operation_with_schema(
402        "fragment oneGoodArgOneInvalidArg on Dog {
403          doesKnowCommand(whoKnows: 1, dogCommand: SIT, unknown: true)
404        }",
405        TEST_SCHEMA,
406        &mut plan,
407    );
408
409    let messages = get_messages(&errors);
410    assert_eq!(messages.len(), 2);
411    assert_eq!(
412        messages,
413        vec![
414            "Unknown argument \"whoKnows\" on field \"Dog.doesKnowCommand\".",
415            "Unknown argument \"unknown\" on field \"Dog.doesKnowCommand\"."
416        ]
417    );
418}
419
420#[test]
421fn unknown_args_deeply() {
422    use crate::validation::test_utils::*;
423
424    let mut plan = create_plan_from_rule(Box::new(KnownArgumentNames::new()));
425    let errors = test_operation_with_schema(
426        "{
427          dog {
428            doesKnowCommand(unknown: true)
429          }
430          human {
431            pet {
432              ... on Dog {
433                doesKnowCommand(unknown: true)
434              }
435            }
436          }
437        }",
438        TEST_SCHEMA,
439        &mut plan,
440    );
441
442    let messages = get_messages(&errors);
443    assert_eq!(messages.len(), 2);
444    assert_eq!(
445        messages,
446        vec![
447            "Unknown argument \"unknown\" on field \"Dog.doesKnowCommand\".",
448            "Unknown argument \"unknown\" on field \"Dog.doesKnowCommand\"."
449        ]
450    );
451}