Skip to main content

graphql_query/validate/rules/
no_unused_fragments.rs

1use bumpalo::collections::Vec;
2
3use super::super::{ValidationContext, ValidationRule};
4use crate::{ast::*, visit::*};
5
6/// Validate that a document uses all the fragments it defines at least once.
7///
8/// See [`ValidationRule`]
9/// [Reference](https://spec.graphql.org/October2021/#sec-Fragments-Must-Be-Used)
10pub struct NoUnusedFragments<'a> {
11    fragment_names: Vec<'a, &'a str>,
12    fragment_spreads: Vec<'a, &'a str>,
13}
14
15impl<'a> DefaultIn<'a> for NoUnusedFragments<'a> {
16    fn default_in(arena: &'a bumpalo::Bump) -> Self {
17        Self {
18            fragment_names: Vec::new_in(arena),
19            fragment_spreads: Vec::new_in(arena),
20        }
21    }
22}
23
24impl<'a> ValidationRule<'a> for NoUnusedFragments<'a> {}
25
26impl<'a> Visitor<'a, ValidationContext<'a>> for NoUnusedFragments<'a> {
27    fn enter_fragment(
28        &mut self,
29        _ctx: &mut ValidationContext<'a>,
30        fragment: &'a FragmentDefinition<'a>,
31        _info: &VisitInfo,
32    ) -> VisitFlow {
33        self.fragment_names.push(fragment.name.name);
34        VisitFlow::Next
35    }
36
37    fn enter_fragment_spread(
38        &mut self,
39        _ctx: &mut ValidationContext<'a>,
40        spread: &'a FragmentSpread<'a>,
41        _info: &VisitInfo,
42    ) -> VisitFlow {
43        self.fragment_spreads.push(spread.name.name);
44        VisitFlow::Skip
45    }
46
47    fn leave_document(
48        &mut self,
49        ctx: &mut ValidationContext<'a>,
50        _document: &'a Document<'a>,
51        _info: &VisitInfo,
52    ) -> VisitFlow {
53        for name in self.fragment_names.iter() {
54            if !self.fragment_spreads.contains(name) {
55                ctx.add_error("All defined fragments must be at least spread once");
56            }
57        }
58        VisitFlow::Next
59    }
60
61    fn enter_variable_definition(
62        &mut self,
63        _ctx: &mut ValidationContext<'a>,
64        _var_def: &'a VariableDefinition,
65        _info: &VisitInfo,
66    ) -> VisitFlow {
67        VisitFlow::Skip
68    }
69
70    fn enter_argument(
71        &mut self,
72        _ctx: &mut ValidationContext<'a>,
73        _argument: &'a Argument,
74        _info: &VisitInfo,
75    ) -> VisitFlow {
76        VisitFlow::Skip
77    }
78
79    fn enter_directive(
80        &mut self,
81        _ctx: &mut ValidationContext<'a>,
82        _directive: &'a Directive,
83        _info: &VisitInfo,
84    ) -> VisitFlow {
85        VisitFlow::Skip
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn valid_spread() {
95        let ctx = ASTContext::new();
96        let document = Document::parse(
97            &ctx,
98            "query { ...Root } fragment Root on Query { __typename }",
99        )
100        .unwrap();
101        NoUnusedFragments::validate(&ctx, document).unwrap();
102    }
103
104    #[test]
105    fn missing_spread() {
106        let ctx = ASTContext::new();
107        let document = Document::parse(
108            &ctx,
109            "query { __typename } fragment Root on Query { __typename }",
110        )
111        .unwrap();
112        NoUnusedFragments::validate(&ctx, document).unwrap_err();
113    }
114}