graphql_tools/validation/rules/
known_directives.rs1use super::ValidationRule;
2use crate::ast::{visit_document, 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<'a> OperationVisitor<'a, ValidationErrorContext> for KnownDirectives {
34 fn enter_operation_definition(
35 &mut self,
36 _: &mut OperationVisitorContext<'a>,
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<'a>,
51 _: &mut ValidationErrorContext,
52 _: &OperationDefinition,
53 ) {
54 self.recent_location = None;
55 }
56
57 fn enter_field(
58 &mut self,
59 _: &mut OperationVisitorContext<'a>,
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<'a>,
69 _: &mut ValidationErrorContext,
70 _: &Field,
71 ) {
72 self.recent_location = None;
73 }
74
75 fn enter_fragment_definition(
76 &mut self,
77 _: &mut OperationVisitorContext<'a>,
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<'a>,
87 _: &mut ValidationErrorContext,
88 _: &FragmentDefinition,
89 ) {
90 self.recent_location = None;
91 }
92
93 fn enter_fragment_spread(
94 &mut self,
95 _: &mut OperationVisitorContext<'a>,
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<'a>,
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<'a>,
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<'a>,
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<'a>,
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<'a>(&self) -> &'a str {
165 "KnownDirectives"
166 }
167
168 fn validate(
169 &self,
170 ctx: &mut OperationVisitorContext,
171 error_collector: &mut ValidationErrorContext,
172 ) {
173 visit_document(
174 &mut KnownDirectives::new(),
175 ctx.operation,
176 ctx,
177 error_collector,
178 );
179 }
180}
181
182#[test]
183fn no_directives() {
184 use crate::validation::test_utils::*;
185
186 let mut plan = create_plan_from_rule(Box::new(KnownDirectives::new()));
187 let errors = test_operation_with_schema(
188 "query Foo {
189 name
190 ...Frag
191 }
192
193 fragment Frag on Dog {
194 name
195 }",
196 TEST_SCHEMA,
197 &mut plan,
198 );
199
200 assert_eq!(get_messages(&errors).len(), 0);
201}
202
203#[test]
204fn standard_directives() {
205 use crate::validation::test_utils::*;
206
207 let mut plan = create_plan_from_rule(Box::new(KnownDirectives::new()));
208 let errors = test_operation_with_schema(
209 "{
210 human @skip(if: false) {
211 name
212 pets {
213 ... on Dog @include(if: true) {
214 name
215 }
216 }
217 }
218 }",
219 TEST_SCHEMA,
220 &mut plan,
221 );
222
223 assert_eq!(get_messages(&errors).len(), 0);
224}
225
226#[test]
227fn unknown_directive() {
228 use crate::validation::test_utils::*;
229
230 let mut plan = create_plan_from_rule(Box::new(KnownDirectives::new()));
231 let errors = test_operation_with_schema(
232 "{
233 human @unknown(directive: \"value\") {
234 name
235 }
236 }",
237 TEST_SCHEMA,
238 &mut plan,
239 );
240
241 assert_eq!(get_messages(&errors).len(), 1);
242}
243
244#[test]
245fn many_unknown_directives() {
246 use crate::validation::test_utils::*;
247
248 let mut plan = create_plan_from_rule(Box::new(KnownDirectives::new()));
249 let errors = test_operation_with_schema(
250 "{
251 __typename @unknown
252 human @unknown {
253 name
254 pets @unknown {
255 name
256 }
257 }
258 }",
259 TEST_SCHEMA,
260 &mut plan,
261 );
262
263 assert_eq!(get_messages(&errors).len(), 3);
264}
265
266#[test]
267fn well_placed_directives() {
268 use crate::validation::test_utils::*;
269
270 let mut plan = create_plan_from_rule(Box::new(KnownDirectives::new()));
271 let errors = test_operation_with_schema(
272 "
273 # TODO: update once this is released https://github.com/graphql-rust/graphql-parser/issues/60 query ($var: Boolean @onVariableDefinition)
274 query ($var: Boolean) @onQuery {
275 human @onField {
276 ...Frag @onFragmentSpread
277 ... @onInlineFragment {
278 name @onField
279 }
280 }
281 }
282
283 mutation @onMutation {
284 someField @onField
285 }
286
287 subscription @onSubscription {
288 someField @onField
289 }
290
291 fragment Frag on Human @onFragmentDefinition {
292 name @onField
293 }",
294 TEST_SCHEMA,
295 &mut plan,
296 );
297
298 assert_eq!(get_messages(&errors).len(), 0);
299}
300
301#[test]
302fn misplaced_directives() {
303 use crate::validation::test_utils::*;
304
305 let mut plan = create_plan_from_rule(Box::new(KnownDirectives::new()));
306 let errors = test_operation_with_schema(
307 " query ($var: Boolean) @onMutation {
308 human @onQuery {
309 ...Frag @onQuery
310 ... @onQuery {
311 name @onQuery
312 }
313 }
314 }
315
316 mutation @onQuery {
317 someField @onQuery
318 }
319
320 subscription @onQuery {
321 someField @onQuery
322 }
323
324 fragment Frag on Human @onQuery {
325 name @onQuery
326 }",
327 TEST_SCHEMA,
328 &mut plan,
329 );
330
331 assert_eq!(get_messages(&errors).len(), 11);
332}