Skip to content

Commit

Permalink
Coalesce multiple errors for easier debugging
Browse files Browse the repository at this point in the history
  • Loading branch information
klaaspieter authored and Tony DiPasquale committed Oct 12, 2016
1 parent 4b57653 commit 058d428
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 5 deletions.
2 changes: 1 addition & 1 deletion Argo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "<group>";
Expand Down
17 changes: 17 additions & 0 deletions Argo/Types/DecodeError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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: ", ")))"
}
}
}
Expand All @@ -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 }
}
}
}
Expand All @@ -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])
}
}
7 changes: 4 additions & 3 deletions Argo/Types/Decoded/Applicative.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ public extension Decoded {
- returns: A value of type `Decoded<U>`
*/
func apply<U>(_ f: Decoded<(T) -> U>) -> Decoded<U> {
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)
}
}
}
13 changes: 13 additions & 0 deletions Argo/Types/Decoded/Decoded.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}

Expand Down Expand Up @@ -109,6 +110,18 @@ public extension Decoded {
static func customError<T>(_ message: String) -> Decoded<T> {
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<T>(errors: [DecodeError]) -> Decoded<T> {
return .failure(.multiple(errors))
}
}

extension Decoded: CustomStringConvertible {
Expand Down
39 changes: 38 additions & 1 deletion ArgoTests/Tests/DecodedTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,44 @@ class DecodedTests: XCTestCase {
default: XCTFail("Unexpected Case Occurred")
}
}


func testDecodedMultipleErrors() {
let user: Decoded<User> = 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<User> = 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<User> = decode(json(fromFile: "user_with_email")!)
let materialized = materialize { user.value! }
Expand Down

0 comments on commit 058d428

Please sign in to comment.