Skip to content

Commit 10f35a9

Browse files
Adds 'undefined' Map case
This allows input types to differentiate between fields that were defined as null or undefined
1 parent e5de315 commit 10f35a9

File tree

4 files changed

+199
-3
lines changed

4 files changed

+199
-3
lines changed

Sources/GraphQL/Map/Map.swift

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public enum MapError : Error {
1111
// MARK: Map
1212

1313
public enum Map {
14+
case undefined
1415
case null
1516
case bool(Bool)
1617
case number(Number)
@@ -165,6 +166,13 @@ extension Map {
165166
// MARK: is<Type>
166167

167168
extension Map {
169+
public var isUndefined: Bool {
170+
if case .undefined = self {
171+
return true
172+
}
173+
return false
174+
}
175+
168176
public var isNull: Bool {
169177
if case .null = self {
170178
return true
@@ -206,6 +214,8 @@ extension Map {
206214
extension Map {
207215
public var typeDescription: String {
208216
switch self {
217+
case .undefined:
218+
return "undefined"
209219
case .null:
210220
return "null"
211221
case .bool:
@@ -259,6 +269,9 @@ extension Map {
259269
}
260270

261271
switch self {
272+
case .undefined:
273+
return false
274+
262275
case .null:
263276
return false
264277

@@ -337,6 +350,9 @@ extension Map {
337350
}
338351

339352
switch self {
353+
case .undefined:
354+
return "undefined"
355+
340356
case .null:
341357
return "null"
342358

@@ -645,6 +661,8 @@ extension Map : Codable {
645661
var container = encoder.singleValueContainer()
646662

647663
switch self {
664+
case .undefined:
665+
fatalError("undefined values should have been excluded from encoding")
648666
case .null:
649667
try container.encodeNil()
650668
case let .bool(value):
@@ -660,8 +678,10 @@ extension Map : Codable {
660678
// Instead decode as a keyed container (like normal Dictionary) in the order of our OrderedDictionary
661679
var container = encoder.container(keyedBy: _DictionaryCodingKey.self)
662680
for (key, value) in dictionary {
663-
let codingKey = _DictionaryCodingKey(stringValue: key)!
664-
try container.encode(value, forKey: codingKey)
681+
if !value.isUndefined {
682+
let codingKey = _DictionaryCodingKey(stringValue: key)!
683+
try container.encode(value, forKey: codingKey)
684+
}
665685
}
666686
}
667687
}
@@ -711,6 +731,8 @@ public func == (lhs: Map, rhs: Map) -> Bool {
711731
extension Map : Hashable {
712732
public func hash(into hasher: inout Hasher) {
713733
switch self {
734+
case .undefined:
735+
hasher.combine(0)
714736
case .null:
715737
hasher.combine(0)
716738
case let .bool(value):
@@ -837,6 +859,8 @@ extension Map {
837859

838860
func serialize(map: Map) -> String {
839861
switch map {
862+
case .undefined:
863+
return "undefined"
840864
case .null:
841865
return "null"
842866
case let .bool(value):
@@ -893,6 +917,10 @@ extension Map {
893917
}
894918

895919
for (key, value) in dictionary.sorted(by: {$0.0 < $1.0}) {
920+
guard !value.isUndefined else {
921+
continue // Do not serialize undefined values
922+
}
923+
896924
if debug {
897925
string += "\n"
898926
string += indent()

Sources/GraphQL/Map/MapSerialization.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ public struct MapSerialization {
3434

3535
static func object(with map: Map) throws -> NSObject {
3636
switch map {
37+
case .undefined:
38+
fatalError("undefined values should have been excluded from serialization")
3739
case .null:
3840
return NSNull()
3941
case let .bool(value):
@@ -48,7 +50,9 @@ public struct MapSerialization {
4850
// Coerce to an unordered dictionary
4951
var unorderedDictionary: [String: NSObject] = [:]
5052
for (key, value) in dictionary {
51-
try unorderedDictionary[key] = object(with: value)
53+
if !value.isUndefined {
54+
try unorderedDictionary[key] = object(with: value)
55+
}
5256
}
5357
return unorderedDictionary as NSDictionary
5458
}

Sources/GraphQL/Utilities/ValueFromAST.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ func valueFromAST(valueAST: Value?, type: GraphQLInputType, variables: [String:
7070
var obj = obj
7171
let field = fields[fieldName]
7272
let fieldAST = fieldASTs[fieldName]
73+
guard fieldAST != nil else {
74+
obj[fieldName] = .undefined
75+
return obj
76+
}
77+
7378
var fieldValue = try valueFromAST(
7479
valueAST: fieldAST?.value,
7580
type: field!.type,
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import XCTest
2+
import NIO
3+
@testable import GraphQL
4+
5+
fileprivate struct Echo {
6+
let field1: String?
7+
let field2: String?
8+
}
9+
10+
fileprivate let EchoInputType = try! GraphQLInputObjectType(
11+
name: "EchoInput",
12+
fields: [
13+
"field1": InputObjectField(
14+
type: GraphQLString
15+
),
16+
"field2": InputObjectField(
17+
type: GraphQLString
18+
),
19+
]
20+
)
21+
22+
fileprivate let EchoOutputType = try! GraphQLObjectType(
23+
name: "Echo",
24+
description: "",
25+
fields: [
26+
"field1": GraphQLField(
27+
type: GraphQLString
28+
),
29+
"field2": GraphQLField(
30+
type: GraphQLString
31+
),
32+
],
33+
isTypeOf: { source, _, _ in
34+
source is Echo
35+
}
36+
)
37+
38+
class InputTests : XCTestCase {
39+
40+
let schema = try! GraphQLSchema(
41+
query: try! GraphQLObjectType(
42+
name: "Query",
43+
fields: [
44+
"echo": GraphQLField(
45+
type: EchoOutputType,
46+
args: [
47+
"input": GraphQLArgument(
48+
type: EchoInputType
49+
)
50+
],
51+
resolve: { _, arguments, _, _ in
52+
let input = arguments["input"]
53+
print(input["field2"])
54+
return Echo(
55+
field1: input["field1"].string,
56+
field2: input["field2"].string
57+
)
58+
}
59+
),
60+
]
61+
),
62+
types: [EchoInputType, EchoOutputType]
63+
)
64+
65+
func testBasic() throws {
66+
let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
67+
68+
defer {
69+
XCTAssertNoThrow(try group.syncShutdownGracefully())
70+
}
71+
72+
XCTAssertEqual(
73+
try graphql(
74+
schema: schema,
75+
request: """
76+
{
77+
echo(input:{
78+
field1: "value1",
79+
field2: "value2",
80+
}) {
81+
field1
82+
field2
83+
}
84+
}
85+
""",
86+
eventLoopGroup: group
87+
).wait(),
88+
GraphQLResult(data: [
89+
"echo": [
90+
"field1": "value1",
91+
"field2": "value2",
92+
]
93+
])
94+
)
95+
}
96+
97+
func testIncludedNull() throws {
98+
let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
99+
100+
defer {
101+
XCTAssertNoThrow(try group.syncShutdownGracefully())
102+
}
103+
104+
XCTAssertEqual(
105+
try graphql(
106+
schema: schema,
107+
request: """
108+
{
109+
echo(input:{
110+
field1: "value1",
111+
field2: null,
112+
}) {
113+
field1
114+
field2
115+
}
116+
}
117+
""",
118+
eventLoopGroup: group
119+
).wait(),
120+
GraphQLResult(data: [
121+
"echo": [
122+
"field1": "value1",
123+
"field2": nil,
124+
]
125+
])
126+
)
127+
}
128+
129+
func testImpliedNull() throws {
130+
let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
131+
132+
defer {
133+
XCTAssertNoThrow(try group.syncShutdownGracefully())
134+
}
135+
136+
XCTAssertEqual(
137+
try graphql(
138+
schema: schema,
139+
request: """
140+
{
141+
echo(input:{
142+
field1: "value1"
143+
}) {
144+
field1
145+
field2
146+
}
147+
}
148+
""",
149+
eventLoopGroup: group
150+
).wait(),
151+
GraphQLResult(data: [
152+
"echo": [
153+
"field1": "value1",
154+
"field2": nil,
155+
]
156+
])
157+
)
158+
}
159+
}

0 commit comments

Comments
 (0)