graphql_tools/validation/rules/
no_undefined_variables.rs

1use super::ValidationRule;
2use crate::ast::{
3    visit_document, AstNodeWithName, OperationVisitor, OperationVisitorContext, ValueExtension,
4};
5use crate::static_graphql::query::{self, OperationDefinition};
6use crate::validation::utils::{ValidationError, ValidationErrorContext};
7use std::collections::{HashMap, HashSet};
8
9/// No undefined variables
10///
11/// A GraphQL operation is only valid if all variables encountered, both directly
12/// and via fragment spreads, are defined by that operation.
13///
14/// See https://spec.graphql.org/draft/#sec-All-Variable-Uses-Defined
15pub struct NoUndefinedVariables<'a> {
16    current_scope: Option<NoUndefinedVariablesScope<'a>>,
17    defined_variables: HashMap<Option<&'a str>, HashSet<&'a str>>,
18    used_variables: HashMap<NoUndefinedVariablesScope<'a>, Vec<&'a str>>,
19    spreads: HashMap<NoUndefinedVariablesScope<'a>, Vec<&'a str>>,
20}
21
22impl<'a> Default for NoUndefinedVariables<'a> {
23    fn default() -> Self {
24        Self::new()
25    }
26}
27
28impl<'a> NoUndefinedVariables<'a> {
29    pub fn new() -> Self {
30        Self {
31            current_scope: None,
32            defined_variables: HashMap::new(),
33            used_variables: HashMap::new(),
34            spreads: HashMap::new(),
35        }
36    }
37}
38
39impl<'a> NoUndefinedVariables<'a> {
40    fn find_undefined_vars(
41        &self,
42        from: &NoUndefinedVariablesScope<'a>,
43        defined: &HashSet<&str>,
44        unused: &mut HashSet<&'a str>,
45        visited: &mut HashSet<NoUndefinedVariablesScope<'a>>,
46    ) {
47        if visited.contains(from) {
48            return;
49        }
50
51        visited.insert(from.clone());
52
53        if let Some(used_vars) = self.used_variables.get(from) {
54            for var in used_vars {
55                if !defined.contains(*var) {
56                    unused.insert(*var);
57                }
58            }
59        }
60
61        if let Some(spreads) = self.spreads.get(from) {
62            for spread in spreads {
63                self.find_undefined_vars(
64                    &NoUndefinedVariablesScope::Fragment(spread),
65                    defined,
66                    unused,
67                    visited,
68                );
69            }
70        }
71    }
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, Hash)]
75pub enum NoUndefinedVariablesScope<'a> {
76    Operation(Option<&'a str>),
77    Fragment(&'a str),
78}
79
80impl<'a> OperationVisitor<'a, ValidationErrorContext> for NoUndefinedVariables<'a> {
81    fn enter_operation_definition(
82        &mut self,
83        _: &mut OperationVisitorContext,
84        _: &mut ValidationErrorContext,
85        operation_definition: &'a OperationDefinition,
86    ) {
87        let op_name = operation_definition.node_name();
88        self.current_scope = Some(NoUndefinedVariablesScope::Operation(op_name));
89        self.defined_variables.insert(op_name, HashSet::new());
90    }
91
92    fn enter_fragment_definition(
93        &mut self,
94        _: &mut OperationVisitorContext,
95        _: &mut ValidationErrorContext,
96        fragment_definition: &'a query::FragmentDefinition,
97    ) {
98        self.current_scope = Some(NoUndefinedVariablesScope::Fragment(
99            &fragment_definition.name,
100        ));
101    }
102
103    fn enter_fragment_spread(
104        &mut self,
105        _: &mut OperationVisitorContext,
106        _: &mut ValidationErrorContext,
107        fragment_spread: &'a query::FragmentSpread,
108    ) {
109        if let Some(scope) = &self.current_scope {
110            self.spreads
111                .entry(scope.clone())
112                .or_default()
113                .push(&fragment_spread.fragment_name);
114        }
115    }
116
117    fn enter_variable_definition(
118        &mut self,
119        _: &mut OperationVisitorContext,
120        _: &mut ValidationErrorContext,
121        variable_definition: &'a query::VariableDefinition,
122    ) {
123        if let Some(NoUndefinedVariablesScope::Operation(ref name)) = self.current_scope {
124            if let Some(vars) = self.defined_variables.get_mut(name) {
125                vars.insert(&variable_definition.name);
126            }
127        }
128    }
129
130    fn enter_argument(
131        &mut self,
132        _: &mut OperationVisitorContext,
133        _: &mut ValidationErrorContext,
134        (_arg_name, arg_value): &'a (String, query::Value),
135    ) {
136        if let Some(ref scope) = self.current_scope {
137            self.used_variables
138                .entry(scope.clone())
139                .or_default()
140                .append(&mut arg_value.variables_in_use());
141        }
142    }
143
144    fn leave_document(
145        &mut self,
146        _: &mut OperationVisitorContext,
147        user_context: &mut ValidationErrorContext,
148        _: &query::Document,
149    ) {
150        for (op_name, def_vars) in &self.defined_variables {
151            let mut unused = HashSet::new();
152            let mut visited = HashSet::new();
153
154            self.find_undefined_vars(
155                &NoUndefinedVariablesScope::Operation(*op_name),
156                def_vars,
157                &mut unused,
158                &mut visited,
159            );
160
161            unused.iter().for_each(|var| {
162                user_context.report_error(ValidationError {
163                    error_code: self.error_code(),
164                    message: error_message(var, op_name),
165                    locations: vec![],
166                })
167            })
168        }
169    }
170}
171
172fn error_message(var_name: &str, op_name: &Option<&str>) -> String {
173    if let Some(op_name) = op_name {
174        format!(
175            r#"Variable "${}" is not defined by operation "{}"."#,
176            var_name, op_name
177        )
178    } else {
179        format!(r#"Variable "${}" is not defined."#, var_name)
180    }
181}
182
183impl<'n> ValidationRule for NoUndefinedVariables<'n> {
184    fn error_code<'a>(&self) -> &'a str {
185        "NoUndefinedVariables"
186    }
187
188    fn validate(
189        &self,
190        ctx: &mut OperationVisitorContext,
191        error_collector: &mut ValidationErrorContext,
192    ) {
193        visit_document(
194            &mut NoUndefinedVariables::new(),
195            ctx.operation,
196            ctx,
197            error_collector,
198        );
199    }
200}
201
202#[test]
203fn all_variables_defined() {
204    use crate::validation::test_utils::*;
205
206    let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
207    let errors = test_operation_with_schema(
208        "query Foo($a: String, $b: String, $c: String) {
209          field(a: $a, b: $b, c: $c)
210        }",
211        TEST_SCHEMA,
212        &mut plan,
213    );
214
215    let messages = get_messages(&errors);
216    assert_eq!(messages.len(), 0);
217}
218
219#[test]
220fn all_variables_deeply_defined() {
221    use crate::validation::test_utils::*;
222
223    let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
224    let errors = test_operation_with_schema(
225        "query Foo($a: String, $b: String, $c: String) {
226          field(a: $a) {
227            field(b: $b) {
228              field(c: $c)
229            }
230          }
231        }",
232        TEST_SCHEMA,
233        &mut plan,
234    );
235
236    let messages = get_messages(&errors);
237    assert_eq!(messages.len(), 0);
238}
239
240#[test]
241fn all_variables_deeply_in_inline_fragments_defined() {
242    use crate::validation::test_utils::*;
243
244    let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
245    let errors = test_operation_with_schema(
246        "query Foo($a: String, $b: String, $c: String) {
247          ... on Type {
248            field(a: $a) {
249              field(b: $b) {
250                ... on Type {
251                  field(c: $c)
252                }
253              }
254            }
255          }
256        }",
257        TEST_SCHEMA,
258        &mut plan,
259    );
260
261    let messages = get_messages(&errors);
262    assert_eq!(messages.len(), 0);
263}
264
265#[test]
266fn all_variables_in_fragments_deeply_defined() {
267    use crate::validation::test_utils::*;
268
269    let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
270    let errors = test_operation_with_schema(
271        "query Foo($a: String, $b: String, $c: String) {
272          ...FragA
273        }
274        fragment FragA on Type {
275          field(a: $a) {
276            ...FragB
277          }
278        }
279        fragment FragB on Type {
280          field(b: $b) {
281            ...FragC
282          }
283        }
284        fragment FragC on Type {
285          field(c: $c)
286        }",
287        TEST_SCHEMA,
288        &mut plan,
289    );
290
291    let messages = get_messages(&errors);
292    assert_eq!(messages.len(), 0);
293}
294
295#[test]
296fn variable_within_single_fragment_defined_in_multiple_operations() {
297    use crate::validation::test_utils::*;
298
299    let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
300    let errors = test_operation_with_schema(
301        "query Foo($a: String) {
302          ...FragA
303        }
304        query Bar($a: String) {
305          ...FragA
306        }
307        fragment FragA on Type {
308          field(a: $a)
309        }",
310        TEST_SCHEMA,
311        &mut plan,
312    );
313
314    let messages = get_messages(&errors);
315    assert_eq!(messages.len(), 0);
316}
317
318#[test]
319fn variable_within_fragments_defined_in_operations() {
320    use crate::validation::test_utils::*;
321
322    let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
323    let errors = test_operation_with_schema(
324        "query Foo($a: String) {
325          ...FragA
326        }
327        query Bar($b: String) {
328          ...FragB
329        }
330        fragment FragA on Type {
331          field(a: $a)
332        }
333        fragment FragB on Type {
334          field(b: $b)
335        }",
336        TEST_SCHEMA,
337        &mut plan,
338    );
339
340    let messages = get_messages(&errors);
341    assert_eq!(messages.len(), 0);
342}
343
344#[test]
345fn variable_within_recursive_fragment_defined() {
346    use crate::validation::test_utils::*;
347
348    let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
349    let errors = test_operation_with_schema(
350        "query Foo($a: String) {
351          ...FragA
352        }
353        fragment FragA on Type {
354          field(a: $a) {
355            ...FragA
356          }
357        }",
358        TEST_SCHEMA,
359        &mut plan,
360    );
361
362    let messages = get_messages(&errors);
363    assert_eq!(messages.len(), 0);
364}
365
366#[test]
367fn variable_not_defined() {
368    use crate::validation::test_utils::*;
369
370    let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
371    let errors = test_operation_with_schema(
372        "query Foo($a: String, $b: String, $c: String) {
373          field(a: $a, b: $b, c: $c, d: $d)
374        }",
375        TEST_SCHEMA,
376        &mut plan,
377    );
378
379    let messages = get_messages(&errors);
380    assert_eq!(messages.len(), 1);
381    assert_eq!(
382        messages,
383        vec!["Variable \"$d\" is not defined by operation \"Foo\"."]
384    );
385}
386
387#[test]
388fn variable_not_defined_by_un_named_query() {
389    use crate::validation::test_utils::*;
390
391    let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
392    let errors = test_operation_with_schema(
393        "{
394          field(a: $a)
395        }",
396        TEST_SCHEMA,
397        &mut plan,
398    );
399
400    let messages = get_messages(&errors);
401    assert_eq!(messages.len(), 1);
402    assert_eq!(messages, vec!["Variable \"$a\" is not defined."]);
403}
404
405#[test]
406fn multiple_variables_not_defined() {
407    use crate::validation::test_utils::*;
408
409    let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
410    let errors = test_operation_with_schema(
411        "query Foo($b: String) {
412          field(a: $a, b: $b, c: $c)
413        }",
414        TEST_SCHEMA,
415        &mut plan,
416    );
417
418    let messages = get_messages(&errors);
419    assert_eq!(messages.len(), 2);
420    assert!(messages.contains(&&"Variable \"$a\" is not defined by operation \"Foo\".".to_owned()));
421    assert!(messages.contains(&&"Variable \"$c\" is not defined by operation \"Foo\".".to_owned()));
422}
423
424#[test]
425fn variable_in_fragment_not_defined_by_un_named_query() {
426    use crate::validation::test_utils::*;
427
428    let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
429    let errors = test_operation_with_schema(
430        "{
431          ...FragA
432        }
433        fragment FragA on Type {
434          field(a: $a)
435        }",
436        TEST_SCHEMA,
437        &mut plan,
438    );
439
440    let messages = get_messages(&errors);
441    assert_eq!(messages.len(), 1);
442    assert_eq!(messages, vec!["Variable \"$a\" is not defined.",]);
443}
444
445#[test]
446fn variable_in_fragment_not_defined_by_operation() {
447    use crate::validation::test_utils::*;
448
449    let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
450    let errors = test_operation_with_schema(
451        "query Foo($a: String, $b: String) {
452          ...FragA
453        }
454        fragment FragA on Type {
455          field(a: $a) {
456            ...FragB
457          }
458        }
459        fragment FragB on Type {
460          field(b: $b) {
461            ...FragC
462          }
463        }
464        fragment FragC on Type {
465          field(c: $c)
466        }",
467        TEST_SCHEMA,
468        &mut plan,
469    );
470
471    let messages = get_messages(&errors);
472    assert_eq!(messages.len(), 1);
473    assert_eq!(
474        messages,
475        vec!["Variable \"$c\" is not defined by operation \"Foo\"."]
476    );
477}
478
479#[test]
480fn multiple_variables_in_fragments_not_defined() {
481    use crate::validation::test_utils::*;
482
483    let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
484    let errors = test_operation_with_schema(
485        "query Foo($b: String) {
486          ...FragA
487        }
488        fragment FragA on Type {
489          field(a: $a) {
490            ...FragB
491          }
492        }
493        fragment FragB on Type {
494          field(b: $b) {
495            ...FragC
496          }
497        }
498        fragment FragC on Type {
499          field(c: $c)
500        }",
501        TEST_SCHEMA,
502        &mut plan,
503    );
504
505    let messages = get_messages(&errors);
506    assert_eq!(messages.len(), 2);
507    assert!(messages.contains(&&"Variable \"$c\" is not defined by operation \"Foo\".".to_owned()));
508    assert!(messages.contains(&&"Variable \"$a\" is not defined by operation \"Foo\".".to_owned()));
509}
510
511#[test]
512fn single_variable_in_fragment_not_defined_by_multiple_operations() {
513    use crate::validation::test_utils::*;
514
515    let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
516    let errors = test_operation_with_schema(
517        "query Foo($a: String) {
518          ...FragAB
519        }
520        query Bar($a: String) {
521          ...FragAB
522        }
523        fragment FragAB on Type {
524          field(a: $a, b: $b)
525        }",
526        TEST_SCHEMA,
527        &mut plan,
528    );
529
530    let messages = get_messages(&errors);
531    assert_eq!(messages.len(), 2);
532    assert!(messages.contains(&&"Variable \"$b\" is not defined by operation \"Bar\".".to_owned()));
533    assert!(messages.contains(&&"Variable \"$b\" is not defined by operation \"Foo\".".to_owned()));
534}
535
536#[test]
537fn variables_in_fragment_not_defined_by_multiple_operations() {
538    use crate::validation::test_utils::*;
539
540    let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
541    let errors = test_operation_with_schema(
542        "query Foo($b: String) {
543          ...FragAB
544        }
545        query Bar($a: String) {
546          ...FragAB
547        }
548        fragment FragAB on Type {
549          field(a: $a, b: $b)
550        }",
551        TEST_SCHEMA,
552        &mut plan,
553    );
554
555    let messages = get_messages(&errors);
556    assert_eq!(messages.len(), 2);
557    assert!(messages.contains(&&"Variable \"$a\" is not defined by operation \"Foo\".".to_owned()));
558    assert!(messages.contains(&&"Variable \"$b\" is not defined by operation \"Bar\".".to_owned()));
559}
560
561#[test]
562fn variable_in_fragment_used_by_other_operation() {
563    use crate::validation::test_utils::*;
564
565    let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
566    let errors = test_operation_with_schema(
567        "query Foo($b: String) {
568          ...FragA
569        }
570        query Bar($a: String) {
571          ...FragB
572        }
573        fragment FragA on Type {
574          field(a: $a)
575        }
576        fragment FragB on Type {
577          field(b: $b)
578        }",
579        TEST_SCHEMA,
580        &mut plan,
581    );
582
583    let messages = get_messages(&errors);
584    assert_eq!(messages.len(), 2);
585    assert!(messages.contains(&&"Variable \"$a\" is not defined by operation \"Foo\".".to_owned()));
586    assert!(messages.contains(&&"Variable \"$b\" is not defined by operation \"Bar\".".to_owned()));
587}
588#[test]
589fn multiple_undefined_variables_produce_multiple_errors() {
590    use crate::validation::test_utils::*;
591
592    let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
593    let errors = test_operation_with_schema(
594        "query Foo($b: String) {
595          ...FragAB
596        }
597        query Bar($a: String) {
598          ...FragAB
599        }
600        fragment FragAB on Type {
601          field1(a: $a, b: $b)
602          ...FragC
603          field3(a: $a, b: $b)
604        }
605        fragment FragC on Type {
606          field2(c: $c)
607        }",
608        TEST_SCHEMA,
609        &mut plan,
610    );
611
612    let messages = get_messages(&errors);
613    assert_eq!(messages.len(), 4);
614    assert!(messages.contains(&&"Variable \"$c\" is not defined by operation \"Foo\".".to_owned()));
615    assert!(messages.contains(&&"Variable \"$a\" is not defined by operation \"Foo\".".to_owned()));
616    assert!(messages.contains(&&"Variable \"$b\" is not defined by operation \"Bar\".".to_owned()));
617    assert!(messages.contains(&&"Variable \"$c\" is not defined by operation \"Bar\".".to_owned()));
618}