async_graphql/validation/rules/
known_directives.rs1use 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}