Skip to content

Commit 27e4657

Browse files
feat: Adds UniqueArgumentDefinitionNamesRule
1 parent 45d9b15 commit 27e4657

File tree

3 files changed

+243
-1
lines changed

3 files changed

+243
-1
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
2+
/**
3+
* Unique argument definition names
4+
*
5+
* A GraphQL Object or Interface type is only valid if all its fields have uniquely named arguments.
6+
* A GraphQL Directive is only valid if all its arguments are uniquely named.
7+
*/
8+
func UniqueArgumentDefinitionNamesRule(
9+
context: SDLValidationContext
10+
) -> Visitor {
11+
return Visitor(
12+
enter: { node, _, _, _, _ in
13+
if let directiveNode = node as? DirectiveDefinition {
14+
let argumentNodes = directiveNode.arguments
15+
checkArgUniqueness(
16+
parentName: "@\(directiveNode.name.value)",
17+
argumentNodes: argumentNodes
18+
)
19+
} else if let node = node as? InterfaceTypeDefinition {
20+
checkArgUniquenessPerField(name: node.name, fields: node.fields)
21+
} else if let node = node as? InterfaceExtensionDefinition {
22+
checkArgUniquenessPerField(
23+
name: node.definition.name,
24+
fields: node.definition.fields
25+
)
26+
} else if let node = node as? ObjectTypeDefinition {
27+
checkArgUniquenessPerField(name: node.name, fields: node.fields)
28+
} else if let node = node as? TypeExtensionDefinition {
29+
checkArgUniquenessPerField(
30+
name: node.definition.name,
31+
fields: node.definition.fields
32+
)
33+
}
34+
return .continue
35+
}
36+
)
37+
38+
func checkArgUniquenessPerField(
39+
name: Name,
40+
fields: [FieldDefinition]
41+
) {
42+
let typeName = name.value
43+
let fieldNodes = fields
44+
for fieldDef in fieldNodes {
45+
let fieldName = fieldDef.name.value
46+
47+
let argumentNodes = fieldDef.arguments
48+
49+
checkArgUniqueness(parentName: "\(typeName).\(fieldName)", argumentNodes: argumentNodes)
50+
}
51+
}
52+
53+
func checkArgUniqueness(
54+
parentName: String,
55+
argumentNodes: [InputValueDefinition]
56+
) {
57+
let seenArgs = [String: [InputValueDefinition]](grouping: argumentNodes) { arg in
58+
arg.name.value
59+
}
60+
for (argName, argNodes) in seenArgs {
61+
if argNodes.count > 1 {
62+
context.report(
63+
error: GraphQLError(
64+
message: "Argument \"\(parentName)(\(argName):)\" can only be defined once.",
65+
nodes: argNodes.map { node in node.name }
66+
)
67+
)
68+
}
69+
}
70+
}
71+
}

Sources/GraphQL/Validation/SpecifiedRules.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public let specifiedSDLRules: [SDLValidationRule] = [
4242
UniqueTypeNamesRule,
4343
UniqueEnumValueNamesRule,
4444
UniqueFieldDefinitionNamesRule,
45-
// UniqueArgumentDefinitionNamesRule,
45+
UniqueArgumentDefinitionNamesRule,
4646
// UniqueDirectiveNamesRule,
4747
KnownTypeNamesRule,
4848
KnownDirectivesRule,
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
@testable import GraphQL
2+
import XCTest
3+
4+
class UniqueArgumentDefinitionNamesRuleTests: SDLValidationTestCase {
5+
override func setUp() {
6+
rule = UniqueArgumentDefinitionNamesRule
7+
}
8+
9+
func testNoArgs() throws {
10+
try assertValidationErrors(
11+
"""
12+
type SomeObject {
13+
someField: String
14+
}
15+
16+
interface SomeInterface {
17+
someField: String
18+
}
19+
20+
directive @someDirective on QUERY
21+
""",
22+
[]
23+
)
24+
}
25+
26+
func testOneArgument() throws {
27+
try assertValidationErrors(
28+
"""
29+
type SomeObject {
30+
someField(foo: String): String
31+
}
32+
33+
interface SomeInterface {
34+
someField(foo: String): String
35+
}
36+
37+
extend type SomeObject {
38+
anotherField(foo: String): String
39+
}
40+
41+
extend interface SomeInterface {
42+
anotherField(foo: String): String
43+
}
44+
45+
directive @someDirective(foo: String) on QUERY
46+
""",
47+
[]
48+
)
49+
}
50+
51+
func testMultipleArguments() throws {
52+
try assertValidationErrors(
53+
"""
54+
type SomeObject {
55+
someField(
56+
foo: String
57+
bar: String
58+
): String
59+
}
60+
61+
interface SomeInterface {
62+
someField(
63+
foo: String
64+
bar: String
65+
): String
66+
}
67+
68+
extend type SomeObject {
69+
anotherField(
70+
foo: String
71+
bar: String
72+
): String
73+
}
74+
75+
extend interface SomeInterface {
76+
anotherField(
77+
foo: String
78+
bar: String
79+
): String
80+
}
81+
82+
directive @someDirective(
83+
foo: String
84+
bar: String
85+
) on QUERY
86+
""",
87+
[]
88+
)
89+
}
90+
91+
func testDuplicatingArguments() throws {
92+
try assertValidationErrors(
93+
"""
94+
type SomeObject {
95+
someField(
96+
foo: String
97+
bar: String
98+
foo: String
99+
): String
100+
}
101+
102+
interface SomeInterface {
103+
someField(
104+
foo: String
105+
bar: String
106+
foo: String
107+
): String
108+
}
109+
110+
extend type SomeObject {
111+
anotherField(
112+
foo: String
113+
bar: String
114+
bar: String
115+
): String
116+
}
117+
118+
extend interface SomeInterface {
119+
anotherField(
120+
bar: String
121+
foo: String
122+
foo: String
123+
): String
124+
}
125+
126+
directive @someDirective(
127+
foo: String
128+
bar: String
129+
foo: String
130+
) on QUERY
131+
""",
132+
[
133+
GraphQLError(
134+
message: #"Argument "SomeObject.someField(foo:)" can only be defined once."#,
135+
locations: [
136+
.init(line: 3, column: 5),
137+
.init(line: 5, column: 5),
138+
]
139+
),
140+
GraphQLError(
141+
message: #"Argument "SomeInterface.someField(foo:)" can only be defined once."#,
142+
locations: [
143+
.init(line: 11, column: 5),
144+
.init(line: 13, column: 5),
145+
]
146+
),
147+
GraphQLError(
148+
message: #"Argument "SomeObject.anotherField(bar:)" can only be defined once."#,
149+
locations: [
150+
.init(line: 20, column: 5),
151+
.init(line: 21, column: 5),
152+
]
153+
),
154+
GraphQLError(
155+
message: #"Argument "SomeInterface.anotherField(foo:)" can only be defined once."#,
156+
locations: [
157+
.init(line: 28, column: 5),
158+
.init(line: 29, column: 5),
159+
]
160+
),
161+
GraphQLError(
162+
message: #"Argument "@someDirective(foo:)" can only be defined once."#,
163+
locations: [
164+
.init(line: 34, column: 3),
165+
.init(line: 36, column: 3),
166+
]
167+
),
168+
]
169+
)
170+
}
171+
}

0 commit comments

Comments
 (0)