Skip to content

Commit 1b11819

Browse files
authored
Merge pull request #72 from youfoodz/fix-interface-field-argument-validation
Fixed asserting an object implements an interface with required field arguments
2 parents d377ac2 + 13c50ad commit 1b11819

File tree

4 files changed

+198
-9
lines changed

4 files changed

+198
-9
lines changed

Sources/GraphQL/Type/Definition.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,10 @@ public struct GraphQLArgumentDefinition {
693693
}
694694
}
695695

696+
public func isRequiredArgument(_ arg: GraphQLArgumentDefinition) -> Bool {
697+
return arg.type is GraphQLNonNull && arg.defaultValue == nil
698+
}
699+
696700
extension GraphQLArgumentDefinition : Encodable {
697701
private enum CodingKeys : String, CodingKey {
698702
case name

Sources/GraphQL/Type/Schema.swift

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -339,15 +339,12 @@ func assert(
339339
// Assert additional arguments must not be required.
340340
for objectArg in objectField.args {
341341
let argName = objectArg.name
342-
if interfaceField.args.find({ $0.name == argName }) != nil {
343-
guard !(objectArg.type is GraphQLNonNull) else {
344-
throw GraphQLError(
345-
message:
346-
"\(object.name).\(fieldName)(\(argName):) is of required type " +
347-
"\"\(objectArg.type)\" but is not also provided by the " +
348-
"interface \(interface.name).\(fieldName)."
349-
)
350-
}
342+
if interfaceField.args.find({ $0.name == argName }) == nil && isRequiredArgument(objectArg) {
343+
throw GraphQLError(
344+
message:
345+
"\(object.name).\(fieldName) includes required argument (\(argName):) that is missing " +
346+
"from the Interface field \(interface.name).\(fieldName)."
347+
)
351348
}
352349
}
353350
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
@testable import GraphQL
2+
import XCTest
3+
4+
class GraphQLArgumentDefinitionTests: XCTestCase {
5+
6+
func testArgumentWithNullableTypeIsNotARequiredArgument() {
7+
let argument = GraphQLArgumentDefinition(
8+
name: "nullableString",
9+
type: GraphQLString
10+
)
11+
12+
XCTAssertFalse(isRequiredArgument(argument))
13+
}
14+
15+
func testArgumentWithNonNullTypeIsNotARequiredArgumentWhenItHasADefaultValue() {
16+
let argument = GraphQLArgumentDefinition(
17+
name: "nonNullString",
18+
type: GraphQLNonNull(GraphQLString),
19+
defaultValue: .string("Some string")
20+
)
21+
22+
XCTAssertFalse(isRequiredArgument(argument))
23+
}
24+
25+
func testArgumentWithNonNullArgumentIsARequiredArgumentWhenItDoesNotHaveADefaultValue() {
26+
let argument = GraphQLArgumentDefinition(
27+
name: "nonNullString",
28+
type: GraphQLNonNull(GraphQLString),
29+
defaultValue: nil
30+
)
31+
32+
XCTAssertTrue(isRequiredArgument(argument))
33+
}
34+
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
@testable import GraphQL
2+
import XCTest
3+
4+
class GraphQLSchemaTests: XCTestCase {
5+
6+
func testAssertObjectImplementsInterfacePassesWhenObjectFieldHasRequiredArgumentsFromInterface() throws {
7+
let interface = try GraphQLInterfaceType(
8+
name: "Interface",
9+
fields: [
10+
"fieldWithNoArg": GraphQLField(
11+
type: GraphQLInt,
12+
args: [:]
13+
),
14+
"fieldWithOneArg": GraphQLField(
15+
type: GraphQLInt,
16+
args: ["requiredArg": GraphQLArgument(type: GraphQLString)]
17+
),
18+
"fieldWithMultipleArgs": GraphQLField(
19+
type: GraphQLInt,
20+
args: [
21+
"arg1": GraphQLArgument(type: GraphQLString),
22+
"arg2": GraphQLArgument(type: GraphQLNonNull(GraphQLInt)),
23+
"arg3": GraphQLArgument(type: GraphQLNonNull(GraphQLBoolean))
24+
]
25+
),
26+
]
27+
)
28+
29+
let object = try GraphQLObjectType(
30+
name: "Object",
31+
fields: [
32+
"fieldWithNoArg": GraphQLField(
33+
type: GraphQLInt,
34+
args: [:]
35+
),
36+
"fieldWithOneArg": GraphQLField(
37+
type: GraphQLInt,
38+
args: ["requiredArg": GraphQLArgument(type: GraphQLString)]
39+
),
40+
"fieldWithMultipleArgs": GraphQLField(
41+
type: GraphQLInt,
42+
args: [
43+
"arg1": GraphQLArgument(type: GraphQLString),
44+
"arg2": GraphQLArgument(type: GraphQLNonNull(GraphQLInt)),
45+
"arg3": GraphQLArgument(type: GraphQLNonNull(GraphQLBoolean))
46+
]
47+
),
48+
],
49+
interfaces: [interface],
50+
isTypeOf: { (_, _, _) -> Bool in
51+
preconditionFailure("Should not be called")
52+
}
53+
)
54+
55+
_ = try GraphQLSchema(query: object, types: [interface, object])
56+
}
57+
58+
func testAssertObjectImplementsInterfacePassesWhenObjectFieldHasRequiredArgumentMissingInInterfaceButHasDefaultValue() throws {
59+
let interface = try GraphQLInterfaceType(
60+
name: "Interface",
61+
fields: [
62+
"fieldWithOneArg": GraphQLField(
63+
type: GraphQLInt,
64+
args: [:]
65+
),
66+
]
67+
)
68+
69+
let object = try GraphQLObjectType(
70+
name: "Object",
71+
fields: [
72+
"fieldWithOneArg": GraphQLField(
73+
type: GraphQLInt,
74+
args: [
75+
"addedRequiredArgWithDefaultValue": GraphQLArgument(
76+
type: GraphQLNonNull(GraphQLInt),
77+
defaultValue: .int(5)
78+
)
79+
]
80+
),
81+
],
82+
interfaces: [interface],
83+
isTypeOf: { (_, _, _) -> Bool in
84+
preconditionFailure("Should not be called")
85+
}
86+
)
87+
88+
_ = try GraphQLSchema(query: object, types: [interface, object])
89+
}
90+
91+
func testAssertObjectImplementsInterfacePassesWhenObjectFieldHasNullableArgumentMissingInInterface() throws {
92+
let interface = try GraphQLInterfaceType(
93+
name: "Interface",
94+
fields: [
95+
"fieldWithOneArg": GraphQLField(
96+
type: GraphQLInt,
97+
args: [:]
98+
),
99+
]
100+
)
101+
102+
let object = try GraphQLObjectType(
103+
name: "Object",
104+
fields: [
105+
"fieldWithOneArg": GraphQLField(
106+
type: GraphQLInt,
107+
args: ["addedNullableArg": GraphQLArgument(type: GraphQLInt)]
108+
),
109+
],
110+
interfaces: [interface],
111+
isTypeOf: { (_, _, _) -> Bool in
112+
preconditionFailure("Should not be called")
113+
}
114+
)
115+
116+
_ = try GraphQLSchema(query: object, types: [interface, object])
117+
}
118+
119+
func testAssertObjectImplementsInterfaceFailsWhenObjectFieldHasRequiredArgumentMissingInInterface() throws {
120+
let interface = try GraphQLInterfaceType(
121+
name: "Interface",
122+
fields: [
123+
"fieldWithoutArg": GraphQLField(
124+
type: GraphQLInt,
125+
args: [:]
126+
),
127+
]
128+
)
129+
130+
let object = try GraphQLObjectType(
131+
name: "Object",
132+
fields: [
133+
"fieldWithoutArg": GraphQLField(
134+
type: GraphQLInt,
135+
args: [
136+
"addedRequiredArg": GraphQLArgument(type: GraphQLNonNull(GraphQLInt))
137+
]
138+
),
139+
],
140+
interfaces: [interface],
141+
isTypeOf: { (_, _, _) -> Bool in
142+
preconditionFailure("Should not be called")
143+
}
144+
)
145+
146+
do {
147+
_ = try GraphQLSchema(query: object, types: [interface, object])
148+
XCTFail("Expected errors when creating schema")
149+
} catch {
150+
let graphQLError = try XCTUnwrap(error as? GraphQLError)
151+
XCTAssertEqual(graphQLError.message, "Object.fieldWithoutArg includes required argument (addedRequiredArg:) that is missing from the Interface field Interface.fieldWithoutArg.")
152+
}
153+
}
154+
}

0 commit comments

Comments
 (0)