async_graphql/validation/rules/
known_directives.rs

1use crate::{
2    Name, Positioned,
3    model::__DirectiveLocation,
4    parser::types::{
5        Directive, Field, FragmentDefinition, FragmentSpread, InlineFragment, OperationDefinition,
6        OperationType,
7    },
8    validation::visitor::{Visitor, VisitorContext},
9};
10
11#[derive(Default)]
12pub struct KnownDirectives {
13    location_stack: Vec<__DirectiveLocation>,
14}
15
16impl<'a> Visitor<'a> for KnownDirectives {
17    fn enter_operation_definition(
18        &mut self,
19        _ctx: &mut VisitorContext<'a>,
20        _name: Option<&'a Name>,
21        operation_definition: &'a Positioned<OperationDefinition>,
22    ) {
23        self.location_stack
24            .push(match &operation_definition.node.ty {
25                OperationType::Query => __DirectiveLocation::QUERY,
26                OperationType::Mutation => __DirectiveLocation::MUTATION,
27                OperationType::Subscription => __DirectiveLocation::SUBSCRIPTION,
28            });
29    }
30
31    fn exit_operation_definition(
32        &mut self,
33        _ctx: &mut VisitorContext<'a>,
34        _name: Option<&'a Name>,
35        _operation_definition: &'a Positioned<OperationDefinition>,
36    ) {
37        self.location_stack.pop();
38    }
39
40    fn enter_fragment_definition(
41        &mut self,
42        _ctx: &mut VisitorContext<'a>,
43        _name: &'a Name,
44        _fragment_definition: &'a Positioned<FragmentDefinition>,
45    ) {
46        self.location_stack
47            .push(__DirectiveLocation::FRAGMENT_DEFINITION);
48    }
49
50    fn exit_fragment_definition(
51        &mut self,
52        _ctx: &mut VisitorContext<'a>,
53        _name: &'a Name,
54        _fragment_definition: &'a Positioned<FragmentDefinition>,
55    ) {
56        self.location_stack.pop();
57    }
58
59    fn enter_directive(
60        &mut self,
61        ctx: &mut VisitorContext<'a>,
62        directive: &'a Positioned<Directive>,
63    ) {
64        if let Some(schema_directive) = ctx
65            .registry
66            .directives
67            .get(directive.node.name.node.as_str())
68        {
69            if let Some(current_location) = self.location_stack.last() {
70                if !schema_directive.locations.contains(current_location) {
71                    ctx.report_error(
72                        vec![directive.pos],
73                        format!(
74                            "Directive \"{}\" may not be used on \"{:?}\"",
75                            directive.node.name.node, current_location
76                        ),
77                    )
78                }
79            }
80        } else {
81            ctx.report_error(
82                vec![directive.pos],
83                format!("Unknown directive \"{}\"", directive.node.name.node),
84            );
85        }
86    }
87
88    fn enter_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Positioned<Field>) {
89        self.location_stack.push(__DirectiveLocation::FIELD);
90    }
91
92    fn exit_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Positioned<Field>) {
93        self.location_stack.pop();
94    }
95
96    fn enter_fragment_spread(
97        &mut self,
98        _ctx: &mut VisitorContext<'a>,
99        _fragment_spread: &'a Positioned<FragmentSpread>,
100    ) {
101        self.location_stack
102            .push(__DirectiveLocation::FRAGMENT_SPREAD);
103    }
104
105    fn exit_fragment_spread(
106        &mut self,
107        _ctx: &mut VisitorContext<'a>,
108        _fragment_spread: &'a Positioned<FragmentSpread>,
109    ) {
110        self.location_stack.pop();
111    }
112
113    fn enter_inline_fragment(
114        &mut self,
115        _ctx: &mut VisitorContext<'a>,
116        _inline_fragment: &'a Positioned<InlineFragment>,
117    ) {
118        self.location_stack
119            .push(__DirectiveLocation::INLINE_FRAGMENT);
120    }
121
122    fn exit_inline_fragment(
123        &mut self,
124        _ctx: &mut VisitorContext<'a>,
125        _inline_fragment: &'a Positioned<InlineFragment>,
126    ) {
127        self.location_stack.pop();
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    pub fn factory() -> KnownDirectives {
136        KnownDirectives::default()
137    }
138
139    #[test]
140    fn with_no_directives() {
141        expect_passes_rule!(
142            factory,
143            r#"
144          query Foo {
145            name
146            ...Frag
147          }
148          fragment Frag on Dog {
149            name
150          }
151        "#,
152        );
153    }
154
155    #[test]
156    fn with_known_directives() {
157        expect_passes_rule!(
158            factory,
159            r#"
160          {
161            dog @include(if: true) {
162              name
163            }
164            human @skip(if: false) {
165              name
166            }
167          }
168        "#,
169        );
170    }
171
172    #[test]
173    fn with_unknown_directive() {
174        expect_fails_rule!(
175            factory,
176            r#"
177          {
178            dog @unknown(directive: "value") {
179              name
180            }
181          }
182        "#,
183        );
184    }
185
186    #[test]
187    fn with_many_unknown_directives() {
188        expect_fails_rule!(
189            factory,
190            r#"
191          {
192            dog @unknown(directive: "value") {
193              name
194            }
195            human @unknown(directive: "value") {
196              name
197              pets @unknown(directive: "value") {
198                name
199              }
200            }
201          }
202        "#,
203        );
204    }
205
206    #[test]
207    fn with_well_placed_directives() {
208        expect_passes_rule!(
209            factory,
210            r#"
211          query Foo {
212            name @include(if: true)
213            ...Frag @include(if: true)
214            skippedField @skip(if: true)
215            ...SkippedFrag @skip(if: true)
216          }
217          mutation Bar {
218            someField
219          }
220        "#,
221        );
222    }
223
224    #[test]
225    fn with_misplaced_directives() {
226        expect_fails_rule!(
227            factory,
228            r#"
229          query Foo @include(if: true) {
230            name
231            ...Frag
232          }
233          mutation Bar {
234            someField
235          }
236        "#,
237        );
238    }
239}