1use super::ValidationRule;
2use crate::ast::{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<'doc> {
14 current_scope: Option<NoUndefinedVariablesScope<'doc>>,
15 defined_variables: HashMap<Option<&'doc str>, HashSet<&'doc str>>,
16 used_variables: HashMap<NoUndefinedVariablesScope<'doc>, Vec<&'doc str>>,
17 spreads: HashMap<NoUndefinedVariablesScope<'doc>, Vec<&'doc str>>,
18}
19
20impl Default for NoUndefinedVariables<'_> {
21 fn default() -> Self {
22 Self::new()
23 }
24}
25
26impl NoUndefinedVariables<'_> {
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<'doc> NoUndefinedVariables<'doc> {
38 fn find_undefined_vars(
39 &self,
40 from: &NoUndefinedVariablesScope<'doc>,
41 defined: &HashSet<&str>,
42 unused: &mut HashSet<&'doc str>,
43 visited: &mut HashSet<NoUndefinedVariablesScope<'doc>>,
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<'doc> {
74 Operation(Option<&'doc str>),
75 Fragment(&'doc str),
76}
77
78impl<'doc> OperationVisitor<'doc, ValidationErrorContext> for NoUndefinedVariables<'doc> {
79 fn enter_operation_definition(
80 &mut self,
81 _: &mut OperationVisitorContext,
82 _: &mut ValidationErrorContext,
83 operation_definition: &'doc 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: &'doc 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: &'doc 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: &'doc 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): &'doc (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 ValidationRule for NoUndefinedVariables<'_> {
182 fn error_code(&self) -> &'static str {
183 "NoUndefinedVariables"
184 }
185
186 fn visitor<'doc>(&self) -> super::ValidationVisitor<'doc> {
187 Box::new(NoUndefinedVariables::new())
188 }
189}
190
191#[test]
192fn all_variables_defined() {
193 use crate::validation::test_utils::*;
194
195 let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
196 let errors = test_operation_with_schema(
197 "query Foo($a: String, $b: String, $c: String) {
198 field(a: $a, b: $b, c: $c)
199 }",
200 TEST_SCHEMA,
201 &mut plan,
202 );
203
204 let messages = get_messages(&errors);
205 assert_eq!(messages.len(), 0);
206}
207
208#[test]
209fn all_variables_deeply_defined() {
210 use crate::validation::test_utils::*;
211
212 let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
213 let errors = test_operation_with_schema(
214 "query Foo($a: String, $b: String, $c: String) {
215 field(a: $a) {
216 field(b: $b) {
217 field(c: $c)
218 }
219 }
220 }",
221 TEST_SCHEMA,
222 &mut plan,
223 );
224
225 let messages = get_messages(&errors);
226 assert_eq!(messages.len(), 0);
227}
228
229#[test]
230fn all_variables_deeply_in_inline_fragments_defined() {
231 use crate::validation::test_utils::*;
232
233 let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
234 let errors = test_operation_with_schema(
235 "query Foo($a: String, $b: String, $c: String) {
236 ... on Type {
237 field(a: $a) {
238 field(b: $b) {
239 ... on Type {
240 field(c: $c)
241 }
242 }
243 }
244 }
245 }",
246 TEST_SCHEMA,
247 &mut plan,
248 );
249
250 let messages = get_messages(&errors);
251 assert_eq!(messages.len(), 0);
252}
253
254#[test]
255fn all_variables_in_fragments_deeply_defined() {
256 use crate::validation::test_utils::*;
257
258 let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
259 let errors = test_operation_with_schema(
260 "query Foo($a: String, $b: String, $c: String) {
261 ...FragA
262 }
263 fragment FragA on Type {
264 field(a: $a) {
265 ...FragB
266 }
267 }
268 fragment FragB on Type {
269 field(b: $b) {
270 ...FragC
271 }
272 }
273 fragment FragC on Type {
274 field(c: $c)
275 }",
276 TEST_SCHEMA,
277 &mut plan,
278 );
279
280 let messages = get_messages(&errors);
281 assert_eq!(messages.len(), 0);
282}
283
284#[test]
285fn variable_within_single_fragment_defined_in_multiple_operations() {
286 use crate::validation::test_utils::*;
287
288 let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
289 let errors = test_operation_with_schema(
290 "query Foo($a: String) {
291 ...FragA
292 }
293 query Bar($a: String) {
294 ...FragA
295 }
296 fragment FragA on Type {
297 field(a: $a)
298 }",
299 TEST_SCHEMA,
300 &mut plan,
301 );
302
303 let messages = get_messages(&errors);
304 assert_eq!(messages.len(), 0);
305}
306
307#[test]
308fn variable_within_fragments_defined_in_operations() {
309 use crate::validation::test_utils::*;
310
311 let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
312 let errors = test_operation_with_schema(
313 "query Foo($a: String) {
314 ...FragA
315 }
316 query Bar($b: String) {
317 ...FragB
318 }
319 fragment FragA on Type {
320 field(a: $a)
321 }
322 fragment FragB on Type {
323 field(b: $b)
324 }",
325 TEST_SCHEMA,
326 &mut plan,
327 );
328
329 let messages = get_messages(&errors);
330 assert_eq!(messages.len(), 0);
331}
332
333#[test]
334fn variable_within_recursive_fragment_defined() {
335 use crate::validation::test_utils::*;
336
337 let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
338 let errors = test_operation_with_schema(
339 "query Foo($a: String) {
340 ...FragA
341 }
342 fragment FragA on Type {
343 field(a: $a) {
344 ...FragA
345 }
346 }",
347 TEST_SCHEMA,
348 &mut plan,
349 );
350
351 let messages = get_messages(&errors);
352 assert_eq!(messages.len(), 0);
353}
354
355#[test]
356fn variable_not_defined() {
357 use crate::validation::test_utils::*;
358
359 let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
360 let errors = test_operation_with_schema(
361 "query Foo($a: String, $b: String, $c: String) {
362 field(a: $a, b: $b, c: $c, d: $d)
363 }",
364 TEST_SCHEMA,
365 &mut plan,
366 );
367
368 let messages = get_messages(&errors);
369 assert_eq!(messages.len(), 1);
370 assert_eq!(
371 messages,
372 vec!["Variable \"$d\" is not defined by operation \"Foo\"."]
373 );
374}
375
376#[test]
377fn variable_not_defined_by_un_named_query() {
378 use crate::validation::test_utils::*;
379
380 let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
381 let errors = test_operation_with_schema(
382 "{
383 field(a: $a)
384 }",
385 TEST_SCHEMA,
386 &mut plan,
387 );
388
389 let messages = get_messages(&errors);
390 assert_eq!(messages.len(), 1);
391 assert_eq!(messages, vec!["Variable \"$a\" is not defined."]);
392}
393
394#[test]
395fn multiple_variables_not_defined() {
396 use crate::validation::test_utils::*;
397
398 let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
399 let errors = test_operation_with_schema(
400 "query Foo($b: String) {
401 field(a: $a, b: $b, c: $c)
402 }",
403 TEST_SCHEMA,
404 &mut plan,
405 );
406
407 let messages = get_messages(&errors);
408 assert_eq!(messages.len(), 2);
409 assert!(messages.contains(&&"Variable \"$a\" is not defined by operation \"Foo\".".to_owned()));
410 assert!(messages.contains(&&"Variable \"$c\" is not defined by operation \"Foo\".".to_owned()));
411}
412
413#[test]
414fn variable_in_fragment_not_defined_by_un_named_query() {
415 use crate::validation::test_utils::*;
416
417 let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
418 let errors = test_operation_with_schema(
419 "{
420 ...FragA
421 }
422 fragment FragA on Type {
423 field(a: $a)
424 }",
425 TEST_SCHEMA,
426 &mut plan,
427 );
428
429 let messages = get_messages(&errors);
430 assert_eq!(messages.len(), 1);
431 assert_eq!(messages, vec!["Variable \"$a\" is not defined.",]);
432}
433
434#[test]
435fn variable_in_fragment_not_defined_by_operation() {
436 use crate::validation::test_utils::*;
437
438 let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
439 let errors = test_operation_with_schema(
440 "query Foo($a: String, $b: String) {
441 ...FragA
442 }
443 fragment FragA on Type {
444 field(a: $a) {
445 ...FragB
446 }
447 }
448 fragment FragB on Type {
449 field(b: $b) {
450 ...FragC
451 }
452 }
453 fragment FragC on Type {
454 field(c: $c)
455 }",
456 TEST_SCHEMA,
457 &mut plan,
458 );
459
460 let messages = get_messages(&errors);
461 assert_eq!(messages.len(), 1);
462 assert_eq!(
463 messages,
464 vec!["Variable \"$c\" is not defined by operation \"Foo\"."]
465 );
466}
467
468#[test]
469fn multiple_variables_in_fragments_not_defined() {
470 use crate::validation::test_utils::*;
471
472 let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
473 let errors = test_operation_with_schema(
474 "query Foo($b: String) {
475 ...FragA
476 }
477 fragment FragA on Type {
478 field(a: $a) {
479 ...FragB
480 }
481 }
482 fragment FragB on Type {
483 field(b: $b) {
484 ...FragC
485 }
486 }
487 fragment FragC on Type {
488 field(c: $c)
489 }",
490 TEST_SCHEMA,
491 &mut plan,
492 );
493
494 let messages = get_messages(&errors);
495 assert_eq!(messages.len(), 2);
496 assert!(messages.contains(&&"Variable \"$c\" is not defined by operation \"Foo\".".to_owned()));
497 assert!(messages.contains(&&"Variable \"$a\" is not defined by operation \"Foo\".".to_owned()));
498}
499
500#[test]
501fn single_variable_in_fragment_not_defined_by_multiple_operations() {
502 use crate::validation::test_utils::*;
503
504 let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
505 let errors = test_operation_with_schema(
506 "query Foo($a: String) {
507 ...FragAB
508 }
509 query Bar($a: String) {
510 ...FragAB
511 }
512 fragment FragAB on Type {
513 field(a: $a, b: $b)
514 }",
515 TEST_SCHEMA,
516 &mut plan,
517 );
518
519 let messages = get_messages(&errors);
520 assert_eq!(messages.len(), 2);
521 assert!(messages.contains(&&"Variable \"$b\" is not defined by operation \"Bar\".".to_owned()));
522 assert!(messages.contains(&&"Variable \"$b\" is not defined by operation \"Foo\".".to_owned()));
523}
524
525#[test]
526fn variables_in_fragment_not_defined_by_multiple_operations() {
527 use crate::validation::test_utils::*;
528
529 let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
530 let errors = test_operation_with_schema(
531 "query Foo($b: String) {
532 ...FragAB
533 }
534 query Bar($a: String) {
535 ...FragAB
536 }
537 fragment FragAB on Type {
538 field(a: $a, b: $b)
539 }",
540 TEST_SCHEMA,
541 &mut plan,
542 );
543
544 let messages = get_messages(&errors);
545 assert_eq!(messages.len(), 2);
546 assert!(messages.contains(&&"Variable \"$a\" is not defined by operation \"Foo\".".to_owned()));
547 assert!(messages.contains(&&"Variable \"$b\" is not defined by operation \"Bar\".".to_owned()));
548}
549
550#[test]
551fn variable_in_fragment_used_by_other_operation() {
552 use crate::validation::test_utils::*;
553
554 let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
555 let errors = test_operation_with_schema(
556 "query Foo($b: String) {
557 ...FragA
558 }
559 query Bar($a: String) {
560 ...FragB
561 }
562 fragment FragA on Type {
563 field(a: $a)
564 }
565 fragment FragB on Type {
566 field(b: $b)
567 }",
568 TEST_SCHEMA,
569 &mut plan,
570 );
571
572 let messages = get_messages(&errors);
573 assert_eq!(messages.len(), 2);
574 assert!(messages.contains(&&"Variable \"$a\" is not defined by operation \"Foo\".".to_owned()));
575 assert!(messages.contains(&&"Variable \"$b\" is not defined by operation \"Bar\".".to_owned()));
576}
577#[test]
578fn multiple_undefined_variables_produce_multiple_errors() {
579 use crate::validation::test_utils::*;
580
581 let mut plan = create_plan_from_rule(Box::new(NoUndefinedVariables::new()));
582 let errors = test_operation_with_schema(
583 "query Foo($b: String) {
584 ...FragAB
585 }
586 query Bar($a: String) {
587 ...FragAB
588 }
589 fragment FragAB on Type {
590 field1(a: $a, b: $b, d: $d)
591 ...FragC
592 field3(a: $a, b: $b)
593 }
594 fragment FragC on Type {
595 field2(c: $c)
596 }",
597 TEST_SCHEMA,
598 &mut plan,
599 );
600
601 let messages = get_messages(&errors);
602 assert_eq!(messages.len(), 6);
603 assert!(messages.contains(&&"Variable \"$a\" is not defined by operation \"Foo\".".to_owned()));
604 assert!(messages.contains(&&"Variable \"$c\" is not defined by operation \"Foo\".".to_owned()));
605 assert!(messages.contains(&&"Variable \"$d\" is not defined by operation \"Foo\".".to_owned()));
606 assert!(messages.contains(&&"Variable \"$b\" is not defined by operation \"Bar\".".to_owned()));
607 assert!(messages.contains(&&"Variable \"$c\" is not defined by operation \"Bar\".".to_owned()));
608 assert!(messages.contains(&&"Variable \"$d\" is not defined by operation \"Bar\".".to_owned()));
609}