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