graphql_tools/validation/rules/
provided_required_arguments.rs

1use super::ValidationRule;
2use crate::ast::{
3    visit_document, FieldByNameExtension, InputValueHelpers, OperationVisitor,
4    OperationVisitorContext,
5};
6use crate::static_graphql::query::Value;
7use crate::static_graphql::schema::InputValue;
8use crate::validation::utils::{ValidationError, ValidationErrorContext};
9
10/// Provided required arguments
11///
12/// A field or directive is only valid if all required (non-null without a
13/// default value) field arguments have been provided.
14///
15/// See https://spec.graphql.org/draft/#sec-Required-Arguments
16pub struct ProvidedRequiredArguments;
17
18impl Default for ProvidedRequiredArguments {
19    fn default() -> Self {
20        Self::new()
21    }
22}
23
24impl ProvidedRequiredArguments {
25    pub fn new() -> Self {
26        ProvidedRequiredArguments
27    }
28}
29
30impl<'a> OperationVisitor<'a, ValidationErrorContext> for ProvidedRequiredArguments {
31    fn enter_field(
32        &mut self,
33        visitor_context: &mut OperationVisitorContext,
34        user_context: &mut ValidationErrorContext,
35        field: &crate::static_graphql::query::Field,
36    ) {
37        if let Some(parent_type) = visitor_context.current_parent_type() {
38            if let Some(field_def) = parent_type.field_by_name(&field.name) {
39                let missing_required_args =
40                    validate_arguments(&field.arguments, &field_def.arguments);
41
42                for missing in missing_required_args {
43                    user_context.report_error(ValidationError {error_code: self.error_code(),
44              locations: vec![field.position],
45              message: format!("Field \"{}\" argument \"{}\" of type \"{}\" is required, but it was not provided.",
46              field.name, missing.name, missing.value_type),
47          });
48                }
49            }
50        }
51    }
52
53    fn enter_directive(
54        &mut self,
55        visitor_context: &mut OperationVisitorContext,
56        user_context: &mut ValidationErrorContext,
57        directive: &crate::static_graphql::query::Directive,
58    ) {
59        let known_directives = &visitor_context.directives;
60
61        if let Some(directive_def) = known_directives.get(&directive.name) {
62            let missing_required_args =
63                validate_arguments(&directive.arguments, &directive_def.arguments);
64
65            for missing in missing_required_args {
66                user_context.report_error(ValidationError {error_code: self.error_code(),
67              locations: vec![directive.position],
68              message: format!("Directive \"@{}\" argument \"{}\" of type \"{}\" is required, but it was not provided.",
69              directive.name, missing.name, missing.value_type),
70          });
71            }
72        }
73    }
74}
75
76fn validate_arguments(
77    arguments_used: &[(String, Value)],
78    arguments_defined: &[InputValue],
79) -> Vec<InputValue> {
80    arguments_defined
81        .iter()
82        .filter_map(|field_arg_def| {
83            if field_arg_def.is_required()
84                && !arguments_used
85                    .iter()
86                    .any(|(name, _value)| name.eq(&field_arg_def.name))
87            {
88                Some(field_arg_def.clone())
89            } else {
90                None
91            }
92        })
93        .collect()
94}
95
96impl ValidationRule for ProvidedRequiredArguments {
97    fn error_code<'a>(&self) -> &'a str {
98        "ProvidedRequiredArguments"
99    }
100
101    fn validate(
102        &self,
103        ctx: &mut OperationVisitorContext,
104        error_collector: &mut ValidationErrorContext,
105    ) {
106        visit_document(
107            &mut ProvidedRequiredArguments::new(),
108            ctx.operation,
109            ctx,
110            error_collector,
111        );
112    }
113}
114
115#[test]
116fn ignores_unknown_arguments() {
117    use crate::validation::test_utils::*;
118
119    let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {}));
120    let errors = test_operation_with_schema(
121        "{
122          dog {
123            isHouseTrained(unknownArgument: true)
124          }
125        }",
126        TEST_SCHEMA,
127        &mut plan,
128    );
129
130    assert_eq!(get_messages(&errors).len(), 0);
131}
132
133#[test]
134fn arg_on_optional_arg() {
135    use crate::validation::test_utils::*;
136
137    let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {}));
138    let errors = test_operation_with_schema(
139        "{
140          dog {
141            isHouseTrained(atOtherHomes: true)
142          }
143        }",
144        TEST_SCHEMA,
145        &mut plan,
146    );
147
148    assert_eq!(get_messages(&errors).len(), 0);
149}
150
151#[test]
152fn no_arg_on_optional_arg() {
153    use crate::validation::test_utils::*;
154
155    let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {}));
156    let errors = test_operation_with_schema(
157        "{
158          dog {
159            isHouseTrained
160          }
161        }",
162        TEST_SCHEMA,
163        &mut plan,
164    );
165
166    assert_eq!(get_messages(&errors).len(), 0);
167}
168
169#[test]
170fn multiple_args() {
171    use crate::validation::test_utils::*;
172
173    let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {}));
174    let errors = test_operation_with_schema(
175        "{
176          complicatedArgs {
177            multipleReqs(req1: 1, req2: 2)
178          }
179        }",
180        TEST_SCHEMA,
181        &mut plan,
182    );
183
184    assert_eq!(get_messages(&errors).len(), 0);
185}
186
187#[test]
188fn multiple_args_reverse_order() {
189    use crate::validation::test_utils::*;
190
191    let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {}));
192    let errors = test_operation_with_schema(
193        "{
194          complicatedArgs {
195            multipleReqs(req2: 2, req1: 1)
196          }
197        }",
198        TEST_SCHEMA,
199        &mut plan,
200    );
201
202    assert_eq!(get_messages(&errors).len(), 0);
203}
204
205#[test]
206fn no_args_on_multiple_optional() {
207    use crate::validation::test_utils::*;
208
209    let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {}));
210    let errors = test_operation_with_schema(
211        "{
212          complicatedArgs {
213            multipleOpts
214          }
215        }",
216        TEST_SCHEMA,
217        &mut plan,
218    );
219
220    assert_eq!(get_messages(&errors).len(), 0);
221}
222
223#[test]
224fn one_arg_on_multiple_optional() {
225    use crate::validation::test_utils::*;
226
227    let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {}));
228    let errors = test_operation_with_schema(
229        "{
230          complicatedArgs {
231            multipleOpts(opt1: 1)
232          }
233        }",
234        TEST_SCHEMA,
235        &mut plan,
236    );
237
238    assert_eq!(get_messages(&errors).len(), 0);
239}
240
241#[test]
242fn second_arg_on_multiple_optional() {
243    use crate::validation::test_utils::*;
244
245    let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {}));
246    let errors = test_operation_with_schema(
247        "{
248          complicatedArgs {
249            multipleOpts(opt2: 1)
250          }
251        }",
252        TEST_SCHEMA,
253        &mut plan,
254    );
255
256    assert_eq!(get_messages(&errors).len(), 0);
257}
258
259#[test]
260fn multiple_required_args_on_mixed_list() {
261    use crate::validation::test_utils::*;
262
263    let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {}));
264    let errors = test_operation_with_schema(
265        "{
266          complicatedArgs {
267            multipleOptAndReq(req1: 3, req2: 4)
268          }
269        }",
270        TEST_SCHEMA,
271        &mut plan,
272    );
273
274    assert_eq!(get_messages(&errors).len(), 0);
275}
276
277#[test]
278fn multiple_required_and_one_optional_arg_on_mixedlist() {
279    use crate::validation::test_utils::*;
280
281    let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {}));
282    let errors = test_operation_with_schema(
283        "{
284          complicatedArgs {
285            multipleOptAndReq(req1: 3, req2: 4, opt1: 5)
286          }
287        }",
288        TEST_SCHEMA,
289        &mut plan,
290    );
291
292    assert_eq!(get_messages(&errors).len(), 0);
293}
294
295#[test]
296fn all_required_and_optional_args_on_mixedlist() {
297    use crate::validation::test_utils::*;
298
299    let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {}));
300    let errors = test_operation_with_schema(
301        "{
302          complicatedArgs {
303            multipleOptAndReq(req1: 3, req2: 4, opt1: 5, opt2: 6)
304          }
305        }",
306        TEST_SCHEMA,
307        &mut plan,
308    );
309
310    assert_eq!(get_messages(&errors).len(), 0);
311}
312
313#[test]
314fn missing_one_non_nullable_argument() {
315    use crate::validation::test_utils::*;
316
317    let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {}));
318    let errors = test_operation_with_schema(
319        "{
320          complicatedArgs {
321            multipleReqs(req2: 2)
322          }
323        }",
324        TEST_SCHEMA,
325        &mut plan,
326    );
327
328    let messages = get_messages(&errors);
329    assert_eq!(messages.len(), 1);
330    assert_eq!(messages, vec![
331      "Field \"multipleReqs\" argument \"req1\" of type \"Int!\" is required, but it was not provided."
332    ]);
333}
334
335#[test]
336fn missing_multiple_non_nullable_arguments() {
337    use crate::validation::test_utils::*;
338
339    let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {}));
340    let errors = test_operation_with_schema(
341        "{
342          complicatedArgs {
343            multipleReqs
344          }
345        }",
346        TEST_SCHEMA,
347        &mut plan,
348    );
349
350    let messages = get_messages(&errors);
351    assert_eq!(messages.len(), 2);
352    assert_eq!(messages, vec![
353      "Field \"multipleReqs\" argument \"req1\" of type \"Int!\" is required, but it was not provided.",
354      "Field \"multipleReqs\" argument \"req2\" of type \"Int!\" is required, but it was not provided."
355    ]);
356}
357
358#[test]
359fn incorrect_value_and_missing_argument() {
360    use crate::validation::test_utils::*;
361
362    let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {}));
363    let errors = test_operation_with_schema(
364        "{
365          complicatedArgs {
366            multipleReqs(req1: \"one\")
367          }
368        }",
369        TEST_SCHEMA,
370        &mut plan,
371    );
372
373    let messages = get_messages(&errors);
374    assert_eq!(messages.len(), 1);
375    assert_eq!(messages, vec![
376      "Field \"multipleReqs\" argument \"req2\" of type \"Int!\" is required, but it was not provided."
377    ]);
378}
379
380#[test]
381fn ignores_unknown_directives() {
382    use crate::validation::test_utils::*;
383
384    let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {}));
385    let errors = test_operation_with_schema(
386        "{
387          dog @unknown
388        }",
389        TEST_SCHEMA,
390        &mut plan,
391    );
392
393    let messages = get_messages(&errors);
394    assert_eq!(messages.len(), 0);
395}
396
397#[test]
398fn with_directives_of_valid_types() {
399    use crate::validation::test_utils::*;
400
401    let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {}));
402    let errors = test_operation_with_schema(
403        "{
404          dog @include(if: true) {
405            name
406          }
407          human @skip(if: false) {
408            name
409          }
410        }",
411        TEST_SCHEMA,
412        &mut plan,
413    );
414
415    let messages = get_messages(&errors);
416    assert_eq!(messages.len(), 0);
417}
418
419#[test]
420fn with_directive_with_missing_types() {
421    use crate::validation::test_utils::*;
422
423    let mut plan = create_plan_from_rule(Box::new(ProvidedRequiredArguments {}));
424    let errors = test_operation_with_schema(
425        "{
426          dog @include {
427            name @skip
428          }
429        }",
430        TEST_SCHEMA,
431        &mut plan,
432    );
433
434    let messages = get_messages(&errors);
435    assert_eq!(messages.len(), 2);
436    assert_eq!(messages, vec![
437      "Directive \"@include\" argument \"if\" of type \"Boolean!\" is required, but it was not provided.",
438      "Directive \"@skip\" argument \"if\" of type \"Boolean!\" is required, but it was not provided."
439    ])
440}