Skip to content

Commit f4cdfab

Browse files
committed
added UniqueFragmentNames
1 parent 4433705 commit f4cdfab

File tree

4 files changed

+216
-1
lines changed

4 files changed

+216
-1
lines changed

src/ast/ext.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,3 +231,9 @@ impl AstNodeWithName for query::OperationDefinition {
231231
}
232232
}
233233
}
234+
235+
impl AstNodeWithName for query::FragmentDefinition {
236+
fn node_name(&self) -> Option<String> {
237+
Some(self.name.clone())
238+
}
239+
}

src/validation/rules/defaults.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::validation::validate::ValidationPlan;
33
use super::{
44
FieldsOnCorrectType, FragmentsOnCompositeTypes, KnownFragmentNamesRule, KnownTypeNames,
55
LeafFieldSelections, LoneAnonymousOperation, NoUnusedFragments, OverlappingFieldsCanBeMerged,
6-
SingleFieldSubscriptions, UniqueOperationNames, VariablesAreInputTypes,
6+
SingleFieldSubscriptions, UniqueFragmentNames, UniqueOperationNames, VariablesAreInputTypes,
77
};
88

99
pub fn default_rules_validation_plan() -> ValidationPlan {
@@ -18,6 +18,7 @@ pub fn default_rules_validation_plan() -> ValidationPlan {
1818
plan.add_rule(Box::new(NoUnusedFragments {}));
1919
plan.add_rule(Box::new(LeafFieldSelections {}));
2020
plan.add_rule(Box::new(UniqueOperationNames {}));
21+
plan.add_rule(Box::new(UniqueFragmentNames {}));
2122
plan.add_rule(Box::new(SingleFieldSubscriptions {}));
2223
plan.add_rule(Box::new(VariablesAreInputTypes {}));
2324

src/validation/rules/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub mod overlapping_fields_can_be_merged;
1010
/// Utilities validating GraphQL documents/operations
1111
pub mod rule;
1212
pub mod single_field_subscriptions;
13+
pub mod unique_fragment_names;
1314
pub mod unique_operation_names;
1415
pub mod variables_are_input_types;
1516

@@ -24,5 +25,6 @@ pub use self::no_unused_fragments::*;
2425
pub use self::overlapping_fields_can_be_merged::*;
2526
pub use self::rule::*;
2627
pub use self::single_field_subscriptions::*;
28+
pub use self::unique_fragment_names::*;
2729
pub use self::unique_operation_names::*;
2830
pub use self::variables_are_input_types::*;
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
use std::collections::HashMap;
2+
3+
use super::ValidationRule;
4+
use crate::ast::AstNodeWithName;
5+
use crate::static_graphql::query::*;
6+
use crate::validation::utils::ValidationError;
7+
use crate::{ast::QueryVisitor, validation::utils::ValidationContext};
8+
9+
/// Unique fragment names
10+
///
11+
/// A GraphQL document is only valid if all defined fragments have unique names.
12+
///
13+
/// See https://spec.graphql.org/draft/#sec-Fragment-Name-Uniqueness
14+
pub struct UniqueFragmentNames;
15+
16+
impl QueryVisitor<FoundFragments> for UniqueFragmentNames {
17+
fn enter_fragment_definition(
18+
&self,
19+
node: &FragmentDefinition,
20+
visitor_context: &mut FoundFragments,
21+
) {
22+
if let Some(name) = node.node_name() {
23+
visitor_context.store_finding(&name);
24+
}
25+
}
26+
}
27+
28+
struct FoundFragments {
29+
findings_counter: HashMap<String, i32>,
30+
}
31+
32+
impl FoundFragments {
33+
fn new() -> Self {
34+
Self {
35+
findings_counter: HashMap::new(),
36+
}
37+
}
38+
39+
fn store_finding(&mut self, name: &String) {
40+
let value = *self.findings_counter.entry(name.clone()).or_insert(0);
41+
self.findings_counter.insert(name.clone(), value + 1);
42+
}
43+
}
44+
45+
impl ValidationRule for UniqueFragmentNames {
46+
fn validate<'a>(&self, ctx: &ValidationContext) -> Vec<ValidationError> {
47+
let mut found_fragments = FoundFragments::new();
48+
self.visit_document(&ctx.operation.clone(), &mut found_fragments);
49+
50+
found_fragments
51+
.findings_counter
52+
.into_iter()
53+
.filter(|(_key, value)| *value > 1)
54+
.map(|(key, _value)| ValidationError {
55+
message: format!("There can be only one fragment named \"{}\".", key),
56+
locations: vec![],
57+
})
58+
.collect()
59+
}
60+
}
61+
62+
#[test]
63+
fn no_fragments() {
64+
use crate::validation::test_utils::*;
65+
66+
let mut plan = create_plan_from_rule(Box::new(UniqueFragmentNames {}));
67+
let errors = test_operation_without_schema(
68+
"{
69+
field
70+
}",
71+
&mut plan,
72+
);
73+
74+
assert_eq!(get_messages(&errors).len(), 0);
75+
}
76+
77+
#[test]
78+
fn one_fragment() {
79+
use crate::validation::test_utils::*;
80+
81+
let mut plan = create_plan_from_rule(Box::new(UniqueFragmentNames {}));
82+
let errors = test_operation_without_schema(
83+
"{
84+
...fragA
85+
}
86+
fragment fragA on Type {
87+
field
88+
}",
89+
&mut plan,
90+
);
91+
92+
assert_eq!(get_messages(&errors).len(), 0);
93+
}
94+
95+
#[test]
96+
fn many_fragment() {
97+
use crate::validation::test_utils::*;
98+
99+
let mut plan = create_plan_from_rule(Box::new(UniqueFragmentNames {}));
100+
let errors = test_operation_without_schema(
101+
"{
102+
...fragA
103+
...fragB
104+
...fragC
105+
}
106+
fragment fragA on Type {
107+
fieldA
108+
}
109+
fragment fragB on Type {
110+
fieldB
111+
}
112+
fragment fragC on Type {
113+
fieldC
114+
}",
115+
&mut plan,
116+
);
117+
118+
assert_eq!(get_messages(&errors).len(), 0);
119+
}
120+
121+
#[test]
122+
fn inline_fragments_are_always_unique() {
123+
use crate::validation::test_utils::*;
124+
125+
let mut plan = create_plan_from_rule(Box::new(UniqueFragmentNames {}));
126+
let errors = test_operation_without_schema(
127+
"{
128+
...on Type {
129+
fieldA
130+
}
131+
...on Type {
132+
fieldB
133+
}
134+
}",
135+
&mut plan,
136+
);
137+
138+
assert_eq!(get_messages(&errors).len(), 0);
139+
}
140+
141+
#[test]
142+
fn fragment_and_operation_named_the_same() {
143+
use crate::validation::test_utils::*;
144+
145+
let mut plan = create_plan_from_rule(Box::new(UniqueFragmentNames {}));
146+
let errors = test_operation_without_schema(
147+
"query Foo {
148+
...Foo
149+
}
150+
fragment Foo on Type {
151+
field
152+
}",
153+
&mut plan,
154+
);
155+
156+
assert_eq!(get_messages(&errors).len(), 0);
157+
}
158+
159+
#[test]
160+
fn fragments_named_the_same() {
161+
use crate::validation::test_utils::*;
162+
163+
let mut plan = create_plan_from_rule(Box::new(UniqueFragmentNames {}));
164+
let errors = test_operation_without_schema(
165+
"{
166+
...fragA
167+
}
168+
fragment fragA on Type {
169+
fieldA
170+
}
171+
fragment fragA on Type {
172+
fieldB
173+
}",
174+
&mut plan,
175+
);
176+
177+
let messages = get_messages(&errors);
178+
assert_eq!(messages.len(), 1);
179+
assert_eq!(
180+
messages,
181+
vec!["There can be only one fragment named \"fragA\"."]
182+
);
183+
}
184+
185+
#[test]
186+
fn fragments_named_the_same_without_being_referenced() {
187+
use crate::validation::test_utils::*;
188+
189+
let mut plan = create_plan_from_rule(Box::new(UniqueFragmentNames {}));
190+
let errors = test_operation_without_schema(
191+
"fragment fragA on Type {
192+
fieldA
193+
}
194+
fragment fragA on Type {
195+
fieldB
196+
}",
197+
&mut plan,
198+
);
199+
200+
let messages = get_messages(&errors);
201+
assert_eq!(messages.len(), 1);
202+
assert_eq!(
203+
messages,
204+
vec!["There can be only one fragment named \"fragA\"."]
205+
);
206+
}

0 commit comments

Comments
 (0)