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
9pub 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}