graphql_tools/validation/rules/
unique_operation_names.rs

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