graphql_tools/validation/rules/
unique_operation_names.rs1use std::collections::HashMap;
2
3use super::ValidationRule;
4use crate::ast::{AstNodeWithName, OperationVisitor, OperationVisitorContext};
5use crate::static_graphql::query::*;
6use crate::validation::utils::{ValidationError, ValidationErrorContext};
7
8pub struct UniqueOperationNames<'doc> {
14 findings_counter: HashMap<&'doc str, i32>,
15}
16
17impl<'doc> OperationVisitor<'doc, ValidationErrorContext> for UniqueOperationNames<'doc> {
18 fn enter_operation_definition(
19 &mut self,
20 _: &mut OperationVisitorContext,
21 _: &mut ValidationErrorContext,
22 operation_definition: &'doc OperationDefinition,
23 ) {
24 if let Some(name) = operation_definition.node_name() {
25 self.store_finding(name);
26 }
27 }
28
29 fn leave_document(
30 &mut self,
31 _: &mut OperationVisitorContext,
32 user_context: &mut ValidationErrorContext,
33 _: &Document,
34 ) {
35 self.findings_counter
36 .iter()
37 .filter(|(_, count)| **count > 1)
38 .for_each(|(name, _)| {
39 user_context.report_error(ValidationError {
40 error_code: self.error_code(),
41 message: format!("There can be only one operation named \"{}\".", name),
42 locations: vec![],
43 });
44 });
45 }
46}
47
48impl Default for UniqueOperationNames<'_> {
49 fn default() -> Self {
50 Self::new()
51 }
52}
53
54impl<'doc> UniqueOperationNames<'doc> {
55 pub fn new() -> Self {
56 Self {
57 findings_counter: HashMap::new(),
58 }
59 }
60
61 fn store_finding(&mut self, name: &'doc str) {
62 let value = *self.findings_counter.entry(name).or_insert(0);
63 self.findings_counter.insert(name, value + 1);
64 }
65}
66
67impl ValidationRule for UniqueOperationNames<'_> {
68 fn error_code(&self) -> &'static str {
69 "UniqueOperationNames"
70 }
71
72 fn visitor<'doc>(&self) -> super::ValidationVisitor<'doc> {
73 Box::new(UniqueOperationNames::new())
74 }
75}
76
77#[test]
78fn no_operations() {
79 use crate::validation::test_utils::*;
80
81 let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new()));
82 let errors = test_operation_with_schema(
83 "fragment fragA on Type {
84 field
85 }",
86 TEST_SCHEMA,
87 &mut plan,
88 );
89
90 assert_eq!(get_messages(&errors).len(), 0);
91}
92
93#[test]
94fn one_anon_operation() {
95 use crate::validation::test_utils::*;
96
97 let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new()));
98 let errors = test_operation_with_schema(
99 "{
100 field
101 }",
102 TEST_SCHEMA,
103 &mut plan,
104 );
105
106 let messages = get_messages(&errors);
107 assert_eq!(messages.len(), 0);
108}
109
110#[test]
111fn one_named_operation() {
112 use crate::validation::test_utils::*;
113
114 let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new()));
115 let errors = test_operation_with_schema(
116 "query Foo {
117 field
118 }",
119 TEST_SCHEMA,
120 &mut plan,
121 );
122
123 let messages = get_messages(&errors);
124 assert_eq!(messages.len(), 0);
125}
126
127#[test]
128fn multiple_operations() {
129 use crate::validation::test_utils::*;
130
131 let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new()));
132 let errors = test_operation_with_schema(
133 "query Foo {
134 field
135 }
136 query Bar {
137 field
138 }",
139 TEST_SCHEMA,
140 &mut plan,
141 );
142
143 let messages = get_messages(&errors);
144 assert_eq!(messages.len(), 0);
145}
146
147#[test]
148fn multiple_operations_of_different_types() {
149 use crate::validation::test_utils::*;
150
151 let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new()));
152 let errors = test_operation_with_schema(
153 "query Foo {
154 field
155 }
156 mutation Bar {
157 field
158 }
159 subscription Baz {
160 field
161 }",
162 TEST_SCHEMA,
163 &mut plan,
164 );
165
166 let messages = get_messages(&errors);
167 assert_eq!(messages.len(), 0);
168}
169
170#[test]
171fn fragment_and_operation_named_the_same() {
172 use crate::validation::test_utils::*;
173
174 let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new()));
175 let errors = test_operation_with_schema(
176 "query Foo {
177 ...Foo
178 }
179 fragment Foo on Type {
180 field
181 }",
182 TEST_SCHEMA,
183 &mut plan,
184 );
185
186 let messages = get_messages(&errors);
187 assert_eq!(messages.len(), 0);
188}
189
190#[test]
191fn multiple_operations_of_same_name() {
192 use crate::validation::test_utils::*;
193
194 let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new()));
195 let errors = test_operation_with_schema(
196 "query Foo {
197 fieldA
198 }
199 query Foo {
200 fieldB
201 }",
202 TEST_SCHEMA,
203 &mut plan,
204 );
205
206 let messages = get_messages(&errors);
207 assert_eq!(messages.len(), 1);
208 assert_eq!(
209 messages,
210 vec!["There can be only one operation named \"Foo\".",]
211 );
212}
213
214#[test]
215fn multiple_ops_of_same_name_of_different_types_mutation() {
216 use crate::validation::test_utils::*;
217
218 let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new()));
219 let errors = test_operation_with_schema(
220 "query Foo {
221 fieldA
222 }
223 mutation Foo {
224 fieldB
225 }",
226 TEST_SCHEMA,
227 &mut plan,
228 );
229
230 let messages = get_messages(&errors);
231 assert_eq!(messages.len(), 1);
232 assert_eq!(
233 messages,
234 vec!["There can be only one operation named \"Foo\".",]
235 );
236}
237
238#[test]
239fn multiple_ops_of_same_name_of_different_types_subscription() {
240 use crate::validation::test_utils::*;
241
242 let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new()));
243 let errors = test_operation_with_schema(
244 "query Foo {
245 fieldA
246 }
247 subscription Foo {
248 fieldB
249 }",
250 TEST_SCHEMA,
251 &mut plan,
252 );
253
254 let messages = get_messages(&errors);
255 assert_eq!(messages.len(), 1);
256 assert_eq!(
257 messages,
258 vec!["There can be only one operation named \"Foo\".",]
259 );
260}