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::{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<'doc> OperationVisitor<'doc, ValidationErrorContext> for UniqueDirectivesPerLocation {
60    fn enter_operation_definition(
61        &mut self,
62        ctx: &mut OperationVisitorContext<'doc>,
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<'doc>,
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<'doc>,
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<'doc>,
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<'doc>,
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(&self) -> &'static str {
108        "UniqueDirectivesPerLocation"
109    }
110
111    fn visitor<'doc>(&self) -> super::ValidationVisitor<'doc> {
112        Box::new(UniqueDirectivesPerLocation::new())
113    }
114}
115
116#[test]
117fn no_directives() {
118    use crate::validation::test_utils::*;
119
120    let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new()));
121    let errors = test_operation_with_schema(
122        "fragment Test on Type {
123            field
124          }",
125        TEST_SCHEMA,
126        &mut plan,
127    );
128
129    assert_eq!(get_messages(&errors).len(), 0);
130}
131
132#[test]
133fn unique_directives_in_different_locations() {
134    use crate::validation::test_utils::*;
135
136    let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new()));
137    let errors = test_operation_with_schema(
138        "fragment Test on Type @directiveA {
139            field @directiveB
140          }",
141        TEST_SCHEMA,
142        &mut plan,
143    );
144
145    assert_eq!(get_messages(&errors).len(), 0);
146}
147
148#[test]
149fn unique_directives_in_same_location() {
150    use crate::validation::test_utils::*;
151
152    let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new()));
153    let errors = test_operation_with_schema(
154        "fragment Test on Type @directiveA @directiveB {
155            field @directiveA @directiveB
156          }",
157        TEST_SCHEMA,
158        &mut plan,
159    );
160
161    assert_eq!(get_messages(&errors).len(), 0);
162}
163
164#[test]
165fn same_directives_in_different_locations() {
166    use crate::validation::test_utils::*;
167
168    let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new()));
169    let errors = test_operation_with_schema(
170        "fragment Test on Type @directiveA {
171            field @directiveA
172          }",
173        TEST_SCHEMA,
174        &mut plan,
175    );
176
177    assert_eq!(get_messages(&errors).len(), 0);
178}
179
180#[test]
181fn same_directives_in_similar_locations() {
182    use crate::validation::test_utils::*;
183
184    let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new()));
185    let errors = test_operation_with_schema(
186        "fragment Test on Type {
187            field @directive
188            field @directive
189          }",
190        TEST_SCHEMA,
191        &mut plan,
192    );
193
194    assert_eq!(get_messages(&errors).len(), 0);
195}
196
197#[test]
198fn repeatable_directives_in_same_location() {
199    use crate::validation::test_utils::*;
200
201    let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new()));
202    let errors = test_operation_with_schema(
203        "fragment Test on Type @repeatable @repeatable {
204            field @repeatable @repeatable
205          }",
206        TEST_SCHEMA,
207        &mut plan,
208    );
209
210    assert_eq!(get_messages(&errors).len(), 0);
211}
212
213#[test]
214fn unknown_directives_must_be_ignored() {
215    use crate::validation::test_utils::*;
216
217    let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new()));
218    let errors = test_operation_with_schema(
219        "fragment Test on Type @repeatable @repeatable {
220            field @repeatable @repeatable
221          }",
222        TEST_SCHEMA,
223        &mut plan,
224    );
225
226    assert_eq!(get_messages(&errors).len(), 0);
227}
228
229#[test]
230fn duplicate_directives_in_one_location() {
231    use crate::validation::test_utils::*;
232
233    let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new()));
234    let errors = test_operation_with_schema(
235        "fragment Test on Type {
236            field @onField @onField
237          }",
238        TEST_SCHEMA,
239        &mut plan,
240    );
241    let messages = get_messages(&errors);
242    assert_eq!(messages.len(), 1);
243    assert_eq!(messages, vec!["Duplicate directive \"onField\""])
244}
245
246#[test]
247fn many_duplicate_directives_in_one_location() {
248    use crate::validation::test_utils::*;
249
250    let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new()));
251    let errors = test_operation_with_schema(
252        "fragment Test on Type {
253            field @onField @onField @onField
254          }",
255        TEST_SCHEMA,
256        &mut plan,
257    );
258    let messages = get_messages(&errors);
259    assert_eq!(messages.len(), 2);
260    assert_eq!(
261        messages,
262        vec![
263            "Duplicate directive \"onField\"",
264            "Duplicate directive \"onField\""
265        ]
266    )
267}
268
269#[test]
270fn different_duplicate_directives_in_one_location() {
271    use crate::validation::test_utils::*;
272
273    let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new()));
274    let errors = test_operation_with_schema(
275        "fragment Test on Type {
276            field @onField @testDirective @onField @testDirective
277          }",
278        TEST_SCHEMA,
279        &mut plan,
280    );
281    let messages = get_messages(&errors);
282    assert_eq!(messages.len(), 2);
283    assert_eq!(
284        messages,
285        vec![
286            "Duplicate directive \"onField\"",
287            "Duplicate directive \"testDirective\""
288        ]
289    )
290}
291
292#[test]
293fn duplicate_directives_in_many_location() {
294    use crate::validation::test_utils::*;
295
296    let mut plan = create_plan_from_rule(Box::new(UniqueDirectivesPerLocation::new()));
297    let errors = test_operation_with_schema(
298        "fragment Test on Type @onFragmentDefinition @onFragmentDefinition {
299            field @onField @onField
300          }",
301        TEST_SCHEMA,
302        &mut plan,
303    );
304    let messages = get_messages(&errors);
305    assert_eq!(messages.len(), 2);
306    assert_eq!(
307        messages,
308        vec![
309            "Duplicate directive \"onFragmentDefinition\"",
310            "Duplicate directive \"onField\""
311        ]
312    )
313}