Skip to main content

graphql_tools/validation/rules/
known_argument_names.rs

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