Skip to main content

graphql_query/validate/rules/
no_unused_variables.rs

1use bumpalo::collections::Vec;
2
3use super::super::{ValidationContext, ValidationRule};
4use crate::{ast::*, visit::*};
5
6/// Validate that a document uses all the variables it defines at least once.
7///
8/// See [`ValidationRule`]
9/// [Reference](https://spec.graphql.org/draft/#sec-All-Variables-Used)
10pub struct NoUnusedVariables<'a> {
11    variables: Vec<'a, &'a str>,
12    used_variables: Vec<'a, &'a str>,
13}
14
15impl<'a> DefaultIn<'a> for NoUnusedVariables<'a> {
16    fn default_in(arena: &'a bumpalo::Bump) -> Self {
17        Self {
18            variables: Vec::new_in(arena),
19            used_variables: Vec::new_in(arena),
20        }
21    }
22}
23
24impl<'a> ValidationRule<'a> for NoUnusedVariables<'a> {}
25
26impl<'a> Visitor<'a, ValidationContext<'a>> for NoUnusedVariables<'a> {
27    fn enter_operation(
28        &mut self,
29        _ctx: &mut ValidationContext<'a>,
30        operation: &'a OperationDefinition<'a>,
31        _info: &VisitInfo,
32    ) -> VisitFlow {
33        operation
34            .variable_definitions
35            .children
36            .iter()
37            .for_each(|def| {
38                self.variables.push(def.variable.name);
39            });
40        VisitFlow::Next
41    }
42
43    fn enter_field(
44        &mut self,
45        _ctx: &mut ValidationContext<'a>,
46        field: &'a Field<'a>,
47        _info: &VisitInfo,
48    ) -> VisitFlow {
49        field.arguments.children.iter().for_each(|arg| {
50            if let Value::Variable(var) = arg.value {
51                self.used_variables.push(var.name);
52            }
53        });
54        VisitFlow::Next
55    }
56
57    fn leave_document(
58        &mut self,
59        ctx: &mut ValidationContext<'a>,
60        _document: &'a Document<'a>,
61        _info: &VisitInfo,
62    ) -> VisitFlow {
63        self.variables.iter().for_each(|defined_variable| {
64            if !self.used_variables.contains(defined_variable) {
65                ctx.add_error("All defined variables must be at least used once");
66            }
67        });
68        VisitFlow::Next
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn used_all_variables() {
78        let ctx = ASTContext::new();
79        let document =
80            Document::parse(&ctx, "query ($x: Int!) { todos(from: $x) { id } }").unwrap();
81        NoUnusedVariables::validate(&ctx, document).unwrap();
82    }
83
84    #[test]
85    fn has_unused_variable() {
86        let ctx = ASTContext::new();
87        let document = Document::parse(
88            &ctx,
89            "query ($x: Int!, $unused: String!) { todos(from: $x) { id } }",
90        )
91        .unwrap();
92        NoUnusedVariables::validate(&ctx, document).unwrap_err();
93    }
94}