Skip to content

Commit 0c87951

Browse files
feature: Adds UniqueInputFieldNamesRule
1 parent 70ffab6 commit 0c87951

File tree

3 files changed

+170
-1
lines changed

3 files changed

+170
-1
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
2+
/**
3+
* Unique input field names
4+
*
5+
* A GraphQL input object value is only valid if all supplied fields are
6+
* uniquely named.
7+
*
8+
* See https://spec.graphql.org/draft/#sec-Input-Object-Field-Uniqueness
9+
*/
10+
func UniqueInputFieldNamesRule(context: ValidationContext) -> Visitor {
11+
var knownNameStack = [[String: Name]]()
12+
var knownNames = [String: Name]()
13+
14+
return Visitor(
15+
enter: { node, _, _, _, _ in
16+
if node is ObjectValue {
17+
knownNameStack.append(knownNames)
18+
knownNames = [:]
19+
return .continue
20+
}
21+
if let objectField = node as? ObjectField {
22+
let fieldName = objectField.name.value
23+
if let knownName = knownNames[fieldName] {
24+
context.report(
25+
error: GraphQLError(
26+
message: "There can be only one input field named \"\(fieldName)\".",
27+
nodes: [knownName, objectField.name]
28+
)
29+
)
30+
} else {
31+
knownNames[fieldName] = objectField.name
32+
}
33+
return .continue
34+
}
35+
return .continue
36+
},
37+
leave: { node, _, _, _, _ in
38+
if node is ObjectValue {
39+
let prevKnownNames = knownNameStack.popLast()
40+
knownNames = prevKnownNames ?? [:]
41+
}
42+
return .continue
43+
}
44+
)
45+
}

Sources/GraphQL/Validation/SpecifiedRules.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,5 @@ public let specifiedRules: [(ValidationContext) -> Visitor] = [
3333
// DefaultValuesOfCorrectTypeRule,
3434
// VariablesInAllowedPositionRule,
3535
// OverlappingFieldsCanBeMergedRule,
36-
// UniqueInputFieldNamesRule,
36+
UniqueInputFieldNamesRule,
3737
]
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
@testable import GraphQL
2+
import XCTest
3+
4+
class UniqueInputFieldNamesRuleTests: ValidationTestCase {
5+
override func setUp() {
6+
rule = UniqueInputFieldNamesRule
7+
}
8+
9+
func testInputObjectWithFields() throws {
10+
try assertValid(
11+
"""
12+
{
13+
field(arg: { f: true })
14+
}
15+
"""
16+
)
17+
}
18+
19+
func testSameInputObjectWithinTwoArgs() throws {
20+
try assertValid(
21+
"""
22+
{
23+
field(arg1: { f: true }, arg2: { f: true })
24+
}
25+
"""
26+
)
27+
}
28+
29+
func testMultipleInputObjectFields() throws {
30+
try assertValid(
31+
"""
32+
{
33+
field(arg: { f1: "value", f2: "value", f3: "value" })
34+
}
35+
"""
36+
)
37+
}
38+
39+
func testAllowsForNestedInputObjectsWithSimilarFields() throws {
40+
try assertValid(
41+
"""
42+
{
43+
field(arg: {
44+
deep: {
45+
deep: {
46+
id: 1
47+
}
48+
id: 1
49+
}
50+
id: 1
51+
})
52+
}
53+
"""
54+
)
55+
}
56+
57+
func testDuplicateInputObjectFields() throws {
58+
let errors = try assertInvalid(
59+
errorCount: 1,
60+
query:
61+
"""
62+
{
63+
field(arg: { f1: "value", f1: "value" })
64+
}
65+
"""
66+
)
67+
try assertValidationError(
68+
error: errors[0],
69+
locations: [
70+
(line: 2, column: 16),
71+
(line: 2, column: 29),
72+
],
73+
message: #"There can be only one input field named "f1"."#
74+
)
75+
}
76+
77+
func testManyDuplicateInputObjectFields() throws {
78+
let errors = try assertInvalid(
79+
errorCount: 2,
80+
query:
81+
"""
82+
{
83+
field(arg: { f1: "value", f1: "value", f1: "value" })
84+
}
85+
"""
86+
)
87+
try assertValidationError(
88+
error: errors[0],
89+
locations: [
90+
(line: 2, column: 16),
91+
(line: 2, column: 29),
92+
],
93+
message: #"There can be only one input field named "f1"."#
94+
)
95+
try assertValidationError(
96+
error: errors[1],
97+
locations: [
98+
(line: 2, column: 16),
99+
(line: 2, column: 42),
100+
],
101+
message: #"There can be only one input field named "f1"."#
102+
)
103+
}
104+
105+
func testNestedDuplicateInputObjectFields() throws {
106+
let errors = try assertInvalid(
107+
errorCount: 1,
108+
query:
109+
"""
110+
{
111+
field(arg: { f1: {f2: "value", f2: "value" }})
112+
}
113+
"""
114+
)
115+
try assertValidationError(
116+
error: errors[0],
117+
locations: [
118+
(line: 2, column: 21),
119+
(line: 2, column: 34),
120+
],
121+
message: #"There can be only one input field named "f2"."#
122+
)
123+
}
124+
}

0 commit comments

Comments
 (0)