From 805af965b3331008173db1325ec27ac96d0b854d Mon Sep 17 00:00:00 2001 From: Mike Bignell Date: Wed, 29 Jun 2016 11:32:08 +0100 Subject: [PATCH 1/5] Added unboxing of array of values that can be formatted with a formatter --- Sources/Unbox.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Sources/Unbox.swift b/Sources/Unbox.swift index 1fb2c39..dcfa771 100644 --- a/Sources/Unbox.swift +++ b/Sources/Unbox.swift @@ -692,6 +692,20 @@ public class Unboxer { }) } + /// Unbox a required Array containing values that can be formatted using a formatter + public func unbox(key: String, isKeyPath: Bool = true, formatter: F) -> [T] { + return UnboxValueResolver<[F.UnboxRawValueType]>(self).resolveRequiredValueForKey(key, isKeyPath: isKeyPath, fallbackValue: [], transform: { (array) -> [T]? in + return array.flatMap({ formatter.formatUnboxedValue($0) }) + }) + } + + /// Unbox an optional Array containing values that can be formatted using a formatter + public func unbox(key: String, isKeyPath: Bool = true, formatter: F) -> [T]? { + return UnboxValueResolver<[F.UnboxRawValueType]>(self).resolveOptionalValueForKey(key, isKeyPath: isKeyPath, transform: { (array) -> [T]? in + return array.flatMap({ formatter.formatUnboxedValue($0) }) + }) + } + /// Make this Unboxer fail for a certain key. This will cause the `Unbox()` function that triggered this Unboxer to return `nil`. public func failForKey(key: String) { self.failForInvalidValue(nil, forKey: key) From 46f50720c902439eb23bce2c0993577a52ef7173 Mon Sep 17 00:00:00 2001 From: Mike Bignell Date: Wed, 29 Jun 2016 13:04:18 +0100 Subject: [PATCH 2/5] Added invalid element options to the unbox array with formatter. Started writing tests --- Sources/Unbox.swift | 38 ++++++++++++++++++++++++++++++++------ Tests/UnboxTests.swift | 39 ++++++++++++++++++++++++++++++++++----- 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/Sources/Unbox.swift b/Sources/Unbox.swift index dcfa771..dc4e0a3 100644 --- a/Sources/Unbox.swift +++ b/Sources/Unbox.swift @@ -692,17 +692,43 @@ public class Unboxer { }) } - /// Unbox a required Array containing values that can be formatted using a formatter - public func unbox(key: String, isKeyPath: Bool = true, formatter: F) -> [T] { + /// Unbox a required Array containing values that can be formatted using a formatter (optionally allowing invalid elements) + public func unbox(key: String, isKeyPath: Bool = true, formatter: F, allowInvalidElements: Bool = false) -> [T] { return UnboxValueResolver<[F.UnboxRawValueType]>(self).resolveRequiredValueForKey(key, isKeyPath: isKeyPath, fallbackValue: [], transform: { (array) -> [T]? in - return array.flatMap({ formatter.formatUnboxedValue($0) }) + if allowInvalidElements { + return array.flatMap({ formatter.formatUnboxedValue($0) }) + } else { + return array.map({ (value) -> T in + if let formattedValue = formatter.formatUnboxedValue(value) { + return formattedValue + } + + self.failForInvalidValue(value, forKey: key) + return T.unboxFallbackValue() + }) + } }) } - /// Unbox an optional Array containing values that can be formatted using a formatter - public func unbox(key: String, isKeyPath: Bool = true, formatter: F) -> [T]? { + /// Unbox an optional Array containing values that can be formatted using a formatter (optionally allowing invalid elements) + public func unbox(key: String, isKeyPath: Bool = true, formatter: F, allowInvalidElements: Bool = false) -> [T]? { return UnboxValueResolver<[F.UnboxRawValueType]>(self).resolveOptionalValueForKey(key, isKeyPath: isKeyPath, transform: { (array) -> [T]? in - return array.flatMap({ formatter.formatUnboxedValue($0) }) + if allowInvalidElements { + return array.flatMap({ formatter.formatUnboxedValue($0) }) + } else { + var invalidElement = false + + let mapping = array.flatMap({ (value) -> T? in + if let formattedValue = formatter.formatUnboxedValue(value) { + return formattedValue + } + + invalidElement = true + return nil + }) + + return invalidElement ? nil : mapping + } }) } diff --git a/Tests/UnboxTests.swift b/Tests/UnboxTests.swift index 52a3c9c..9de6043 100644 --- a/Tests/UnboxTests.swift +++ b/Tests/UnboxTests.swift @@ -205,16 +205,19 @@ class UnboxTests: XCTestCase { func testRequiredDateFormatting() { struct Model: Unboxable { let date: NSDate + let dateArray: [NSDate] init(unboxer: Unboxer) { let formatter = NSDateFormatter() formatter.dateFormat = "YYYY-MM-dd" self.date = unboxer.unbox("date", formatter: formatter) + self.dateArray = unboxer.unbox("dateArray", formatter: formatter) } } let dictionary: UnboxableDictionary = [ - "date" : "2015-12-15" + "date" : "2015-12-15", + "dateArray" : ["2015-12-15"] ] do { @@ -224,16 +227,38 @@ class UnboxTests: XCTestCase { XCTAssertEqual(calendar.component(.Year, fromDate: unboxed.date), 2015) XCTAssertEqual(calendar.component(.Month, fromDate: unboxed.date), 12) XCTAssertEqual(calendar.component(.Day, fromDate: unboxed.date), 15) + + if let firstDate = unboxed.dateArray.first { + XCTAssertEqual(calendar.component(.Year, fromDate: firstDate), 2015) + XCTAssertEqual(calendar.component(.Month, fromDate: firstDate), 12) + XCTAssertEqual(calendar.component(.Day, fromDate: firstDate), 15) + } else { + XCTFail("Array empty") + } + } catch { XCTFail("\(error)") } do { - let invalidDictionary: UnboxableDictionary = [ - "date" : "2015-12-tuesday" + let invalidDateDictionary: UnboxableDictionary = [ + "date" : "2015-12-tuesday", + "dateArray" : ["2015-12-15"] + ] + + try Unbox(invalidDateDictionary) as Model + XCTFail("Should have thrown") + } catch { + // Test passed + } + + do { + let invalidDateArrayDictionary: UnboxableDictionary = [ + "date" : "2015-12-15", + "dateArray" : ["2015-12-tuesday"] ] - try Unbox(invalidDictionary) as Model + try Unbox(invalidDateArrayDictionary) as Model XCTFail("Should have thrown") } catch { // Test passed @@ -243,21 +268,25 @@ class UnboxTests: XCTestCase { func testOptionalDateFormattingFailureNotThrowing() { struct Model: Unboxable { let date: NSDate? + let dateArray: [NSDate]? init(unboxer: Unboxer) { let formatter = NSDateFormatter() formatter.dateFormat = "YYYY-MM-dd" self.date = unboxer.unbox("date", formatter: formatter) + self.dateArray = unboxer.unbox("dateArray", formatter: formatter) } } do { let invalidDictionary: UnboxableDictionary = [ - "date" : "2015-12-tuesday" + "date" : "2015-12-tuesday", + "dateArray" : ["2015-12-tuesday"] ] let unboxed: Model = try Unbox(invalidDictionary) XCTAssertNil(unboxed.date) + XCTAssertNil(unboxed.dateArray) } catch { XCTFail("\(error)") } From c0e5f9e2162f28ab3465ff931a30c2902b6cb1ec Mon Sep 17 00:00:00 2001 From: Mike Bignell Date: Wed, 29 Jun 2016 13:14:00 +0100 Subject: [PATCH 3/5] Unbox array with formatter tests now cover all code paths --- Tests/UnboxTests.swift | 70 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/Tests/UnboxTests.swift b/Tests/UnboxTests.swift index 9de6043..3db5128 100644 --- a/Tests/UnboxTests.swift +++ b/Tests/UnboxTests.swift @@ -215,11 +215,25 @@ class UnboxTests: XCTestCase { } } + struct AllowInvalidElementsModel: Unboxable { + let date: NSDate + let dateArray: [NSDate] + + init(unboxer: Unboxer) { + let formatter = NSDateFormatter() + formatter.dateFormat = "YYYY-MM-dd" + self.date = unboxer.unbox("date", formatter: formatter) + self.dateArray = unboxer.unbox("dateArray", formatter: formatter, allowInvalidElements: true) + } + } + let dictionary: UnboxableDictionary = [ "date" : "2015-12-15", "dateArray" : ["2015-12-15"] ] + // Valid tests: + do { let unboxed: Model = try Unbox(dictionary) @@ -240,6 +254,28 @@ class UnboxTests: XCTestCase { XCTFail("\(error)") } + do { + let invalidValueDateArrayDictionary: UnboxableDictionary = [ + "date" : "2015-12-15", + "dateArray" : ["2015-12-tuesday", "2015-12-15"] + ] + + let unboxed: AllowInvalidElementsModel = try Unbox(invalidValueDateArrayDictionary) + + if let firstDate = unboxed.dateArray.first { + let calendar = NSCalendar.currentCalendar() + XCTAssertEqual(calendar.component(.Year, fromDate: firstDate), 2015) + XCTAssertEqual(calendar.component(.Month, fromDate: firstDate), 12) + XCTAssertEqual(calendar.component(.Day, fromDate: firstDate), 15) + } else { + XCTFail("Array empty") + } + } catch { + XCTFail("\(error)") + } + + // Invalid tests: + do { let invalidDateDictionary: UnboxableDictionary = [ "date" : "2015-12-tuesday", @@ -278,6 +314,18 @@ class UnboxTests: XCTestCase { } } + struct AllowInvalidElementsModel: Unboxable { + let date: NSDate? + let dateArray: [NSDate]? + + init(unboxer: Unboxer) { + let formatter = NSDateFormatter() + formatter.dateFormat = "YYYY-MM-dd" + self.date = unboxer.unbox("date", formatter: formatter) + self.dateArray = unboxer.unbox("dateArray", formatter: formatter, allowInvalidElements: true) + } + } + do { let invalidDictionary: UnboxableDictionary = [ "date" : "2015-12-tuesday", @@ -290,6 +338,28 @@ class UnboxTests: XCTestCase { } catch { XCTFail("\(error)") } + + do { + let invalidDictionary: UnboxableDictionary = [ + "date" : "2015-12-tuesday", + "dateArray" : ["2015-12-15", "2015-12-tuesday"] + ] + + let unboxed: AllowInvalidElementsModel = try Unbox(invalidDictionary) + XCTAssertNil(unboxed.date) + + let calendar = NSCalendar.currentCalendar() + if let firstDate = unboxed.dateArray?.first { + XCTAssertEqual(calendar.component(.Year, fromDate: firstDate), 2015) + XCTAssertEqual(calendar.component(.Month, fromDate: firstDate), 12) + XCTAssertEqual(calendar.component(.Day, fromDate: firstDate), 15) + } else { + XCTFail("Array empty") + } + + } catch { + XCTFail("\(error)") + } } func testCustomDictionaryKeyType() { From d6c338ea0c5c2e6cfe45ebc9ff61a9b5d2ba582d Mon Sep 17 00:00:00 2001 From: Mike Bignell Date: Wed, 29 Jun 2016 13:19:17 +0100 Subject: [PATCH 4/5] Unbox array with formatter tests now check to see if array contents is as expected --- Tests/UnboxTests.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Tests/UnboxTests.swift b/Tests/UnboxTests.swift index 3db5128..81c9fa8 100644 --- a/Tests/UnboxTests.swift +++ b/Tests/UnboxTests.swift @@ -262,6 +262,8 @@ class UnboxTests: XCTestCase { let unboxed: AllowInvalidElementsModel = try Unbox(invalidValueDateArrayDictionary) + XCTAssertEqual(unboxed.dateArray.count, 1) + if let firstDate = unboxed.dateArray.first { let calendar = NSCalendar.currentCalendar() XCTAssertEqual(calendar.component(.Year, fromDate: firstDate), 2015) @@ -347,6 +349,7 @@ class UnboxTests: XCTestCase { let unboxed: AllowInvalidElementsModel = try Unbox(invalidDictionary) XCTAssertNil(unboxed.date) + XCTAssertEqual(unboxed.dateArray?.count, 1) let calendar = NSCalendar.currentCalendar() if let firstDate = unboxed.dateArray?.first { From f81302646a1e5678beac3bc4a1aff1213efac854 Mon Sep 17 00:00:00 2001 From: Mike Bignell Date: Fri, 1 Jul 2016 11:13:09 +0100 Subject: [PATCH 5/5] Unbox array with formatter uses a for-in loop rather than a mapping function to return early when invalid values encountered when not allowed --- Sources/Unbox.swift | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Sources/Unbox.swift b/Sources/Unbox.swift index dc4e0a3..6f0b98d 100644 --- a/Sources/Unbox.swift +++ b/Sources/Unbox.swift @@ -716,18 +716,17 @@ public class Unboxer { if allowInvalidElements { return array.flatMap({ formatter.formatUnboxedValue($0) }) } else { - var invalidElement = false + var formattedArray = [T]() - let mapping = array.flatMap({ (value) -> T? in - if let formattedValue = formatter.formatUnboxedValue(value) { - return formattedValue + for value in array { + guard let formattedValue = formatter.formatUnboxedValue(value) else { + return nil } - invalidElement = true - return nil - }) + formattedArray.append(formattedValue) + } - return invalidElement ? nil : mapping + return formattedArray } }) }