graphql_tools/validation/rules/
known_directives.rs1use super::ValidationRule;
2use crate::ast::{OperationVisitor, OperationVisitorContext};
3use crate::static_graphql::query::{
4 Directive, Field, FragmentDefinition, InlineFragment, OperationDefinition,
5};
6use crate::static_graphql::schema::DirectiveLocation;
7use crate::validation::utils::{ValidationError, ValidationErrorContext};
8
9pub struct KnownDirectives {
16 recent_location: Option<DirectiveLocation>,
17}
18
19impl Default for KnownDirectives {
20 fn default() -> Self {
21 Self::new()
22 }
23}
24
25impl KnownDirectives {
26 pub fn new() -> Self {
27 KnownDirectives {
28 recent_location: None,
29 }
30 }
31}
32
33impl<'doc> OperationVisitor<'doc, ValidationErrorContext> for KnownDirectives {
34 fn enter_operation_definition(
35 &mut self,
36 _: &mut OperationVisitorContext<'doc>,
37 _: &mut ValidationErrorContext,
38 operation_definition: &crate::static_graphql::query::OperationDefinition,
39 ) {
40 self.recent_location = Some(match operation_definition {
41 OperationDefinition::Mutation(_) => DirectiveLocation::Mutation,
42 OperationDefinition::Query(_) => DirectiveLocation::Query,
43 OperationDefinition::SelectionSet(_) => DirectiveLocation::Query,
44 OperationDefinition::Subscription(_) => DirectiveLocation::Subscription,
45 })
46 }
47
48 fn leave_operation_definition(
49 &mut self,
50 _: &mut OperationVisitorContext<'doc>,
51 _: &mut ValidationErrorContext,
52 _: &OperationDefinition,
53 ) {
54 self.recent_location = None;
55 }
56
57 fn enter_field(
58 &mut self,
59 _: &mut OperationVisitorContext<'doc>,
60 _: &mut ValidationErrorContext,
61 _: &Field,
62 ) {
63 self.recent_location = Some(DirectiveLocation::Field);
64 }
65
66 fn leave_field(
67 &mut self,
68 _: &mut OperationVisitorContext<'doc>,
69 _: &mut ValidationErrorContext,
70 _: &Field,
71 ) {
72 self.recent_location = None;
73 }
74
75 fn enter_fragment_definition(
76 &mut self,
77 _: &mut OperationVisitorContext<'doc>,
78 _: &mut ValidationErrorContext,
79 _: &FragmentDefinition,
80 ) {
81 self.recent_location = Some(DirectiveLocation::FragmentDefinition);
82 }
83
84 fn leave_fragment_definition(
85 &mut self,
86 _: &mut OperationVisitorContext<'doc>,
87 _: &mut ValidationErrorContext,
88 _: &FragmentDefinition,
89 ) {
90 self.recent_location = None;
91 }
92
93 fn enter_fragment_spread(
94 &mut self,
95 _: &mut OperationVisitorContext<'doc>,
96 _: &mut ValidationErrorContext,
97 _: &crate::static_graphql::query::FragmentSpread,
98 ) {
99 self.recent_location = Some(DirectiveLocation::FragmentSpread);
100 }
101
102 fn leave_fragment_spread(
103 &mut self,
104 _: &mut OperationVisitorContext<'doc>,
105 _: &mut ValidationErrorContext,
106 _: &crate::static_graphql::query::FragmentSpread,
107 ) {
108 self.recent_location = None;
109 }
110
111 fn enter_inline_fragment(
112 &mut self,
113 _: &mut OperationVisitorContext<'doc>,
114 _: &mut ValidationErrorContext,
115 _: &InlineFragment,
116 ) {
117 self.recent_location = Some(DirectiveLocation::InlineFragment);
118 }
119
120 fn leave_inline_fragment(
121 &mut self,
122 _: &mut OperationVisitorContext<'doc>,
123 _: &mut ValidationErrorContext,
124 _: &InlineFragment,
125 ) {
126 self.recent_location = None;
127 }
128
129 fn enter_directive(
130 &mut self,
131 visitor_context: &mut OperationVisitorContext<'doc>,
132 user_context: &mut ValidationErrorContext,
133 directive: &Directive,
134 ) {
135 if let Some(directive_type) = visitor_context.directives.get(&directive.name) {
136 if let Some(current_location) = &self.recent_location {
137 if !directive_type
138 .locations
139 .iter()
140 .any(|l| l == current_location)
141 {
142 user_context.report_error(ValidationError {
143 error_code: self.error_code(),
144 locations: vec![directive.position],
145 message: format!(
146 "Directive \"@{}\" may not be used on {}",
147 directive.name,
148 current_location.as_str()
149 ),
150 });
151 }
152 }
153 } else {
154 user_context.report_error(ValidationError {
155 error_code: self.error_code(),
156 locations: vec![directive.position],
157 message: format!("Unknown directive \"@{}\".", directive.name),
158 });
159 }
160 }
161}
162
163impl ValidationRule for KnownDirectives {
164 fn error_code(&self) -> &'static str {
165 "KnownDirectives"
166 }
167
168 fn visitor<'doc>(&self) -> super::ValidationVisitor<'doc> {
169 Box::new(KnownDirectives::new())
170 }
171}
172
173#[test]
174fn no_directives() {
175 use crate::validation::test_utils::*;
176
177 let mut plan = create_plan_from_rule(Box::new(KnownDirectives::new()));
178 let errors = test_operation_with_schema(
179 "query Foo {
180 name
181 ...Frag
182 }
183
184 fragment Frag on Dog {
185 name
186 }",
187 TEST_SCHEMA,
188 &mut plan,
189 );
190
191 assert_eq!(get_messages(&errors).len(), 0);
192}
193
194#[test]
195fn standard_directives() {
196 use crate::validation::test_utils::*;
197
198 let mut plan = create_plan_from_rule(Box::new(KnownDirectives::new()));
199 let errors = test_operation_with_schema(
200 "{
201 human @skip(if: false) {
202 name
203 pets {
204 ... on Dog @include(if: true) {
205 name
206 }
207 }
208 }
209 }",
210 TEST_SCHEMA,
211 &mut plan,
212 );
213
214 assert_eq!(get_messages(&errors).len(), 0);
215}
216
217#[test]
218fn unknown_directive() {
219 use crate::validation::test_utils::*;
220
221 let mut plan = create_plan_from_rule(Box::new(KnownDirectives::new()));
222 let errors = test_operation_with_schema(
223 "{
224 human @unknown(directive: \"value\") {
225 name
226 }
227 }",
228 TEST_SCHEMA,
229 &mut plan,
230 );
231
232 assert_eq!(get_messages(&errors).len(), 1);
233}
234
235#[test]
236fn many_unknown_directives() {
237 use crate::validation::test_utils::*;
238
239 let mut plan = create_plan_from_rule(Box::new(KnownDirectives::new()));
240 let errors = test_operation_with_schema(
241 "{
242 __typename @unknown
243 human @unknown {
244 name
245 pets @unknown {
246 name
247 }
248 }
249 }",
250 TEST_SCHEMA,
251 &mut plan,
252 );
253
254 assert_eq!(get_messages(&errors).len(), 3);
255}
256
257#[test]
258fn well_placed_directives() {
259 use crate::validation::test_utils::*;
260
261 let mut plan = create_plan_from_rule(Box::new(KnownDirectives::new()));
262 let errors = test_operation_with_schema(
263 "
264 # TODO: update once this is released https://github.com/graphql-rust/graphql-parser/issues/60 query ($var: Boolean @onVariableDefinition)
265 query ($var: Boolean) @onQuery {
266 human @onField {
267 ...Frag @onFragmentSpread
268 ... @onInlineFragment {
269 name @onField
270 }
271 }
272 }
273
274 mutation @onMutation {
275 someField @onField
276 }
277
278 subscription @onSubscription {
279 someField @onField
280 }
281
282 fragment Frag on Human @onFragmentDefinition {
283 name @onField
284 }",
285 TEST_SCHEMA,
286 &mut plan,
287 );
288
289 assert_eq!(get_messages(&errors).len(), 0);
290}
291
292#[test]
293fn misplaced_directives() {
294 use crate::validation::test_utils::*;
295
296 let mut plan = create_plan_from_rule(Box::new(KnownDirectives::new()));
297 let errors = test_operation_with_schema(
298 " query ($var: Boolean) @onMutation {
299 human @onQuery {
300 ...Frag @onQuery
301 ... @onQuery {
302 name @onQuery
303 }
304 }
305 }
306
307 mutation @onQuery {
308 someField @onQuery
309 }
310
311 subscription @onQuery {
312 someField @onQuery
313 }
314
315 fragment Frag on Human @onQuery {
316 name @onQuery
317 }",
318 TEST_SCHEMA,
319 &mut plan,
320 );
321
322 assert_eq!(get_messages(&errors).len(), 11);
323}