Skip to content

Commit

Permalink
Merge pull request #87 from NeedleInAJayStack/inputNulls
Browse files Browse the repository at this point in the history
  • Loading branch information
paulofaria authored Sep 21, 2021
2 parents e5de315 + 0a47a82 commit 834e2cf
Show file tree
Hide file tree
Showing 8 changed files with 615 additions and 52 deletions.
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

0 comments on commit 834e2cf

Please sign in to comment.