Skip to main content

graphql_tools/validation/rules/
known_directives.rs

1use 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
9/// Known Directives
10///
11/// A GraphQL document is only valid if all `@directives` are known by the
12/// schema and legally positioned.
13///
14/// See https://spec.graphql.org/draft/#sec-Directives-Are-Defined
15pub 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}