Skip to main content

graphql_tools/validation/rules/
unique_fragment_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 fragment names
9///
10/// A GraphQL document is only valid if all defined fragments have unique names.
11///
12/// See https://spec.graphql.org/draft/#sec-Fragment-Name-Uniqueness
13pub struct UniqueFragmentNames<'doc> {
14    findings_counter: HashMap<&'doc str, i32>,
15}
16
17impl<'doc> OperationVisitor<'doc, ValidationErrorContext> for UniqueFragmentNames<'doc> {
18    fn enter_fragment_definition(
19        &mut self,
20        _: &mut OperationVisitorContext,
21        _: &mut ValidationErrorContext,
22        fragment: &'doc FragmentDefinition,
23    ) {
24        if let Some(name) = fragment.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 fragment named \"{}\".", name),
42                    locations: vec![],
43                });
44            });
45    }
46}
47
48impl Default for UniqueFragmentNames<'_> {
49    fn default() -> Self {
50        Self::new()
51    }
52}
53
54impl<'doc> UniqueFragmentNames<'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 UniqueFragmentNames<'_> {
68    fn error_code(&self) -> &'static str {
69        "UniqueFragmentNames"
70    }
71
72    fn visitor<'doc>(&self) -> super::ValidationVisitor<'doc> {
73        Box::new(UniqueFragmentNames::new())
74    }
75}
76
77#[test]
78fn no_fragments() {
79    use crate::validation::test_utils::*;
80
81    let mut plan = create_plan_from_rule(Box::new(UniqueFragmentNames::new()));
82    let errors = test_operation_with_schema(
83        "{
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_fragment() {
95    use crate::validation::test_utils::*;
96
97    let mut plan = create_plan_from_rule(Box::new(UniqueFragmentNames::new()));
98    let errors = test_operation_with_schema(
99        "{
100          ...fragA
101        }
102        fragment fragA on Type {
103          field
104        }",
105        TEST_SCHEMA,
106        &mut plan,
107    );
108
109    assert_eq!(get_messages(&errors).len(), 0);
110}
111
112#[test]
113fn many_fragment() {
114    use crate::validation::test_utils::*;
115
116    let mut plan = create_plan_from_rule(Box::new(UniqueFragmentNames::new()));
117    let errors = test_operation_with_schema(
118        "{
119          ...fragA
120          ...fragB
121          ...fragC
122        }
123        fragment fragA on Type {
124          fieldA
125        }
126        fragment fragB on Type {
127          fieldB
128        }
129        fragment fragC on Type {
130          fieldC
131        }",
132        TEST_SCHEMA,
133        &mut plan,
134    );
135
136    assert_eq!(get_messages(&errors).len(), 0);
137}
138
139#[test]
140fn inline_fragments_are_always_unique() {
141    use crate::validation::test_utils::*;
142
143    let mut plan = create_plan_from_rule(Box::new(UniqueFragmentNames::new()));
144    let errors = test_operation_with_schema(
145        "{
146          ...on Type {
147            fieldA
148          }
149          ...on Type {
150            fieldB
151          }
152        }",
153        TEST_SCHEMA,
154        &mut plan,
155    );
156
157    assert_eq!(get_messages(&errors).len(), 0);
158}
159
160#[test]
161fn fragment_and_operation_named_the_same() {
162    use crate::validation::test_utils::*;
163
164    let mut plan = create_plan_from_rule(Box::new(UniqueFragmentNames::new()));
165    let errors = test_operation_with_schema(
166        "query Foo {
167          ...Foo
168        }
169        fragment Foo on Type {
170          field
171        }",
172        TEST_SCHEMA,
173        &mut plan,
174    );
175
176    assert_eq!(get_messages(&errors).len(), 0);
177}
178
179#[test]
180fn fragments_named_the_same() {
181    use crate::validation::test_utils::*;
182
183    let mut plan = create_plan_from_rule(Box::new(UniqueFragmentNames::new()));
184    let errors = test_operation_with_schema(
185        "{
186          ...fragA
187        }
188        fragment fragA on Type {
189          fieldA
190        }
191        fragment fragA on Type {
192          fieldB
193        }",
194        TEST_SCHEMA,
195        &mut plan,
196    );
197
198    let messages = get_messages(&errors);
199    assert_eq!(messages.len(), 1);
200    assert_eq!(
201        messages,
202        vec!["There can be only one fragment named \"fragA\"."]
203    );
204}
205
206#[test]
207fn fragments_named_the_same_without_being_referenced() {
208    use crate::validation::test_utils::*;
209
210    let mut plan = create_plan_from_rule(Box::new(UniqueFragmentNames::new()));
211    let errors = test_operation_with_schema(
212        "fragment fragA on Type {
213          fieldA
214        }
215        fragment fragA on Type {
216          fieldB
217        }",
218        TEST_SCHEMA,
219        &mut plan,
220    );
221
222    let messages = get_messages(&errors);
223    assert_eq!(messages.len(), 1);
224    assert_eq!(
225        messages,
226        vec!["There can be only one fragment named \"fragA\"."]
227    );
228}