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