Skip to main content

graphql_tools/validation/rules/
unique_directives_per_location.rs

1use std::collections::HashSet;
2
3use super::ValidationRule;
4use crate::static_graphql::query::{
5    Directive, Field, FragmentDefinition, FragmentSpread, InlineFragment, OperationDefinition,
6};
7use crate::{
8    ast::{visit_document, OperationVisitor, OperationVisitorContext},
9    validation::utils::{ValidationError, ValidationErrorContext},
10};
11
12/// Unique directive names per location
13///
14/// A GraphQL document is only valid if all non-repeatable directives at
15/// a given location are uniquely named.
16///
17/// See  https://spec.graphql.org/draft/#sec-Directives-Are-Unique-Per-Location
18pub struct UniqueDirectivesPerLocation {}
19
20impl Default for UniqueDirectivesPerLocation {
21    fn default() -> Self {
22        Self::new()
23    }
24}
25
26impl UniqueDirectivesPerLocation {
27    pub fn new() -> Self {
28        UniqueDirectivesPerLocation {}
29    }
30
31    pub fn check_duplicate_directive(
32        &self,
33        ctx: &mut OperationVisitorContext,
34        err_context: &mut ValidationErrorContext,
35        directives: &[Directive],
36    ) {
37        let mut exists = HashSet::new();
38
39        for directive in directives {
40            if let Some(meta_directive) = ctx.directives.get(&directive.name) {
41                if !meta_directive.repeatable {
42                    if exists.contains(&directive.name) {
43                        err_context.report_error(ValidationError {
44                            error_code: self.error_code(),
45                            locations: vec![directive.position],
46                            message: format!("Duplicate directive \"{}\"", &directive.name),
47                        });
48
49                        continue;
50                    }
51
52                    exists.insert(directive.name.clone());
53                }
54            }
55        }
56    }
57}
58
59impl<'a> OperationVisitor<'a, ValidationErrorContext> for UniqueDirectivesPerLocation {
60    fn enter_operation_definition(
61        &mut self,
62        ctx: &mut OperationVisitorContext<'a>,
63        err_ctx: &mut ValidationErrorContext,
64        operation: &OperationDefinition,
65    ) {
66        self.check_duplicate_directive(ctx, err_ctx, operation.directives());
67    }
68
69    fn enter_field(
70        &mut self,
71        ctx: &mut OperationVisitorContext<'a>,
72        err_ctx: &mut ValidationErrorContext,
73        field: &Field,
74    ) {
75        self.check_duplicate_directive(ctx, err_ctx, &field.directives);
76    }
77
78    fn enter_fragment_definition(
79        &mut self,
80        ctx: &mut OperationVisitorContext<'a>,
81        err_ctx: &mut ValidationErrorContext,
82        fragment: &FragmentDefinition,
83    ) {
84        self.check_duplicate_directive(ctx, err_ctx, &fragment.directives);
85    }
86
87    fn enter_fragment_spread(
88        &mut self,
89        ctx: &mut OperationVisitorContext<'a>,
90        err_ctx: &mut ValidationErrorContext,
91        fragment_spread: &FragmentSpread,
92    ) {
93        self.check_duplicate_directive(ctx, err_ctx, &fragment_spread.directives)
94    }
95
96    fn enter_inline_fragment(
97        &mut self,
98        ctx: &mut OperationVisitorContext<'a>,
99        err_ctx: &mut ValidationErrorContext,
100        inline_fragment: &InlineFragment,
101    ) {
102        self.check_duplicate_directive(ctx, err_ctx, &inline_fragment.directives)
103    }
104}
105
106impl ValidationRule for UniqueDirectivesPerLocation {
107    fn error_code<'a>(&self) -> &'a str {
108        "UniqueDirectivesPerLocation"
109    }
110
111    fn validate(
112        &self,
113        ctx: &mut OperationVisitorContext,
114        error_collector: &mut ValidationErrorContext,
115    ) {
116        visit_document(
117            &mut UniqueDirectivesPerLocation::new(),
118            ctx.operation,
119            ctx,
120            error_collector,
121        );
122    }
123}
124
125#[test]
126fn no_directives() {
127    use crate::validation::test_utils::*;
128
129    let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new()));
130    let errors = test_operation_with_schema(
131        "fragment Test on Type {
132            field
133          }",
134        TEST_SCHEMA,
135        &mut plan,
136    );
137
138    assert_eq!(get_messages(&errors).len(), 0);
139}
140
141#[test]
142fn unique_directives_in_different_locations() {
143    use crate::validation::test_utils::*;
144
145    let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new()));
146    let errors = test_operation_with_schema(
147        "fragment Test on Type @directiveA {
148            field @directiveB
149          }",
150        TEST_SCHEMA,
151        &mut plan,
152    );
153
154    assert_eq!(get_messages(&errors).len(), 0);
155}
156
157#[test]
158fn unique_directives_in_same_location() {
159    use crate::validation::test_utils::*;
160
161    let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new()));
162    let errors = test_operation_with_schema(
163        "fragment Test on Type @directiveA @directiveB {
164            field @directiveA @directiveB
165          }",
166        TEST_SCHEMA,
167        &mut plan,
168    );
169
170    assert_eq!(get_messages(&errors).len(), 0);
171}
172
173#[test]
174fn same_directives_in_different_locations() {
175    use crate::validation::test_utils::*;
176
177    let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new()));
178    let errors = test_operation_with_schema(
179        "fragment Test on Type @directiveA {
180            field @directiveA
181          }",
182        TEST_SCHEMA,
183        &mut plan,
184    );
185
186    assert_eq!(get_messages(&errors).len(), 0);
187}
188
189#[test]
190fn same_directives_in_similar_locations() {
191    use crate::validation::test_utils::*;
192
193    let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new()));
194    let errors = test_operation_with_schema(
195        "fragment Test on Type {
196            field @directive
197            field @directive
198          }",
199        TEST_SCHEMA,
200        &mut plan,
201    );
202
203    assert_eq!(get_messages(&errors).len(), 0);
204}
205
206#[test]
207fn repeatable_directives_in_same_location() {
208    use crate::validation::test_utils::*;
209
210    let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new()));
211    let errors = test_operation_with_schema(
212        "fragment Test on Type @repeatable @repeatable {
213            field @repeatable @repeatable
214          }",
215        TEST_SCHEMA,
216        &mut plan,
217    );
218
219    assert_eq!(get_messages(&errors).len(), 0);
220}
221
222#[test]
223fn unknown_directives_must_be_ignored() {
224    use crate::validation::test_utils::*;
225
226    let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new()));
227    let errors = test_operation_with_schema(
228        "fragment Test on Type @repeatable @repeatable {
229            field @repeatable @repeatable
230          }",
231        TEST_SCHEMA,
232        &mut plan,
233    );
234
235    assert_eq!(get_messages(&errors).len(), 0);
236}
237
238#[test]
239fn duplicate_directives_in_one_location() {
240    use crate::validation::test_utils::*;
241
242    let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new()));
243    let errors = test_operation_with_schema(
244        "fragment Test on Type {
245            field @onField @onField
246          }",
247        TEST_SCHEMA,
248        &mut plan,
249    );
250    let messages = get_messages(&errors);
251    assert_eq!(messages.len(), 1);
252    assert_eq!(messages, vec!["Duplicate directive \"onField\""])
253}
254
255#[test]
256fn many_duplicate_directives_in_one_location() {
257    use crate::validation::test_utils::*;
258
259    let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new()));
260    let errors = test_operation_with_schema(
261        "fragment Test on Type {
262            field @onField @onField @onField
263          }",
264        TEST_SCHEMA,
265        &mut plan,
266    );
267    let messages = get_messages(&errors);
268    assert_eq!(messages.len(), 2);
269    assert_eq!(
270        messages,
271        vec![
272            "Duplicate directive \"onField\"",
273            "Duplicate directive \"onField\""
274        ]
275    )
276}
277
278#[test]
279fn different_duplicate_directives_in_one_location() {
280    use crate::validation::test_utils::*;
281
282    let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new()));
283    let errors = test_operation_with_schema(
284        "fragment Test on Type {
285            field @onField @testDirective @onField @testDirective
286          }",
287        TEST_SCHEMA,
288        &mut plan,
289    );
290    let messages = get_messages(&errors);
291    assert_eq!(messages.len(), 2);
292    assert_eq!(
293        messages,
294        vec![
295            "Duplicate directive \"onField\"",
296            "Duplicate directive \"testDirective\""
297        ]
298    )
299}
300
301#[test]
302fn duplicate_directives_in_many_location() {
303    use crate::validation::test_utils::*;
304
305    let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new()));
306    let errors = test_operation_with_schema(
307        "fragment Test on Type @onFragmentDefinition @onFragmentDefinition {
308            field @onField @onField
309          }",
310        TEST_SCHEMA,
311        &mut plan,
312    );
313    let messages = get_messages(&errors);
314    assert_eq!(messages.len(), 2);
315    assert_eq!(
316        messages,
317        vec![
318            "Duplicate directive \"onFragmentDefinition\"",
319            "Duplicate directive \"onField\""
320        ]
321    )
322}