Skip to content

Commit 70ffab6

Browse files
feature: Adds UniqueArgumentNamesRule
1 parent 2c3abcc commit 70ffab6

File tree

3 files changed

+219
-1
lines changed

3 files changed

+219
-1
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
2+
/**
3+
* Unique argument names
4+
*
5+
* A GraphQL field or directive is only valid if all supplied arguments are
6+
* uniquely named.
7+
*
8+
* See https://spec.graphql.org/draft/#sec-Argument-Names
9+
*/
10+
func UniqueArgumentNamesRule(context: ValidationContext) -> Visitor {
11+
return Visitor(
12+
enter: { node, _, _, _, _ in
13+
let argumentNodes: [Argument]
14+
if let field = node as? Field {
15+
argumentNodes = field.arguments
16+
} else if let directive = node as? Directive {
17+
argumentNodes = directive.arguments
18+
} else {
19+
return .continue
20+
}
21+
22+
let seenArgs = Dictionary(grouping: argumentNodes) { $0.name.value }
23+
24+
for (argName, argNodes) in seenArgs {
25+
if argNodes.count > 1 {
26+
context.report(
27+
error: GraphQLError(
28+
message: "There can be only one argument named \"\(argName)\".",
29+
nodes: argNodes.map { $0.name }
30+
)
31+
)
32+
}
33+
}
34+
return .continue
35+
}
36+
)
37+
}

Sources/GraphQL/Validation/SpecifiedRules.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public let specifiedRules: [(ValidationContext) -> Visitor] = [
2525
// DeferStreamDirectiveOnValidOperationsRule,
2626
// DeferStreamDirectiveLabelRule,
2727
KnownArgumentNamesRule,
28-
// UniqueArgumentNamesRule,
28+
UniqueArgumentNamesRule,
2929
// ArgumentsOfCorrectTypeRule,
3030
// ValuesOfCorrectTypeRule,
3131
// ProvidedRequiredArgumentsRule,
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
@testable import GraphQL
2+
import XCTest
3+
4+
class UniqueArgumentNamesRuleTests: ValidationTestCase {
5+
override func setUp() {
6+
rule = UniqueArgumentNamesRule
7+
}
8+
9+
func testNoArgumentsOnField() throws {
10+
try assertValid(
11+
"""
12+
{
13+
field
14+
}
15+
"""
16+
)
17+
}
18+
19+
func testNoArgumentsOnDirective() throws {
20+
try assertValid(
21+
"""
22+
{
23+
field @directive
24+
}
25+
"""
26+
)
27+
}
28+
29+
func testArgumentOnField() throws {
30+
try assertValid(
31+
"""
32+
{
33+
field(arg: "value")
34+
}
35+
"""
36+
)
37+
}
38+
39+
func testArgumentOnDirective() throws {
40+
try assertValid(
41+
"""
42+
{
43+
field @directive(arg: "value")
44+
}
45+
"""
46+
)
47+
}
48+
49+
func testSameArgumentOnTwoFields() throws {
50+
try assertValid(
51+
"""
52+
{
53+
one: field(arg: "value")
54+
two: field(arg: "value")
55+
}
56+
"""
57+
)
58+
}
59+
60+
func testSameArgumentOnFieldAndDirective() throws {
61+
try assertValid(
62+
"""
63+
{
64+
field(arg: "value") @directive(arg: "value")
65+
}
66+
"""
67+
)
68+
}
69+
70+
func testSameArgumentOnTwoDirectives() throws {
71+
try assertValid(
72+
"""
73+
{
74+
field @directive1(arg: "value") @directive2(arg: "value")
75+
}
76+
"""
77+
)
78+
}
79+
80+
func testMultipleFieldArguments() throws {
81+
try assertValid(
82+
"""
83+
{
84+
field(arg1: "value", arg2: "value", arg3: "value")
85+
}
86+
"""
87+
)
88+
}
89+
90+
func testMultipleDirectiveArguments() throws {
91+
try assertValid(
92+
"""
93+
{
94+
field @directive(arg1: "value", arg2: "value", arg3: "value")
95+
}
96+
"""
97+
)
98+
}
99+
100+
func testDuplicateFieldArguments() throws {
101+
let errors = try assertInvalid(
102+
errorCount: 1,
103+
query:
104+
"""
105+
{
106+
field(arg1: "value", arg1: "value")
107+
}
108+
"""
109+
)
110+
try assertValidationError(
111+
error: errors[0],
112+
locations: [
113+
(line: 2, column: 9),
114+
(line: 2, column: 24),
115+
],
116+
message: "There can be only one argument named \"arg1\"."
117+
)
118+
}
119+
120+
func testManyDuplicateFieldArguments() throws {
121+
let errors = try assertInvalid(
122+
errorCount: 1,
123+
query:
124+
"""
125+
{
126+
field(arg1: "value", arg1: "value", arg1: "value")
127+
}
128+
"""
129+
)
130+
try assertValidationError(
131+
error: errors[0],
132+
locations: [
133+
(line: 2, column: 9),
134+
(line: 2, column: 24),
135+
(line: 2, column: 39),
136+
],
137+
message: "There can be only one argument named \"arg1\"."
138+
)
139+
}
140+
141+
func testDuplicateDirectiveArguments() throws {
142+
let errors = try assertInvalid(
143+
errorCount: 1,
144+
query:
145+
"""
146+
{
147+
field @directive(arg1: "value", arg1: "value")
148+
}
149+
"""
150+
)
151+
try assertValidationError(
152+
error: errors[0],
153+
locations: [
154+
(line: 2, column: 20),
155+
(line: 2, column: 35),
156+
],
157+
message: "There can be only one argument named \"arg1\"."
158+
)
159+
}
160+
161+
func testManyDuplicateDirectiveArguments() throws {
162+
let errors = try assertInvalid(
163+
errorCount: 1,
164+
query:
165+
"""
166+
{
167+
field @directive(arg1: "value", arg1: "value", arg1: "value")
168+
}
169+
"""
170+
)
171+
try assertValidationError(
172+
error: errors[0],
173+
locations: [
174+
(line: 2, column: 20),
175+
(line: 2, column: 35),
176+
(line: 2, column: 50),
177+
],
178+
message: "There can be only one argument named \"arg1\"."
179+
)
180+
}
181+
}

0 commit comments

Comments
 (0)