Skip to content

Commit

Permalink
Merge pull request JohnSundell#68 from Evertt/feature-array-of-failures
Browse files Browse the repository at this point in the history
Input-errors are now thrown as an array of errors.
  • Loading branch information
JohnSundell committed May 23, 2016
2 parents d618ca4 + 27c3362 commit fad190b
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 39 deletions.
50 changes: 34 additions & 16 deletions Sources/Unbox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,28 +85,39 @@ public func Unbox<T: UnboxableWithContext>(data: NSData, context: T.ContextType,

// MARK: - Error type

public enum UnboxValueError: ErrorType, CustomStringConvertible {
public var description: String {
switch self {
case .MissingValueForKey(let key):
return "missing key (\(key))"
case .InvalidValue(let key, let valueDescription):
return "invalid value (\(valueDescription)) for key (\(key))"
}
}

/// Thrown when a required key was missing in an unboxed dictionary. Contains the missing key.
case MissingValueForKey(String)
/// Thrown when a required key contained an invalid value in an unboxed dictionary. Contains the invalid
/// key and a description of the invalid data.
case InvalidValue(String, String)
}

/// Enum describing errors that can occur during unboxing. Use the throwing functions to receive any errors.
public enum UnboxError: ErrorType, CustomStringConvertible {
public var description: String {
let baseDescription = "[Unbox error] "

switch self {
case .MissingKey(let key):
return baseDescription + "Missing key (\(key))"
case .InvalidValue(let key, let valueDescription):
return baseDescription + "Invalid value (\(valueDescription)) for key (\(key))"
case .InvalidInput(let errors):
return baseDescription + errors.map{"\($0)"}.joinWithSeparator(", ")
case .InvalidData:
return baseDescription + "Invalid NSData"
case .CustomUnboxingFailed:
return baseDescription + "A custom unboxing closure returned nil"
}
}

/// Thrown when a required key was missing in an unboxed dictionary. Contains the missing key.
case MissingKey(String)
/// Thrown when a required key contained an invalid value in an unboxed dictionary. Contains the invalid
/// key and a description of the invalid data.
case InvalidValue(String, String)
case InvalidInput([UnboxValueError])
/// Thrown when a piece of data (NSData) could not be unboxed because it was considered invalid
case InvalidData
/// Thrown when a custom unboxing closure returned nil
Expand Down Expand Up @@ -303,11 +314,11 @@ public class Unboxer {
/// The underlying JSON dictionary that is being unboxed
public let dictionary: UnboxableDictionary
/// Whether the Unboxer has failed, and a `nil` value will be returned from the `Unbox()` function that triggered it.
public var hasFailed: Bool { return self.failureInfo != nil }
public var hasFailed: Bool { return !self.failureInfo.isEmpty }
/// Any contextual object that was supplied when unboxing was started
public let context: Any?

private var failureInfo: (key: String, value: Any?)?
private var failureInfo = [(key: String, value: Any?)]()

// MARK: - Private initializer

Expand Down Expand Up @@ -539,7 +550,7 @@ public class Unboxer {

/// Make this Unboxer fail for a certain key and invalid value. This will cause the `Unbox()` function that triggered this Unboxer to return `nil`.
public func failForInvalidValue(invalidValue: Any?, forKey key: String) {
self.failureInfo = (key, invalidValue)
self.failureInfo.append((key, invalidValue))
}
}

Expand Down Expand Up @@ -710,15 +721,22 @@ private extension Unboxer {
}

func throwIfFailed() throws {
guard let failureInfo = self.failureInfo else {
guard !failureInfo.isEmpty else {
return
}

if let failedValue: Any = failureInfo.value {
throw UnboxError.InvalidValue(failureInfo.key, "\(failedValue)")
var inputErrors = [UnboxValueError]()

for failure in failureInfo {
if let failedValue: Any = failure.value {
inputErrors.append(.InvalidValue(failure.key, "\(failedValue)"))
}
else {
inputErrors.append(.MissingValueForKey(failure.key))
}
}

throw UnboxError.MissingKey(failureInfo.key)
throw UnboxError.InvalidInput(inputErrors)
}
}

Expand Down
48 changes: 25 additions & 23 deletions Tests/UnboxTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -483,41 +483,43 @@ class UnboxTests: XCTestCase {
}

func testThrowingForMissingRequiredValues() {
let validDictionary = UnboxTestDictionaryWithAllRequiredKeysWithValidValues(false)
let invalidDictionary: UnboxableDictionary = [:]

for key in validDictionary.keys {
var invalidDictionary = validDictionary
invalidDictionary.removeValueForKey(key)

do {
try Unbox(invalidDictionary) as UnboxTestMock
XCTFail("Unbox should have thrown for a missing value")
} catch UnboxError.MissingKey(key) {
// Test passed
} catch {
do {
try Unbox(invalidDictionary) as UnboxTestMock
XCTFail("Unbox should have thrown for a missing value")
} catch UnboxError.InvalidInput(let errors) where !errors.isEmpty {
guard case .MissingValueForKey(_) = errors.first! else {
XCTFail("Unbox did not return the correct error type")
return
}
} catch {
XCTFail("Unbox did not return the correct error type")
}
}

func testThrowingForInvalidRequiredValues() {
let validDictionary = UnboxTestDictionaryWithAllRequiredKeysWithValidValues(false)
var invalidDictionary = UnboxTestDictionaryWithAllRequiredKeysWithValidValues(false)

for key in validDictionary.keys {
var invalidDictionary = validDictionary
for key in invalidDictionary.keys {
let invalidValue = NSObject()
let invalidValueDescription = "\(invalidValue)"
invalidDictionary[key] = invalidValue

do {
try Unbox(invalidDictionary) as UnboxTestMock
XCTFail("Unbox should have thrown for an invalid value")
} catch UnboxError.InvalidValue(key, invalidValueDescription) {
// Test passed
} catch {
break
}

do {
try Unbox(invalidDictionary) as UnboxTestMock
XCTFail("Unbox should have thrown for an invalid value")
} catch UnboxError.InvalidInput(let errors) where !errors.isEmpty {
guard case .InvalidValue(_, _) = errors.first! else {
XCTFail("Unbox did not return the correct error type")
return
}

} catch {
XCTFail("Unbox did not return the correct error type")
}

defer {
let unboxed: UnboxTestMock? = try? Unbox(invalidDictionary)
XCTAssertNil(unboxed, "Unbox did not return nil for an invalid dictionary")
}
Expand Down

0 comments on commit fad190b

Please sign in to comment.