diff --git a/Argo.xcodeproj/project.pbxproj b/Argo.xcodeproj/project.pbxproj index 08aedbb..6220d2b 100644 --- a/Argo.xcodeproj/project.pbxproj +++ b/Argo.xcodeproj/project.pbxproj @@ -546,8 +546,8 @@ EA395DC31A52F8EB00EB607E /* array_root.json */, EA395DC91A52FC1400EB607E /* root_object.json */, EA6DD69E1AB384C700CA3A5B /* big_data.json */, - EA47BB551AFC5DAC002D2CCD /* user_with_bad_type.json */, EA47BB581AFC5E65002D2CCD /* user_without_key.json */, + EA47BB551AFC5DAC002D2CCD /* user_with_bad_type.json */, ); path = JSON; sourceTree = ""; diff --git a/Argo/Types/DecodeError.swift b/Argo/Types/DecodeError.swift index 4799209..53ebd13 100644 --- a/Argo/Types/DecodeError.swift +++ b/Argo/Types/DecodeError.swift @@ -8,6 +8,9 @@ public enum DecodeError: Error { /// A custom error case for adding explicit failure info. case custom(String) + + /// There were multiple errors in the JSON. + case multiple([DecodeError]) } extension DecodeError: CustomStringConvertible { @@ -16,6 +19,7 @@ extension DecodeError: CustomStringConvertible { case let .typeMismatch(expected, actual): return "TypeMismatch(Expected \(expected), got \(actual))" case let .missingKey(s): return "MissingKey(\(s))" case let .custom(s): return "Custom(\(s))" + case let .multiple(es): return "Multiple(\(es.map { $0.description }.joined(separator: ", ")))" } } } @@ -29,6 +33,8 @@ extension DecodeError: Hashable { return string.hashValue case let .custom(string): return string.hashValue + case let .multiple(es): + return es.reduce(0) { $0 ^ $1.hashValue } } } } @@ -44,7 +50,18 @@ public func == (lhs: DecodeError, rhs: DecodeError) -> Bool { case let (.custom(string1), .custom(string2)): return string1 == string2 + case let (.multiple(lhs), .multiple(rhs)): + return lhs == rhs + default: return false } } + +public func + (lhs: DecodeError, rhs: DecodeError) -> DecodeError { + switch (lhs, rhs) { + case let (.multiple(es), e): return .multiple(es + [e]) + case let (e, .multiple(es)): return .multiple([e] + es) + case let (le, re): return .multiple([le, re]) + } +} diff --git a/Argo/Types/Decoded/Applicative.swift b/Argo/Types/Decoded/Applicative.swift index a057b18..11d7314 100644 --- a/Argo/Types/Decoded/Applicative.swift +++ b/Argo/Types/Decoded/Applicative.swift @@ -47,9 +47,10 @@ public extension Decoded { - returns: A value of type `Decoded` */ func apply(_ f: Decoded<(T) -> U>) -> Decoded { - switch f { - case let .success(function): return self.map(function) - case let .failure(error): return .failure(error) + switch (f, self) { + case let (.success(function), _): return self.map(function) + case let (.failure(le), .failure(re)): return .failure(le + re) + case let (.failure(f), _): return .failure(f) } } } diff --git a/Argo/Types/Decoded/Decoded.swift b/Argo/Types/Decoded/Decoded.swift index c149371..05a808c 100644 --- a/Argo/Types/Decoded/Decoded.swift +++ b/Argo/Types/Decoded/Decoded.swift @@ -53,6 +53,7 @@ public extension Decoded { case let .failure(.typeMismatch(expected, actual)): return .failure(.typeMismatch(expected: expected, actual: actual)) case let .failure(.custom(x)): return .failure(.custom(x)) + case let .failure(.multiple(es)): return .failure(.multiple(es)) } } @@ -109,6 +110,18 @@ public extension Decoded { static func customError(_ message: String) -> Decoded { return .failure(.custom(message)) } + + /** + Convenience function for creating `.Multiple` errors + + - parameter errors: The errors + + - returns: A `Decoded.Failure` with a `.Multiple` error constructed from the + provided `errors` value + */ + static func multipleErrors(errors: [DecodeError]) -> Decoded { + return .failure(.multiple(errors)) + } } extension Decoded: CustomStringConvertible { diff --git a/ArgoTests/Tests/DecodedTests.swift b/ArgoTests/Tests/DecodedTests.swift index 0511060..9af2a92 100644 --- a/ArgoTests/Tests/DecodedTests.swift +++ b/ArgoTests/Tests/DecodedTests.swift @@ -55,7 +55,44 @@ class DecodedTests: XCTestCase { default: XCTFail("Unexpected Case Occurred") } } - + + func testDecodedMultipleErrors() { + let user: Decoded = decode([ + "id": "1" + ]) + + let expected: [DecodeError] = [ + .typeMismatch(expected: "Int", actual: "String(1)"), + .missingKey("name") + ] + + switch user { + case let .failure(.multiple(errors)): XCTAssert(errors == expected) + default: XCTFail("Unexpected Case Occurred") + } + } + + func testDecodedMultipleErrorsWithOptionalKeyTypeMismatch() { + let user: Decoded = decode([ + "id": 1, + "email": 1 + ]) + + let expected: [DecodeError] = [ + .missingKey("name"), + .typeMismatch(expected: "String", actual: String(describing: JSON.number(1))) + ] + + switch user { + case let .failure(.multiple(errors)): + print("expected: \(expected)") + print("actual: \(errors)") + + XCTAssert(errors == expected) + default: XCTFail("Unexpected Case Occurred") + } + } + func testDecodedMaterializeSuccess() { let user: Decoded = decode(json(fromFile: "user_with_email")!) let materialized = materialize { user.value! }