Skip to content

Commit 3d0e458

Browse files
committed
added UniqueArgumentNames
1 parent d6c55ec commit 3d0e458

File tree

4 files changed

+299
-2
lines changed

4 files changed

+299
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ cargo add graphql-tools
6262
- [ ] KnownDirectives
6363
- [ ] UniqueDirectivesPerLocation
6464
- [x] KnownArgumentNames
65-
- [ ] UniqueArgumentNames
65+
- [x] UniqueArgumentNames
6666
- [ ] ValuesOfCorrectType
6767
- [ ] ProvidedRequiredArguments
6868
- [ ] VariablesInAllowedPosition

src/validation/rules/defaults.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ use super::{
44
FieldsOnCorrectType, FragmentsOnCompositeTypes, KnownArgumentNames, KnownFragmentNames,
55
KnownTypeNames, LeafFieldSelections, LoneAnonymousOperation, NoFragmentsCycle,
66
NoUndefinedVariables, NoUnusedFragments, OverlappingFieldsCanBeMerged, PossibleFragmentSpreads,
7-
SingleFieldSubscriptions, UniqueFragmentNames, UniqueOperationNames, VariablesAreInputTypes,
7+
SingleFieldSubscriptions, UniqueArgumentNames, UniqueFragmentNames, UniqueOperationNames,
8+
VariablesAreInputTypes,
89
};
910

