Skip to main content

graphql_tools/validation/rules/
known_argument_names.rs

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