Skip to content

Commit c2888ff

Browse files
fix: Scalar parsing defaults match and reject objects correctly
1 parent fe4ea0d commit c2888ff

File tree

2 files changed

+83
-21
lines changed

2 files changed

+83
-21
lines changed

Sources/GraphQL/Type/Definition.swift

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -171,35 +171,22 @@ public final class GraphQLScalarType {
171171
public let kind: TypeKind = .scalar
172172

173173
let serialize: (Any) throws -> Map
174-
let parseValue: ((Map) throws -> Map)?
175-
let parseLiteral: ((Value) throws -> Map)?
176-
177-
public init(
178-
name: String,
179-
description: String? = nil,
180-
serialize: @escaping (Any) throws -> Map
181-
) throws {
182-
try assertValid(name: name)
183-
self.name = name
184-
self.description = description
185-
self.serialize = serialize
186-
parseValue = nil
187-
parseLiteral = nil
188-
}
174+
let parseValue: (Map) throws -> Map
175+
let parseLiteral: (Value) throws -> Map
189176

190177
public init(
191178
name: String,
192179
description: String? = nil,
193180
serialize: @escaping (Any) throws -> Map,
194-
parseValue: @escaping (Map) throws -> Map,
195-
parseLiteral: @escaping (Value) throws -> Map
181+
parseValue: ((Map) throws -> Map)? = nil,
182+
parseLiteral: ((Value) throws -> Map)? = nil
196183
) throws {
197184
try assertValid(name: name)
198185
self.name = name
199186
self.description = description
200187
self.serialize = serialize
201-
self.parseValue = parseValue
202-
self.parseLiteral = parseLiteral
188+
self.parseValue = parseValue ?? defaultParseValue
189+
self.parseLiteral = parseLiteral ?? defaultParseLiteral
203190
}
204191

205192
// Serializes an internal value to include in a response.
@@ -209,15 +196,23 @@ public final class GraphQLScalarType {
209196

210197
// Parses an externally provided value to use as an input.
211198
public func parseValue(value: Map) throws -> Map {
212-
return try parseValue?(value) ?? Map.null
199+
return try parseValue(value)
213200
}
214201

215202
// Parses an externally provided literal value to use as an input.
216203
public func parseLiteral(valueAST: Value) throws -> Map {
217-
return try parseLiteral?(valueAST) ?? Map.null
204+
return try parseLiteral(valueAST)
218205
}
219206
}
220207

208+
let defaultParseValue: ((Map) throws -> Map) = { value in
209+
value
210+
}
211+
212+
let defaultParseLiteral: ((Value) throws -> Map) = { value in
213+
try valueFromASTUntyped(valueAST: value)
214+
}
215+
221216
extension GraphQLScalarType: Encodable {
222217
private enum CodingKeys: String, CodingKey {
223218
case name
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import OrderedCollections
2+
3+
/**
4+
* Produces a JavaScript value given a GraphQL Value AST.
5+
*
6+
* Unlike `valueFromAST()`, no type is provided. The resulting map
7+
* will reflect the provided GraphQL value AST.
8+
*
9+
* | GraphQL Value | Map Value |
10+
* | -------------------- | ---------------- |
11+
* | Input Object | .dictionary |
12+
* | List | .array |
13+
* | Boolean | .boolean |
14+
* | String / Enum | .string |
15+
* | Int | .int |
16+
* | Float | .float |
17+
* | Null | .null |
18+
*
19+
*/
20+
public func valueFromASTUntyped(
21+
valueAST: Value,
22+
variables: [String: Map] = [:]
23+
) throws -> Map {
24+
switch valueAST {
25+
case _ as NullValue:
26+
return .null
27+
case let value as IntValue:
28+
guard let int = Int(value.value) else {
29+
throw GraphQLError(message: "Int cannot represent non-integer value: \(value)")
30+
}
31+
return .int(int)
32+
case let value as FloatValue:
33+
guard let double = Double(value.value) else {
34+
throw GraphQLError(message: "Float cannot represent non numeric value: \(value)")
35+
}
36+
return .double(double)
37+
case let value as StringValue:
38+
return .string(value.value)
39+
case let value as EnumValue:
40+
return .string(value.value)
41+
case let value as BooleanValue:
42+
return .bool(value.value)
43+
case let value as ListValue:
44+
let array = try value.values.map { try valueFromASTUntyped(
45+
valueAST: $0,
46+
variables: variables
47+
) }
48+
return .array(array)
49+
case let value as ObjectValue:
50+
var dictionary = OrderedDictionary<String, Map>()
51+
try value.fields.forEach { field in
52+
dictionary[field.name.value] = try valueFromASTUntyped(
53+
valueAST: field.value,
54+
variables: variables
55+
)
56+
}
57+
return .dictionary(dictionary)
58+
case let value as Variable:
59+
if let variable = variables[value.name.value] {
60+
return variable
61+
} else {
62+
return .undefined
63+
}
64+
default:
65+
return .undefined
66+
}
67+
}

0 commit comments

Comments
 (0)