1011
pub fn default_rules_validation_plan() -> ValidationPlan {
@@ -26,6 +27,7 @@ pub fn default_rules_validation_plan() -> ValidationPlan {
2627
plan.add_rule(Box::new(PossibleFragmentSpreads {}));
2728
plan.add_rule(Box::new(NoUndefinedVariables {}));
2829
plan.add_rule(Box::new(KnownArgumentNames {}));
30+
plan.add_rule(Box::new(UniqueArgumentNames {}));
2931

3032
plan
3133
}

src/validation/rules/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub mod possible_fragment_spreads;
1414
/// Utilities validating GraphQL documents/operations
1515
pub mod rule;
1616
pub mod single_field_subscriptions;
17+
pub mod unique_argument_names;
1718
pub mod unique_fragment_names;
1819
pub mod unique_operation_names;
1920
pub mod variables_are_input_types;
@@ -33,6 +34,7 @@ pub use self::overlapping_fields_can_be_merged::*;
3334
pub use self::possible_fragment_spreads::*;
3435
pub use self::rule::*;
3536
pub use self::single_field_subscriptions::*;
37+
pub use self::unique_argument_names::*;
3638
pub use self::unique_fragment_names::*;
3739
pub use self::unique_operation_names::*;
3840
pub use self::variables_are_input_types::*;
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
use std::collections::HashMap;
2+
3+
use graphql_parser::Pos;
4+
5+
use super::ValidationRule;
6+
use crate::static_graphql::query::Value;
7+
use crate::validation::utils::{ValidationError, ValidationErrorContext};
8+
use crate::{ast::QueryVisitor, validation::utils::ValidationContext};
9+
10+
/// Unique argument names
11+
///
12+
/// A GraphQL field or directive is only valid if all supplied arguments are
13+
/// uniquely named.
14+
///
15+
/// See https://spec.graphql.org/draft/#sec-Argument-Names
16+
pub struct UniqueArgumentNames;
17+
18+
fn collect_from_arguments(
19+
reported_position: Pos,
20+
arguments: &Vec<(String, Value)>,
21+
) -> HashMap<String, Vec<Pos>> {
22+
let mut found_args = HashMap::<String, Vec<Pos>>::new();
23+
24+
for (arg_name, _arg_value) in arguments {
25+
found_args
26+
.entry(arg_name.clone())
27+
.or_insert(vec![])
28+
.push(reported_position);
29+
}
30+
31+
found_args
32+
}
33+
34+
impl<'a> QueryVisitor<ValidationErrorContext<'a>> for UniqueArgumentNames {
35+
fn enter_field(
36+
&self,
37+
field: &crate::static_graphql::query::Field,
38+
visitor_context: &mut ValidationErrorContext<'a>,
39+
) {
40+
let found_args = collect_from_arguments(field.position, &field.arguments);
41+
42+
found_args.iter().for_each(|(arg_name, positions)| {
43+
if positions.len() > 1 {
44+
visitor_context.report_error(ValidationError {
45+
message: format!("There can be only one argument named \"{}\".", arg_name),
46+
locations: positions.clone(),
47+
})
48+
}
49+
});
50+
}
51+
52+
fn enter_directive(
53+
&self,
54+
directive: &crate::static_graphql::query::Directive,
55+
visitor_context: &mut ValidationErrorContext<'a>,
56+
) {
57+
let found_args = collect_from_arguments(directive.position, &directive.arguments);
58+
59+
found_args.iter().for_each(|(arg_name, positions)| {
60+
if positions.len() > 1 {
61+
visitor_context.report_error(ValidationError {
62+
message: format!("There can be only one argument named \"{}\".", arg_name),
63+
locations: positions.clone(),
64+
})
65+
}
66+
});
67+
}
68+
}
69+
70+
impl ValidationRule for UniqueArgumentNames {
71+
fn validate<'a>(&self, ctx: &ValidationContext) -> Vec<ValidationError> {
72+
let mut helper = ValidationErrorContext::new(&ctx);
73+
self.visit_document(&ctx.operation.clone(), &mut helper);
74+
75+
helper.errors
76+
}
77+
}
78+
79+
#[test]
80+
fn no_arguments_on_field() {
81+
use crate::validation::test_utils::*;
82+
83+
let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {}));
84+
let errors = test_operation_without_schema(
85+
"{
86+
field
87+
}",
88+
&mut plan,
89+
);
90+
91+
assert_eq!(get_messages(&errors).len(), 0);
92+
}
93+
94+
#[test]
95+
fn no_arguments_on_directive() {
96+
use crate::validation::test_utils::*;
97+
98+
let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {}));
99+
let errors = test_operation_without_schema(
100+
"{
101+
field @directive
102+
}",
103+
&mut plan,
104+
);
105+
106+
assert_eq!(get_messages(&errors).len(), 0);
107+
}
108+
109+
#[test]
110+
fn argument_on_field() {
111+
use crate::validation::test_utils::*;
112+
113+
let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {}));
114+
let errors = test_operation_without_schema(
115+
"{
116+
field(arg: \"value\")
117+
}",
118+
&mut plan,
119+
);
120+
121+
assert_eq!(get_messages(&errors).len(), 0);
122+
}
123+
124+
#[test]
125+
fn argument_on_directive() {
126+
use crate::validation::test_utils::*;
127+
128+
let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {}));
129+
let errors = test_operation_without_schema(
130+
"{
131+
field @directive(arg: \"value\")
132+
}",
133+
&mut plan,
134+
);
135+
136+
assert_eq!(get_messages(&errors).len(), 0);
137+
}
138+
139+
#[test]
140+
fn same_argument_on_two_fields() {
141+
use crate::validation::test_utils::*;
142+
143+
let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {}));
144+
let errors = test_operation_without_schema(
145+
"{
146+
one: field(arg: \"value\")
147+
two: field(arg: \"value\")
148+
}",
149+
&mut plan,
150+
);
151+
152+
assert_eq!(get_messages(&errors).len(), 0);
153+
}
154+
155+
#[test]
156+
fn same_argument_on_field_and_directive() {
157+
use crate::validation::test_utils::*;
158+
159+
let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {}));
160+
let errors = test_operation_without_schema(
161+
"{
162+
field(arg: \"value\") @directive(arg: \"value\")
163+
}",
164+
&mut plan,
165+
);
166+
167+
assert_eq!(get_messages(&errors).len(), 0);
168+
}
169+
170+
#[test]
171+
fn same_argument_on_two_directives() {
172+
use crate::validation::test_utils::*;
173+
174+
let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {}));
175+
let errors = test_operation_without_schema(
176+
"{
177+
field @directive1(arg: \"value\") @directive2(arg: \"value\")
178+
}",
179+
&mut plan,
180+
);
181+
182+
assert_eq!(get_messages(&errors).len(), 0);
183+
}
184+
185+
#[test]
186+
fn multiple_field_arguments() {
187+
use crate::validation::test_utils::*;
188+
189+
let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {}));
190+
let errors = test_operation_without_schema(
191+
"{
192+
field(arg1: \"value\", arg2: \"value\", arg3: \"value\")
193+
}",
194+
&mut plan,
195+
);
196+
197+
assert_eq!(get_messages(&errors).len(), 0);
198+
}
199+
200+
#[test]
201+
fn multiple_directive_argument() {
202+
use crate::validation::test_utils::*;
203+
204+
let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {}));
205+
let errors = test_operation_without_schema(
206+
"{
207+
field @directive(arg1: \"value\", arg2: \"value\", arg3: \"value\")
208+
}",
209+
&mut plan,
210+
);
211+
212+
assert_eq!(get_messages(&errors).len(), 0);
213+
}
214+
215+
#[test]
216+
fn duplicate_field_arguments() {
217+
use crate::validation::test_utils::*;
218+
219+
let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {}));
220+
let errors = test_operation_without_schema(
221+
"{
222+
field(arg1: \"value\", arg1: \"value\")
223+
}",
224+
&mut plan,
225+
);
226+
227+
let messages = get_messages(&errors);
228+
assert_eq!(messages.len(), 1);
229+
assert_eq!(
230+
messages,
231+
vec!["There can be only one argument named \"arg1\"."]
232+
);
233+
}
234+
235+
#[test]
236+
fn many_duplicate_field_arguments() {
237+
use crate::validation::test_utils::*;
238+
239+
let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {}));
240+
let errors = test_operation_without_schema(
241+
"{
242+
field(arg1: \"value\", arg1: \"value\", arg1: \"value\")
243+
}",
244+
&mut plan,
245+
);
246+
247+
let messages = get_messages(&errors);
248+
assert_eq!(messages.len(), 1);
249+
assert_eq!(
250+
messages,
251+
vec!["There can be only one argument named \"arg1\"."]
252+
);
253+
}
254+
255+
#[test]
256+
fn duplicate_directive_arguments() {
257+
use crate::validation::test_utils::*;
258+
259+
let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {}));
260+
let errors = test_operation_without_schema(
261+
"{
262+
field @directive(arg1: \"value\", arg1: \"value\")
263+
}",
264+
&mut plan,
265+
);
266+
267+
let messages = get_messages(&errors);
268+
assert_eq!(messages.len(), 1);
269+
assert_eq!(
270+
messages,
271+
vec!["There can be only one argument named \"arg1\"."]
272+
);
273+
}
274+
275+
#[test]
276+
fn many_duplicate_directive_arguments() {
277+
use crate::validation::test_utils::*;
278+
279+
let mut plan = create_plan_from_rule(Box::new(UniqueArgumentNames {}));
280+
let errors = test_operation_without_schema(
281+
"{
282+
field @directive(arg1: \"value\", arg1: \"value\", arg1: \"value\")
283+
}",
284+
&mut plan,
285+
);
286+
287+
let messages = get_messages(&errors);
288+
assert_eq!(messages.len(), 1);
289+
assert_eq!(
290+
messages,
291+
vec!["There can be only one argument named \"arg1\"."]
292+
);
293+
}

0 commit comments

Comments
 (0)