graphql_query/validate/rules/
known_fragment_names.rs

1use super::super::{ValidationContext, ValidationRule};
2use crate::{ast::*, visit::*};
3
4/// Validate a document for all fragment names in spreads to be defined in the same document.
5///
6/// See [`ValidationRule`]
7/// [Reference](https://spec.graphql.org/October2021/#sec-Fragment-spread-target-defined)
8#[derive(Default)]
9pub struct KnownFragmentNames<'a> {
10    fragment_names: Vec<&'a str>,
11    fragment_spreads: Vec<&'a str>,
12}
13
14impl<'a> ValidationRule<'a> for KnownFragmentNames<'a> {}
15
16impl<'a> Visitor<'a, ValidationContext<'a>> for KnownFragmentNames<'a> {
17    fn enter_fragment(
18        &mut self,
19        _ctx: &mut ValidationContext<'a>,
20        fragment: &'a FragmentDefinition<'a>,
21        _info: &VisitInfo,
22    ) -> VisitFlow {
23        self.fragment_names.push(fragment.name.name);
24        VisitFlow::Next
25    }
26
27    fn enter_fragment_spread(
28        &mut self,
29        _ctx: &mut ValidationContext<'a>,
30        spread: &'a FragmentSpread<'a>,
31        _info: &VisitInfo,
32    ) -> VisitFlow {
33        self.fragment_spreads.push(spread.name.name);
34        VisitFlow::Skip
35    }
36
37    fn leave_document(
38        &mut self,
39        ctx: &mut ValidationContext<'a>,
40        _document: &'a Document<'a>,
41        _info: &VisitInfo,
42    ) -> VisitFlow {
43        for name in self.fragment_spreads.iter() {
44            if !self.fragment_names.contains(name) {
45                ctx.add_error("Only known fragments may occur in fragment spreads.");
46            }
47        }
48        VisitFlow::Next
49    }
50
51    fn enter_variable_definition(
52        &mut self,
53        _ctx: &mut ValidationContext<'a>,
54        _var_def: &'a VariableDefinition,
55        _info: &VisitInfo,
56    ) -> VisitFlow {
57        VisitFlow::Skip
58    }
59
60    fn enter_argument(
61        &mut self,
62        _ctx: &mut ValidationContext<'a>,
63        _argument: &'a Argument,
64        _info: &VisitInfo,
65    ) -> VisitFlow {
66        VisitFlow::Skip
67    }
68
69    fn enter_directive(
70        &mut self,
71        _ctx: &mut ValidationContext<'a>,
72        _directive: &'a Directive,
73        _info: &VisitInfo,
74    ) -> VisitFlow {
75        VisitFlow::Skip
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn valid_spread() {
85        let ctx = ASTContext::new();
86        let document = Document::parse(
87            &ctx,
88            "query { ...Root } fragment Root on Query { __typename }",
89        )
90        .unwrap();
91        KnownFragmentNames::validate(&ctx, document).unwrap();
92    }
93
94    #[test]
95    fn invalid_spread() {
96        let ctx = ASTContext::new();
97        let document = Document::parse(&ctx, "query { ...Unknown }").unwrap();
98        KnownFragmentNames::validate(&ctx, document).unwrap_err();
99    }
100}