graphql_tools/validation/rules/
unique_directives_per_location.rs

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