diff --git a/Sources/GraphQL/Error/GraphQLError.swift b/Sources/GraphQL/Error/GraphQLError.swift index 125326f1..7cfad6f1 100644 --- a/Sources/GraphQL/Error/GraphQLError.swift +++ b/Sources/GraphQL/Error/GraphQLError.swift @@ -9,6 +9,7 @@ public struct GraphQLError : Error, Codable { case message case locations case path + case extensions } /** @@ -29,6 +30,18 @@ public struct GraphQLError : Error, Codable { * Appears in the result of `description`. */ public let locations: [SourceLocation] + + /** + * A dictionary containing original error reflection + * to supply additional data from error extensions. + * + * Will reflect only Codable objects. + * + * For more information about supported types look at `AnyCodable` - `init(from:)` + * + * Appears in the result of `description`. + */ + public let extensions: [String: AnyCodable]? /** * An array describing the index path into the execution response which @@ -92,6 +105,7 @@ public struct GraphQLError : Error, Codable { self.path = path self.originalError = originalError + self.extensions = originalError?.reflection } public init( @@ -106,6 +120,7 @@ public struct GraphQLError : Error, Codable { self.source = nil self.positions = [] self.originalError = nil + self.extensions = nil } public init(_ error: Error) { @@ -120,6 +135,7 @@ public struct GraphQLError : Error, Codable { message = try container.decode(String.self, forKey: .message) locations = try container.decode([SourceLocation]?.self, forKey: .locations) ?? [] path = try container.decode(IndexPath.self, forKey: .path) + extensions = try container.decodeIfPresent([String: AnyCodable].self, forKey: .extensions) } public func encode(to encoder: Encoder) throws { @@ -132,6 +148,10 @@ public struct GraphQLError : Error, Codable { } try container.encode(path, forKey: .path) + + if let extensions = extensions { + try container.encode(extensions, forKey: .extensions) + } } } diff --git a/Sources/GraphQL/Extensions/Codable+Extensions.swift b/Sources/GraphQL/Extensions/Codable+Extensions.swift index 77501eda..5fbd7342 100644 --- a/Sources/GraphQL/Extensions/Codable+Extensions.swift +++ b/Sources/GraphQL/Extensions/Codable+Extensions.swift @@ -9,3 +9,38 @@ public class AnyEncodable : Encodable { return try self.encodable.encode(to: encoder) } } + +public class AnyCodable : Codable { + private let codable: Codable + + public init(_ codable: Codable) { + self.codable = codable + } + + public func encode(to encoder: Encoder) throws { + return try self.codable.encode(to: encoder) + } + + required public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + if container.decodeNil() { + self.codable = "Null" + } else if let bool = try? container.decode(Bool.self) { + self.codable = bool + } else if let int = try? container.decode(Int.self) { + self.codable = int + } else if let uint = try? container.decode(UInt.self) { + self.codable = uint + } else if let double = try? container.decode(Double.self) { + self.codable = double + } else if let string = try? container.decode(String.self) { + self.codable = string + } else if let array = try? container.decode([AnyCodable].self) { + codable = array + } else if let dictionary = try? container.decode([String: AnyCodable].self) { + codable = dictionary + } else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "AnyCodable: can't decode value") + } + } +} diff --git a/Sources/GraphQL/Extensions/Error+Extensions.swift b/Sources/GraphQL/Extensions/Error+Extensions.swift new file mode 100644 index 00000000..f9f5ecf1 --- /dev/null +++ b/Sources/GraphQL/Extensions/Error+Extensions.swift @@ -0,0 +1,13 @@ +public extension Error { + var reflection: [String: AnyCodable] { + let errorReflection: Mirror = Mirror(reflecting: self) + return Dictionary(uniqueKeysWithValues: errorReflection.children.lazy.map({ (label: String?, value: Any) -> (String, AnyCodable)? in + guard let key = label, + let codableValue = value as? Codable else { + return nil + } + + return (key, AnyCodable(codableValue)) + }).compactMap { $0 }) + } +}