graphql_tools/validation/rules/
unique_directives_per_location.rs1use 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
12pub 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}