Skip to main content

graphql_query/validate/rules/
known_fragment_names.rs

1use bumpalo::collections::Vec;
2
3use super::super::{ValidationContext, ValidationRule};
4use crate::{ast::*, visit::*};
5
6/// Validate a document for all fragment names in spreads to be defined in the same document.
7///
8/// See [`ValidationRule`]
9/// [Reference](https://spec.graphql.org/October2021/#sec-Fragment-spread-target-defined)
10pub struct KnownFragmentNames<'a> {
11    fragment_names: Vec<'a, &'a str>,
12    fragment_spreads: Vec<'a, &'a str>,
13}
14
15impl<'a> DefaultIn<'a> for KnownFragmentNames<'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 KnownFragmentNames<'a> {}
25
26impl<'a> Visitor<'a, ValidationContext<'a>> for KnownFragmentNames<'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_spreads.iter() {
54            if !self.fragment_names.contains(name) {
55                ctx.add_error("Only known fragments may occur in fragment spreads.");
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        KnownFragmentNames::validate(&ctx, document).unwrap();
102    }
103
104    #[test]
105    fn invalid_spread() {
106        let ctx = ASTContext::new();
107        let document = Document::parse(&ctx, "query { ...Unknown }").unwrap();
108        KnownFragmentNames::validate(&ctx, document).unwrap_err();
109    }
110}