Skip to content

Input object null vs undefined #87

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Sep 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-10.15, macos-latest, ubuntu-16.04, ubuntu-18.04, ubuntu-20.04]
os: [macos-10.15, macos-latest, ubuntu-18.04, ubuntu-20.04]
steps:
- uses: actions/checkout@v2
- name: Set code coverage path
Expand Down
22 changes: 13 additions & 9 deletions Sources/GraphQL/Execution/Values.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,20 @@ func getArgumentValues(argDefs: [GraphQLArgumentDefinition], argASTs: [Argument]
return try argDefs.reduce([:]) { result, argDef in
var result = result
let name = argDef.name
let valueAST = argASTMap[name]?.value
let argAST = argASTMap[name]

if let argAST = argAST {
let valueAST = argAST.value

let value = try valueFromAST(
valueAST: valueAST,
type: argDef.type,
variables: variableValues
) ?? argDef.defaultValue
let value = try valueFromAST(
valueAST: valueAST,
type: argDef.type,
variables: variableValues
)

if let value = value {
result[name] = value
} else {
result[name] = .null
}

return result
Expand Down Expand Up @@ -75,7 +79,7 @@ func getVariableValue(schema: GraphQLSchema, definitionAST: VariableDefinition,
if errors.isEmpty {
if input == .null {
if let defaultValue = definitionAST.defaultValue {
return try valueFromAST(valueAST: defaultValue, type: inputType)!
return try valueFromAST(valueAST: defaultValue, type: inputType)
}
else if !(inputType is GraphQLNonNull) {
return .null
Expand Down Expand Up @@ -148,7 +152,7 @@ func coerceValue(type: GraphQLInputType, value: Map) throws -> Map? {
var fieldValue = try coerceValue(type: field!.type, value: value[fieldName] ?? .null)

if fieldValue == .null {
fieldValue = field.flatMap({ $0.defaultValue.map({ .string($0) }) })
fieldValue = field.flatMap({ $0.defaultValue })
} else {
objCopy[fieldName] = fieldValue
}
Expand Down
36 changes: 32 additions & 4 deletions Sources/GraphQL/Map/Map.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public enum MapError : Error {
// MARK: Map

public enum Map {
case undefined
case null
case bool(Bool)
case number(Number)
Expand Down Expand Up @@ -165,6 +166,13 @@ extension Map {
// MARK: is<Type>

extension Map {
public var isUndefined: Bool {
if case .undefined = self {
return true
}
return false
}

public var isNull: Bool {
if case .null = self {
return true
Expand Down Expand Up @@ -206,6 +214,8 @@ extension Map {
extension Map {
public var typeDescription: String {
switch self {
case .undefined:
return "undefined"
case .null:
return "null"
case .bool:
Expand Down Expand Up @@ -259,6 +269,9 @@ extension Map {
}

switch self {
case .undefined:
return false

case .null:
return false

Expand Down Expand Up @@ -337,6 +350,9 @@ extension Map {
}

switch self {
case .undefined:
return "undefined"

case .null:
return "null"

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

switch self {
case .undefined:
fatalError("undefined values should have been excluded from encoding")
case .null:
try container.encodeNil()
case let .bool(value):
Expand All @@ -660,8 +678,10 @@ extension Map : Codable {
// Instead decode as a keyed container (like normal Dictionary) in the order of our OrderedDictionary
var container = encoder.container(keyedBy: _DictionaryCodingKey.self)
for (key, value) in dictionary {
let codingKey = _DictionaryCodingKey(stringValue: key)!
try container.encode(value, forKey: codingKey)
if !value.isUndefined {
let codingKey = _DictionaryCodingKey(stringValue: key)!
try container.encode(value, forKey: codingKey)
}
}
}
}
Expand Down Expand Up @@ -711,6 +731,8 @@ public func == (lhs: Map, rhs: Map) -> Bool {
extension Map : Hashable {
public func hash(into hasher: inout Hasher) {
switch self {
case .undefined:
hasher.combine(0)
case .null:
hasher.combine(0)
case let .bool(value):
Expand Down Expand Up @@ -837,6 +859,8 @@ extension Map {

func serialize(map: Map) -> String {
switch map {
case .undefined:
return "undefined"
case .null:
return "null"
case let .bool(value):
Expand Down Expand Up @@ -891,8 +915,12 @@ extension Map {
if debug {
indentLevel += 1
}

let filtered = dictionary.filter({ item in
!item.value.isUndefined
})

for (key, value) in dictionary.sorted(by: {$0.0 < $1.0}) {
for (key, value) in filtered.sorted(by: {$0.0 < $1.0}) {
if debug {
string += "\n"
string += indent()
Expand All @@ -901,7 +929,7 @@ extension Map {
string += escape(key) + ":" + serialize(map: value)
}

if index != dictionary.count - 1 {
if index != filtered.count - 1 {
if debug {
string += ", "
} else {
Expand Down
6 changes: 5 additions & 1 deletion Sources/GraphQL/Map/MapSerialization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public struct MapSerialization {

static func object(with map: Map) throws -> NSObject {
switch map {
case .undefined:
fatalError("undefined values should have been excluded from serialization")
case .null:
return NSNull()
case let .bool(value):
Expand All @@ -48,7 +50,9 @@ public struct MapSerialization {
// Coerce to an unordered dictionary
var unorderedDictionary: [String: NSObject] = [:]
for (key, value) in dictionary {
try unorderedDictionary[key] = object(with: value)
if !value.isUndefined {
try unorderedDictionary[key] = object(with: value)
}
}
return unorderedDictionary as NSDictionary
}
Expand Down
8 changes: 4 additions & 4 deletions Sources/GraphQL/Type/Definition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1274,12 +1274,12 @@ func defineInputObjectFieldMap(

public struct InputObjectField {
public let type: GraphQLInputType
public let defaultValue: String?
public let defaultValue: Map?
public let description: String?

public init(type: GraphQLInputType, defaultValue: Map? = nil, description: String? = nil) {
self.type = type
self.defaultValue = defaultValue?.description
self.defaultValue = defaultValue
self.description = description
}
}
Expand All @@ -1290,13 +1290,13 @@ public final class InputObjectFieldDefinition {
public let name: String
public internal(set) var type: GraphQLInputType
public let description: String?
public let defaultValue: String?
public let defaultValue: Map?

init(
name: String,
type: GraphQLInputType,
description: String? = nil,
defaultValue: String? = nil
defaultValue: Map? = nil
) {
self.name = name
self.type = type
Expand Down
55 changes: 26 additions & 29 deletions Sources/GraphQL/Utilities/ValueFromAST.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,14 @@ import OrderedCollections
* | Enum Value | .string |
*
*/
func valueFromAST(valueAST: Value?, type: GraphQLInputType, variables: [String: Map] = [:]) throws -> Map? {
func valueFromAST(valueAST: Value, type: GraphQLInputType, variables: [String: Map] = [:]) throws -> Map {
if let nonNullType = type as? GraphQLNonNull {
// Note: we're not checking that the result of valueFromAST is non-null.
// We're assuming that this query has been validated and the value used
// here is of the correct type.
return try valueFromAST(valueAST: valueAST, type: nonNullType.ofType as! GraphQLInputType, variables: variables)
}

guard let valueAST = valueAST else {
return nil
}

if let variable = valueAST as? Variable {
let variableName = variable.name.value

Expand All @@ -38,7 +34,11 @@ func valueFromAST(valueAST: Value?, type: GraphQLInputType, variables: [String:
// Note: we're not doing any checking that this variable is correct. We're
// assuming that this query has been validated and the variable usage here
// is of the correct type.
return variables[variableName]
if let variable = variables[variableName] {
return variable
} else {
return .null
}
}

if let list = type as? GraphQLList {
Expand All @@ -50,36 +50,38 @@ func valueFromAST(valueAST: Value?, type: GraphQLInputType, variables: [String:
valueAST: $0,
type: itemType as! GraphQLInputType,
variables: variables
)!
)
}))
}

return try [valueFromAST(valueAST: valueAST, type: itemType as! GraphQLInputType, variables: variables)!]
return try [valueFromAST(valueAST: valueAST, type: itemType as! GraphQLInputType, variables: variables)]
}

if let objectType = type as? GraphQLInputObjectType {
guard let objectValue = valueAST as? ObjectValue else {
return nil
throw GraphQLError(message: "Must be object type")
}

let fields = objectType.fields

let fieldASTs = objectValue.fields.keyMap({ $0.name.value })

return try .dictionary(fields.keys.reduce([:] as OrderedDictionary<String, Map>) { obj, fieldName in
return try .dictionary(fields.keys.reduce(OrderedDictionary<String, Map>()) { obj, fieldName in
var obj = obj
let field = fields[fieldName]
let fieldAST = fieldASTs[fieldName]
var fieldValue = try valueFromAST(
valueAST: fieldAST?.value,
type: field!.type,
variables: variables
)

if fieldValue == .null {
fieldValue = field.flatMap({ $0.defaultValue.map({ .string($0) }) })
} else {
let field = fields[fieldName]!
if let fieldAST = fieldASTs[fieldName] {
let fieldValue = try valueFromAST(
valueAST: fieldAST.value,
type: field.type,
variables: variables
)
obj[fieldName] = fieldValue
} else {
// If AST doesn't contain field, it is undefined
if let defaultValue = field.defaultValue {
obj[fieldName] = defaultValue
} else {
obj[fieldName] = .undefined
}
}

return obj
Expand All @@ -90,11 +92,6 @@ func valueFromAST(valueAST: Value?, type: GraphQLInputType, variables: [String:
throw GraphQLError(message: "Must be leaf type")
}

let parsed = try type.parseLiteral(valueAST: valueAST)

guard parsed != .null else {
return nil
}

return parsed
// If we've made it this far, it should be a literal
return try type.parseLiteral(valueAST: valueAST)
}
Loading