Skip to main content

graphql_tools/validation/rules/
unique_operation_names.rs

1use 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
8/// Unique operation names
9///
10/// A GraphQL document is only valid if all defined operations have unique names.
11///
12/// See https://spec.graphql.org/draft/#sec-Operation-Name-Uniqueness
13pub 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}