From 05921b4dd91624263beb856c266e88a8fed55043 Mon Sep 17 00:00:00 2001 From: Nicolas Jakubowski Date: Tue, 6 Jun 2017 12:52:15 -0500 Subject: [PATCH 1/4] wip --- Sources/Dictionary+Unbox.swift | 3 + Sources/Sequence+Unbox.swift | 12 ++- Sources/UnboxWarning.swift | 42 ++++++++ Sources/UnboxWarningLogger.swift | 16 +++ Sources/Unboxer.swift | 80 +++++++++++---- Tests/UnboxTests/UnboxTests.swift | 157 ++++++++++++++++++++++++++++++ Unbox.xcodeproj/project.pbxproj | 20 ++++ 7 files changed, 308 insertions(+), 22 deletions(-) create mode 100644 Sources/UnboxWarning.swift create mode 100644 Sources/UnboxWarningLogger.swift diff --git a/Sources/Dictionary+Unbox.swift b/Sources/Dictionary+Unbox.swift index dfef766..38869ab 100644 --- a/Sources/Dictionary+Unbox.swift +++ b/Sources/Dictionary+Unbox.swift @@ -70,6 +70,9 @@ private extension Dictionary { } catch { if !allowInvalidElements { throw error + } else if let unboxError = error as? UnboxError { + let warning = UnboxWarning.invalidElement(error: unboxError) + Unboxer.warningLogger?.log(warning: warning) } } } diff --git a/Sources/Sequence+Unbox.swift b/Sources/Sequence+Unbox.swift index 69b0451..dd1e3ef 100644 --- a/Sources/Sequence+Unbox.swift +++ b/Sources/Sequence+Unbox.swift @@ -13,7 +13,17 @@ internal extension Sequence { } return self.flatMap { - return try? transform($0) + + do { + let unboxed = try transform($0) + return unboxed + } catch { + if let error = error as? UnboxError { + let warning = UnboxWarning.invalidElement(error: error) + Unboxer.warningLogger?.log(warning: warning) + } + return nil + } } } } diff --git a/Sources/UnboxWarning.swift b/Sources/UnboxWarning.swift new file mode 100644 index 0000000..26d5423 --- /dev/null +++ b/Sources/UnboxWarning.swift @@ -0,0 +1,42 @@ +// +// UnboxWarningLogger.swift +// Unbox +// +// Created by Nicolas Jakubowski on 6/6/17. +// Copyright © 2017 John Sundell. All rights reserved. +// + +import Foundation + +/// Warnings are things that went wrong during the unbox operation but that didn't break unboxing +/// These aren't thrown, instead they are sent to a logger where you'll be able to keep record of them and research why you're getting this +public enum UnboxWarning { + + /// An invalid element was found in an array + case invalidElement(error: UnboxError) + + /// Failed to unbox an optional key + case optionalKeyFailedToUnbox(error: UnboxError) +} + +internal extension UnboxError { + + /// Defines whether the receiver can be threated as a warning for the `optionalKeyFailedToUnbox` case + /// We only want to generate a warning if the key is not missing + var isOptionalKeyFailedtoUnboxWarning: Bool { + switch self { + case .customUnboxingFailed, .invalidData: + return true + case .pathError(let error, _): + + switch error { + case .emptyKeyPath, .missingKey: + return false + case .invalidArrayElement, .invalidCollectionElementType, .invalidDictionaryKey, .invalidDictionaryKeyType, .invalidDictionaryValue, .invalidValue: + return true + } + + } + } + +} diff --git a/Sources/UnboxWarningLogger.swift b/Sources/UnboxWarningLogger.swift new file mode 100644 index 0000000..145d443 --- /dev/null +++ b/Sources/UnboxWarningLogger.swift @@ -0,0 +1,16 @@ +// +// UnboxWarningLogger.swift +// Unbox +// +// Created by Nicolas Jakubowski on 6/6/17. +// Copyright © 2017 John Sundell. All rights reserved. +// + +import Foundation + +/// Takes care of dealing with warnings +public protocol UnboxWarningLogger { + + /// Called whenever a warning is found when Unboxing + func log(warning: UnboxWarning) +} diff --git a/Sources/Unboxer.swift b/Sources/Unboxer.swift index 9e920f1..13a17f7 100644 --- a/Sources/Unboxer.swift +++ b/Sources/Unboxer.swift @@ -15,6 +15,12 @@ import Foundation * - and the correct type will be returned. If a required (non-optional) value couldn't be unboxed `UnboxError` will be thrown. */ public final class Unboxer { + + /// Takes care of logging warnings found during the unbox. + /// Warnings are not fatal as errors, they just indicate that something is wrong. + /// For instance, if we fail to unbox an optional key, we'll move forward but you'll receive a warning as this key was present but couldn't be parsed meaning that some data was lost. + public static var warningLogger: UnboxWarningLogger? + /// The underlying JSON dictionary that is being unboxed public let dictionary: UnboxableDictionary @@ -53,83 +59,88 @@ public final class Unboxer { /// Unbox a required value by key public func unbox(key: String) throws -> T { - return try self.unbox(path: .key(key), transform: T.unbox) + return try self.unbox(path: .key(key), transform: T.unbox, unboxedValueIsOptional: false) } /// Unbox a required collection by key public func unbox(key: String, allowInvalidElements: Bool) throws -> T { let transform = T.makeTransform(allowInvalidElements: allowInvalidElements) - return try self.unbox(path: .key(key), transform: transform) + return try self.unbox(path: .key(key), transform: transform, unboxedValueIsOptional: false) } /// Unbox a required Unboxable type by key public func unbox(key: String) throws -> T { - return try self.unbox(path: .key(key), transform: T.makeTransform()) + return try self.unbox(path: .key(key), transform: T.makeTransform(), unboxedValueIsOptional: false) } /// Unbox a required UnboxableWithContext type by key public func unbox(key: String, context: T.UnboxContext) throws -> T { - return try self.unbox(path: .key(key), transform: T.makeTransform(context: context)) + return try self.unbox(path: .key(key), transform: T.makeTransform(context: context), unboxedValueIsOptional: false) } /// Unbox a required collection of UnboxableWithContext values by key public func unbox(key: String, context: V.UnboxContext, allowInvalidElements: Bool = false) throws -> C where C.UnboxValue == V { - return try self.unbox(path: .key(key), transform: V.makeCollectionTransform(context: context, allowInvalidElements: allowInvalidElements)) + return try self.unbox(path: .key(key), transform: V.makeCollectionTransform(context: context, allowInvalidElements: allowInvalidElements), unboxedValueIsOptional: false) } /// Unbox a required value using a formatter by key public func unbox(key: String, formatter: F) throws -> F.UnboxFormattedType { - return try self.unbox(path: .key(key), transform: formatter.makeTransform()) + return try self.unbox(path: .key(key), transform: formatter.makeTransform(), unboxedValueIsOptional: false) } /// Unbox a required collection of values using a formatter by key public func unbox(key: String, formatter: F, allowInvalidElements: Bool = false) throws -> C where C.UnboxValue == F.UnboxFormattedType { - return try self.unbox(path: .key(key), transform: formatter.makeCollectionTransform(allowInvalidElements: allowInvalidElements)) + return try self.unbox(path: .key(key), transform: formatter.makeCollectionTransform(allowInvalidElements: allowInvalidElements), unboxedValueIsOptional: false) } // MARK: - Unboxing required values (by key path) /// Unbox a required value by key path public func unbox(keyPath: String) throws -> T { - return try self.unbox(path: .keyPath(keyPath), transform: T.unbox) + return try self.unbox(path: .keyPath(keyPath), transform: T.unbox, unboxedValueIsOptional: true) } /// Unbox a required collection by key path public func unbox(keyPath: String, allowInvalidElements: Bool) throws -> T where T: Collection { let transform = T.makeTransform(allowInvalidElements: allowInvalidElements) - return try self.unbox(path: .keyPath(keyPath), transform: transform) + return try self.unbox(path: .keyPath(keyPath), transform: transform, unboxedValueIsOptional: true) } /// Unbox a required Unboxable by key path public func unbox(keyPath: String) throws -> T { - return try self.unbox(path: .keyPath(keyPath), transform: T.makeTransform()) + return try self.unbox(path: .keyPath(keyPath), transform: T.makeTransform(), unboxedValueIsOptional: true) } /// Unbox a required UnboxableWithContext type by key path public func unbox(keyPath: String, context: T.UnboxContext) throws -> T { - return try self.unbox(path: .keyPath(keyPath), transform: T.makeTransform(context: context)) + return try self.unbox(path: .keyPath(keyPath), transform: T.makeTransform(context: context), unboxedValueIsOptional: true) } /// Unbox a required collection of UnboxableWithContext values by key path public func unbox(keyPath: String, context: V.UnboxContext, allowInvalidElements: Bool = false) throws -> C where C.UnboxValue == V { - return try self.unbox(path: .keyPath(keyPath), transform: V.makeCollectionTransform(context: context, allowInvalidElements: allowInvalidElements)) + return try self.unbox(path: .keyPath(keyPath), transform: V.makeCollectionTransform(context: context, allowInvalidElements: allowInvalidElements), unboxedValueIsOptional: true) } /// Unbox a required value using a formatter by key path public func unbox(keyPath: String, formatter: F) throws -> F.UnboxFormattedType { - return try self.unbox(path: .keyPath(keyPath), transform: formatter.makeTransform()) + return try self.unbox(path: .keyPath(keyPath), transform: formatter.makeTransform(), unboxedValueIsOptional: true) } /// Unbox a required collection of values using a formatter by key path public func unbox(keyPath: String, formatter: F, allowInvalidElements: Bool = false) throws -> C where C.UnboxValue == F.UnboxFormattedType { - return try self.unbox(path: .keyPath(keyPath), transform: formatter.makeCollectionTransform(allowInvalidElements: allowInvalidElements)) + return try self.unbox(path: .keyPath(keyPath), transform: formatter.makeCollectionTransform(allowInvalidElements: allowInvalidElements), unboxedValueIsOptional: true) } // MARK: - Unboxing optional values (by key) /// Unbox an optional value by key public func unbox(key: String) -> T? { - return try? self.unbox(key: key) + do { + return try unbox(key: key) as T + } catch { + (error as? UnboxError)?.logAsWarningForOptionalValueIfNeeded() + return nil + } } /// Unbox an optional collection by key @@ -149,7 +160,7 @@ public final class Unboxer { /// Unbox an optional collection of UnboxableWithContext values by key public func unbox(key: String, context: V.UnboxContext, allowInvalidElements: Bool = false) -> C? where C.UnboxValue == V { - return try? self.unbox(path: .key(key), transform: V.makeCollectionTransform(context: context, allowInvalidElements: allowInvalidElements)) + return try? self.unbox(path: .key(key), transform: V.makeCollectionTransform(context: context, allowInvalidElements: allowInvalidElements), unboxedValueIsOptional: true) } /// Unbox an optional value using a formatter by key @@ -186,7 +197,7 @@ public final class Unboxer { /// Unbox an optional collection of UnboxableWithContext values by key path public func unbox(keyPath: String, context: V.UnboxContext, allowInvalidElements: Bool = false) -> C? where C.UnboxValue == V { - return try? self.unbox(path: .keyPath(keyPath), transform: V.makeCollectionTransform(context: context, allowInvalidElements: allowInvalidElements)) + return try? self.unbox(path: .keyPath(keyPath), transform: V.makeCollectionTransform(context: context, allowInvalidElements: allowInvalidElements), unboxedValueIsOptional: true) } /// Unbox an optional value using a formatter by key path @@ -215,7 +226,7 @@ internal extension Unboxer { // MARK: - Private private extension Unboxer { - func unbox(path: UnboxPath, transform: UnboxTransform) throws -> R { + func unbox(path: UnboxPath, transform: UnboxTransform, unboxedValueIsOptional isOptional: Bool) throws -> R { do { switch path { case .key(let key): @@ -245,13 +256,25 @@ private extension Unboxer { throw UnboxPathError.emptyKeyPath } } catch { + var processedError: UnboxError? = nil + if let publicError = error as? UnboxError { - throw publicError + processedError = publicError } else if let pathError = error as? UnboxPathError { - throw UnboxError.pathError(pathError, path.description) + processedError = UnboxError.pathError(pathError, path.description) + } + + // log a warning only if we have a processed error + if let processedError = processedError, + isOptional, + processedError.isOptionalKeyFailedtoUnboxWarning { + + let warning = UnboxWarning.optionalKeyFailedToUnbox(error: processedError) + Unboxer.warningLogger?.log(warning: warning) + } - throw error + throw processedError ?? error } } @@ -259,3 +282,18 @@ private extension Unboxer { return try closure(self).orThrow(UnboxError.customUnboxingFailed) } } + +private extension UnboxError { + + /// When unboxing an optional value, an error is generated + /// If the key was present in the dictionary we want to log this error as a warning + func logAsWarningForOptionalValueIfNeeded() { + guard isOptionalKeyFailedtoUnboxWarning else { + return + } + + let warning = UnboxWarning.optionalKeyFailedToUnbox(error: self) + Unboxer.warningLogger?.log(warning: warning) + } + +} diff --git a/Tests/UnboxTests/UnboxTests.swift b/Tests/UnboxTests/UnboxTests.swift index 0d0e97f..4c08a3f 100644 --- a/Tests/UnboxTests/UnboxTests.swift +++ b/Tests/UnboxTests/UnboxTests.swift @@ -1744,6 +1744,154 @@ class UnboxTests: XCTestCase { XCTFail("\(error)") } } + + func testWarningsAreLoggedForInvalidElementsInArray() { + + let logger = UnboxWarningLoggerMock() + Unboxer.warningLogger = logger + + struct Model: Unboxable { + let string: String + + init(unboxer: Unboxer) throws { + self.string = try unboxer.unbox(key: "string") + } + } + + let dictionaries: [UnboxableDictionary] = [ + ["string" : "one"], + ["invalid" : "element"], + ["string" : "two"] + ] + + do { + let unboxed: [Model] = try unbox(dictionaries: dictionaries, allowInvalidElements: true) + XCTAssertEqual(unboxed.first?.string, "one") + XCTAssertEqual(unboxed.last?.string, "two") + } catch { + XCTFail("\(error)") + } + + XCTAssertEqual(logger.receivedWarnings.count, 1, "Expected only one warning call") + + guard let warning = logger.receivedWarnings.first else { + XCTFail("Expected to find a warning but there wasn't any") + return + } + + switch warning { + case .invalidElement: + break + default: + XCTFail("Expected logged warning to be .invalidElement but instead got \(warning)") + } + } + + func testWarningsareLoggedForInvalidElementsInNestedArrayOfDictionaries() { + + let logger = UnboxWarningLoggerMock() + Unboxer.warningLogger = logger + + struct Model: Unboxable { + let nestedModels: [NestedModel] + + init(unboxer: Unboxer) throws { + self.nestedModels = try unboxer.unbox(key: "nested", allowInvalidElements: true) + } + } + + struct NestedModel: Unboxable { + let string: String + + init(unboxer: Unboxer) throws { + self.string = try unboxer.unbox(key: "string") + } + } + + let dictionary: UnboxableDictionary = [ + "nested" : [ + ["string" : "one"], + ["invalid" : "element"], + ["string" : "two"] + ] + ] + + do { + let unboxed: Model = try unbox(dictionary: dictionary) + XCTAssertEqual(unboxed.nestedModels.first?.string, "one") + XCTAssertEqual(unboxed.nestedModels.last?.string, "two") + } catch { + XCTFail("\(error)") + } + + XCTAssertEqual(logger.receivedWarnings.count, 1, "Expected only one warning call") + + guard let warning = logger.receivedWarnings.first else { + XCTFail("Expected to find a warning but there wasn't any") + return + } + + switch warning { + case .invalidElement: + break + default: + XCTFail("Expected logged warning to be .invalidElement but instead got \(warning)") + } + } + + func testWarningsAreLoggedForInvalidElementsInNestedDictionary() { + + let logger = UnboxWarningLoggerMock() + Unboxer.warningLogger = logger + + struct Model: Unboxable { + let nestedModels: [String : NestedModel] + + init(unboxer: Unboxer) throws { + self.nestedModels = try unboxer.unbox(key: "nested", allowInvalidElements: true) + } + } + + struct NestedModel: Unboxable { + let string: String + + init(unboxer: Unboxer) throws { + self.string = try unboxer.unbox(key: "string") + } + } + + let dictionary: UnboxableDictionary = [ + "nested" : [ + "one" : ["string" : "one"], + "two" : ["invalid" : "element"], + "three" : ["string" : "two"] + ] + ] + + do { + let unboxed: Model = try unbox(dictionary: dictionary) + XCTAssertEqual(unboxed.nestedModels.count, 2) + XCTAssertEqual(unboxed.nestedModels["one"]?.string, "one") + XCTAssertEqual(unboxed.nestedModels["three"]?.string, "two") + } catch { + XCTFail("\(error)") + } + + XCTAssertEqual(logger.receivedWarnings.count, 1, "Expected only one warning call") + + guard let warning = logger.receivedWarnings.first else { + XCTFail("Expected to find a warning but there wasn't any") + return + } + + switch warning { + case .invalidElement: + break + default: + XCTFail("Expected logged warning to be .invalidElement but instead got \(warning)") + } + } + } private func UnboxTestDictionaryWithAllRequiredKeysWithValidValues(nested: Bool) -> UnboxableDictionary { @@ -2024,6 +2172,15 @@ private final class UnboxTestContextMock: UnboxableWithContext { } } +private final class UnboxWarningLoggerMock: UnboxWarningLogger { + + private(set) var receivedWarnings: [UnboxWarning] = [] + func log(warning: UnboxWarning) { + receivedWarnings.append(warning) + } + +} + private struct UnboxTestSimpleMock: Unboxable, Equatable { let int: Int diff --git a/Unbox.xcodeproj/project.pbxproj b/Unbox.xcodeproj/project.pbxproj index f9a425f..414deff 100644 --- a/Unbox.xcodeproj/project.pbxproj +++ b/Unbox.xcodeproj/project.pbxproj @@ -181,6 +181,14 @@ 52D6D9871BEFF229002C0205 /* Unbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6D97C1BEFF229002C0205 /* Unbox.framework */; }; DD7502881C68FEDE006590AF /* Unbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6DA0F1BF000BD002C0205 /* Unbox.framework */; }; DD7502921C690C7A006590AF /* Unbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6D9F01BEFFFBE002C0205 /* Unbox.framework */; }; + FE63CED71EE70829000239C9 /* UnboxWarning.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE63CED61EE70829000239C9 /* UnboxWarning.swift */; }; + FE63CED91EE709DF000239C9 /* UnboxWarningLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE63CED81EE709DF000239C9 /* UnboxWarningLogger.swift */; }; + FE63CEDA1EE709DF000239C9 /* UnboxWarningLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE63CED81EE709DF000239C9 /* UnboxWarningLogger.swift */; }; + FE63CEDB1EE709DF000239C9 /* UnboxWarningLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE63CED81EE709DF000239C9 /* UnboxWarningLogger.swift */; }; + FE63CEDC1EE709DF000239C9 /* UnboxWarningLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE63CED81EE709DF000239C9 /* UnboxWarningLogger.swift */; }; + FE63CEDD1EE709F0000239C9 /* UnboxWarning.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE63CED61EE70829000239C9 /* UnboxWarning.swift */; }; + FE63CEDE1EE709F0000239C9 /* UnboxWarning.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE63CED61EE70829000239C9 /* UnboxWarning.swift */; }; + FE63CEDF1EE709F1000239C9 /* UnboxWarning.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE63CED61EE70829000239C9 /* UnboxWarning.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -260,6 +268,8 @@ AD2FAA281CD0B6E100659CF4 /* UnboxTests.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = UnboxTests.plist; sourceTree = ""; }; DD75027A1C68FCFC006590AF /* Unbox-macOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Unbox-macOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; DD75028D1C690C7A006590AF /* Unbox-tvOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Unbox-tvOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + FE63CED61EE70829000239C9 /* UnboxWarning.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnboxWarning.swift; sourceTree = ""; }; + FE63CED81EE709DF000239C9 /* UnboxWarningLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnboxWarningLogger.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -363,6 +373,8 @@ 524F1E041E89B0A3000AC6FE /* UnboxPathError.swift */, 524F1E9F1E89BF6C000AC6FE /* UnboxPathNode.swift */, 524F1E901E89BDFC000AC6FE /* URL+Unbox.swift */, + FE63CED61EE70829000239C9 /* UnboxWarning.swift */, + FE63CED81EE709DF000239C9 /* UnboxWarningLogger.swift */, ); path = Sources; sourceTree = ""; @@ -736,7 +748,9 @@ 524F1E5F1E89BA9E000AC6FE /* UInt64+Unbox.swift in Sources */, 524F1EBE1E89C1C6000AC6FE /* Data+Unbox.swift in Sources */, 524F1E371E89B42E000AC6FE /* UnboxFormatter.swift in Sources */, + FE63CED71EE70829000239C9 /* UnboxWarning.swift in Sources */, 524F1E501E89BA26000AC6FE /* Int32+Unbox.swift in Sources */, + FE63CED91EE709DF000239C9 /* UnboxWarningLogger.swift in Sources */, 524F1ECD1E89C466000AC6FE /* UnboxArrayContainer.swift in Sources */, 524F1EB91E89C16B000AC6FE /* JSONSerialization+Unbox.swift in Sources */, 524F1EAA1E89C01B000AC6FE /* NSArray+Unbox.swift in Sources */, @@ -766,12 +780,14 @@ 524F1E891E89BD1C000AC6FE /* CGFloat+Unbox.swift in Sources */, 524F1E341E89B409000AC6FE /* UnboxableByTransform.swift in Sources */, 524F1E5C1E89BA78000AC6FE /* UInt32+Unbox.swift in Sources */, + FE63CEDB1EE709DF000239C9 /* UnboxWarningLogger.swift in Sources */, 524F1ECA1E89C41C000AC6FE /* UnboxContainer.swift in Sources */, 524F1E841E89BC99000AC6FE /* Dictionary+Unbox.swift in Sources */, 524F1E201E89B345000AC6FE /* UnboxableCollection.swift in Sources */, 524F1E931E89BDFC000AC6FE /* URL+Unbox.swift in Sources */, 524F1E6B1E89BAE7000AC6FE /* Float+Unbox.swift in Sources */, 524F1E3E1E89B884000AC6FE /* Optional+Unbox.swift in Sources */, + FE63CEDE1EE709F0000239C9 /* UnboxWarning.swift in Sources */, 524F1E1B1E89B2A9000AC6FE /* UnboxableRawType.swift in Sources */, 524F1E111E89B168000AC6FE /* UnboxableWithContext.swift in Sources */, 524F1E251E89B381000AC6FE /* UnboxCollectionElementTransformer.swift in Sources */, @@ -815,12 +831,14 @@ 524F1E8A1E89BD1C000AC6FE /* CGFloat+Unbox.swift in Sources */, 524F1E351E89B409000AC6FE /* UnboxableByTransform.swift in Sources */, 524F1E5D1E89BA78000AC6FE /* UInt32+Unbox.swift in Sources */, + FE63CEDC1EE709DF000239C9 /* UnboxWarningLogger.swift in Sources */, 524F1ECB1E89C41C000AC6FE /* UnboxContainer.swift in Sources */, 524F1E851E89BC99000AC6FE /* Dictionary+Unbox.swift in Sources */, 524F1E211E89B345000AC6FE /* UnboxableCollection.swift in Sources */, 524F1E941E89BDFC000AC6FE /* URL+Unbox.swift in Sources */, 524F1E6C1E89BAE7000AC6FE /* Float+Unbox.swift in Sources */, 524F1E3F1E89B884000AC6FE /* Optional+Unbox.swift in Sources */, + FE63CEDF1EE709F1000239C9 /* UnboxWarning.swift in Sources */, 524F1E1C1E89B2A9000AC6FE /* UnboxableRawType.swift in Sources */, 524F1E121E89B168000AC6FE /* UnboxableWithContext.swift in Sources */, 524F1E261E89B381000AC6FE /* UnboxCollectionElementTransformer.swift in Sources */, @@ -864,12 +882,14 @@ 524F1E881E89BD1C000AC6FE /* CGFloat+Unbox.swift in Sources */, 524F1E331E89B409000AC6FE /* UnboxableByTransform.swift in Sources */, 524F1E5B1E89BA78000AC6FE /* UInt32+Unbox.swift in Sources */, + FE63CEDA1EE709DF000239C9 /* UnboxWarningLogger.swift in Sources */, 524F1EC91E89C41C000AC6FE /* UnboxContainer.swift in Sources */, 524F1E831E89BC99000AC6FE /* Dictionary+Unbox.swift in Sources */, 524F1E1F1E89B345000AC6FE /* UnboxableCollection.swift in Sources */, 524F1E921E89BDFC000AC6FE /* URL+Unbox.swift in Sources */, 524F1E6A1E89BAE7000AC6FE /* Float+Unbox.swift in Sources */, 524F1E3D1E89B884000AC6FE /* Optional+Unbox.swift in Sources */, + FE63CEDD1EE709F0000239C9 /* UnboxWarning.swift in Sources */, 524F1E1A1E89B2A9000AC6FE /* UnboxableRawType.swift in Sources */, 524F1E101E89B168000AC6FE /* UnboxableWithContext.swift in Sources */, 524F1E241E89B381000AC6FE /* UnboxCollectionElementTransformer.swift in Sources */, From 0568c7862706f27a5b29197403703b4faeaeaa3e Mon Sep 17 00:00:00 2001 From: Nicolas Jakubowski Date: Tue, 6 Jun 2017 13:05:05 -0500 Subject: [PATCH 2/4] reverted file --- Sources/Unboxer.swift | 172 ++++++++++++++++-------------------------- 1 file changed, 67 insertions(+), 105 deletions(-) diff --git a/Sources/Unboxer.swift b/Sources/Unboxer.swift index 13a17f7..1a9b021 100644 --- a/Sources/Unboxer.swift +++ b/Sources/Unboxer.swift @@ -15,196 +15,185 @@ import Foundation * - and the correct type will be returned. If a required (non-optional) value couldn't be unboxed `UnboxError` will be thrown. */ public final class Unboxer { - - /// Takes care of logging warnings found during the unbox. - /// Warnings are not fatal as errors, they just indicate that something is wrong. - /// For instance, if we fail to unbox an optional key, we'll move forward but you'll receive a warning as this key was present but couldn't be parsed meaning that some data was lost. - public static var warningLogger: UnboxWarningLogger? - /// The underlying JSON dictionary that is being unboxed public let dictionary: UnboxableDictionary - + // MARK: - Initializer - + /// Initialize an instance with a dictionary that can then be decoded using the `unbox()` methods. public init(dictionary: UnboxableDictionary) { self.dictionary = dictionary } - + /// Initialize an instance with binary data than can then be decoded using the `unbox()` methods. Throws `UnboxError` for invalid data. public init(data: Data) throws { self.dictionary = try JSONSerialization.unbox(data: data) } - + // MARK: - Custom unboxing API - + /// Perform custom unboxing using an Unboxer (created from a dictionary) passed to a closure, or throw an UnboxError public static func performCustomUnboxing(dictionary: UnboxableDictionary, closure: (Unboxer) throws -> T?) throws -> T { return try Unboxer(dictionary: dictionary).performCustomUnboxing(closure: closure) } - + /// Perform custom unboxing on an array of dictionaries, executing a closure with a new Unboxer for each one, or throw an UnboxError public static func performCustomUnboxing(array: [UnboxableDictionary], allowInvalidElements: Bool = false, closure: (Unboxer) throws -> T?) throws -> [T] { return try array.map(allowInvalidElements: allowInvalidElements) { try Unboxer(dictionary: $0).performCustomUnboxing(closure: closure) } } - + /// Perform custom unboxing using an Unboxer (created from binary data) passed to a closure, or throw an UnboxError public static func performCustomUnboxing(data: Data, closure: @escaping (Unboxer) throws -> T?) throws -> T { return try data.unbox(closure: closure) } - + // MARK: - Unboxing required values (by key) - + /// Unbox a required value by key public func unbox(key: String) throws -> T { - return try self.unbox(path: .key(key), transform: T.unbox, unboxedValueIsOptional: false) + return try self.unbox(path: .key(key), transform: T.unbox) } - + /// Unbox a required collection by key public func unbox(key: String, allowInvalidElements: Bool) throws -> T { let transform = T.makeTransform(allowInvalidElements: allowInvalidElements) - return try self.unbox(path: .key(key), transform: transform, unboxedValueIsOptional: false) + return try self.unbox(path: .key(key), transform: transform) } - + /// Unbox a required Unboxable type by key public func unbox(key: String) throws -> T { - return try self.unbox(path: .key(key), transform: T.makeTransform(), unboxedValueIsOptional: false) + return try self.unbox(path: .key(key), transform: T.makeTransform()) } - + /// Unbox a required UnboxableWithContext type by key public func unbox(key: String, context: T.UnboxContext) throws -> T { - return try self.unbox(path: .key(key), transform: T.makeTransform(context: context), unboxedValueIsOptional: false) + return try self.unbox(path: .key(key), transform: T.makeTransform(context: context)) } - + /// Unbox a required collection of UnboxableWithContext values by key public func unbox(key: String, context: V.UnboxContext, allowInvalidElements: Bool = false) throws -> C where C.UnboxValue == V { - return try self.unbox(path: .key(key), transform: V.makeCollectionTransform(context: context, allowInvalidElements: allowInvalidElements), unboxedValueIsOptional: false) + return try self.unbox(path: .key(key), transform: V.makeCollectionTransform(context: context, allowInvalidElements: allowInvalidElements)) } - + /// Unbox a required value using a formatter by key public func unbox(key: String, formatter: F) throws -> F.UnboxFormattedType { - return try self.unbox(path: .key(key), transform: formatter.makeTransform(), unboxedValueIsOptional: false) + return try self.unbox(path: .key(key), transform: formatter.makeTransform()) } - + /// Unbox a required collection of values using a formatter by key public func unbox(key: String, formatter: F, allowInvalidElements: Bool = false) throws -> C where C.UnboxValue == F.UnboxFormattedType { - return try self.unbox(path: .key(key), transform: formatter.makeCollectionTransform(allowInvalidElements: allowInvalidElements), unboxedValueIsOptional: false) + return try self.unbox(path: .key(key), transform: formatter.makeCollectionTransform(allowInvalidElements: allowInvalidElements)) } - + // MARK: - Unboxing required values (by key path) - + /// Unbox a required value by key path public func unbox(keyPath: String) throws -> T { - return try self.unbox(path: .keyPath(keyPath), transform: T.unbox, unboxedValueIsOptional: true) + return try self.unbox(path: .keyPath(keyPath), transform: T.unbox) } - + /// Unbox a required collection by key path public func unbox(keyPath: String, allowInvalidElements: Bool) throws -> T where T: Collection { let transform = T.makeTransform(allowInvalidElements: allowInvalidElements) - return try self.unbox(path: .keyPath(keyPath), transform: transform, unboxedValueIsOptional: true) + return try self.unbox(path: .keyPath(keyPath), transform: transform) } - + /// Unbox a required Unboxable by key path public func unbox(keyPath: String) throws -> T { - return try self.unbox(path: .keyPath(keyPath), transform: T.makeTransform(), unboxedValueIsOptional: true) + return try self.unbox(path: .keyPath(keyPath), transform: T.makeTransform()) } - + /// Unbox a required UnboxableWithContext type by key path public func unbox(keyPath: String, context: T.UnboxContext) throws -> T { - return try self.unbox(path: .keyPath(keyPath), transform: T.makeTransform(context: context), unboxedValueIsOptional: true) + return try self.unbox(path: .keyPath(keyPath), transform: T.makeTransform(context: context)) } - + /// Unbox a required collection of UnboxableWithContext values by key path public func unbox(keyPath: String, context: V.UnboxContext, allowInvalidElements: Bool = false) throws -> C where C.UnboxValue == V { - return try self.unbox(path: .keyPath(keyPath), transform: V.makeCollectionTransform(context: context, allowInvalidElements: allowInvalidElements), unboxedValueIsOptional: true) + return try self.unbox(path: .keyPath(keyPath), transform: V.makeCollectionTransform(context: context, allowInvalidElements: allowInvalidElements)) } - + /// Unbox a required value using a formatter by key path public func unbox(keyPath: String, formatter: F) throws -> F.UnboxFormattedType { - return try self.unbox(path: .keyPath(keyPath), transform: formatter.makeTransform(), unboxedValueIsOptional: true) + return try self.unbox(path: .keyPath(keyPath), transform: formatter.makeTransform()) } - + /// Unbox a required collection of values using a formatter by key path public func unbox(keyPath: String, formatter: F, allowInvalidElements: Bool = false) throws -> C where C.UnboxValue == F.UnboxFormattedType { - return try self.unbox(path: .keyPath(keyPath), transform: formatter.makeCollectionTransform(allowInvalidElements: allowInvalidElements), unboxedValueIsOptional: true) + return try self.unbox(path: .keyPath(keyPath), transform: formatter.makeCollectionTransform(allowInvalidElements: allowInvalidElements)) } - + // MARK: - Unboxing optional values (by key) - + /// Unbox an optional value by key public func unbox(key: String) -> T? { - do { - return try unbox(key: key) as T - } catch { - (error as? UnboxError)?.logAsWarningForOptionalValueIfNeeded() - return nil - } + return try? self.unbox(key: key) } - + /// Unbox an optional collection by key public func unbox(key: String, allowInvalidElements: Bool) -> T? { return try? self.unbox(key: key, allowInvalidElements: allowInvalidElements) } - + /// Unbox an optional Unboxable type by key public func unbox(key: String) -> T? { return try? self.unbox(key: key) } - + /// Unbox an optional UnboxableWithContext type by key public func unbox(key: String, context: T.UnboxContext) -> T? { return try? self.unbox(key: key, context: context) } - + /// Unbox an optional collection of UnboxableWithContext values by key public func unbox(key: String, context: V.UnboxContext, allowInvalidElements: Bool = false) -> C? where C.UnboxValue == V { - return try? self.unbox(path: .key(key), transform: V.makeCollectionTransform(context: context, allowInvalidElements: allowInvalidElements), unboxedValueIsOptional: true) + return try? self.unbox(path: .key(key), transform: V.makeCollectionTransform(context: context, allowInvalidElements: allowInvalidElements)) } - + /// Unbox an optional value using a formatter by key public func unbox(key: String, formatter: F) -> F.UnboxFormattedType? { return try? self.unbox(key: key, formatter: formatter) } - + /// Unbox an optional collection of values using a formatter by key public func unbox(key: String, formatter: F, allowInvalidElements: Bool = false) -> C? where C.UnboxValue == F.UnboxFormattedType { return try? self.unbox(key: key, formatter: formatter, allowInvalidElements: allowInvalidElements) } - + // MARK: - Unboxing optional values (by key path) - + /// Unbox an optional value by key path public func unbox(keyPath: String) -> T? { return try? self.unbox(keyPath: keyPath) } - + /// Unbox an optional collection by key path public func unbox(keyPath: String, allowInvalidElements: Bool) -> T? { return try? self.unbox(keyPath: keyPath, allowInvalidElements: allowInvalidElements) } - + /// Unbox an optional Unboxable type by key path public func unbox(keyPath: String) -> T? { return try? self.unbox(keyPath: keyPath) } - + /// Unbox an optional UnboxableWithContext type by key path public func unbox(keyPath: String, context: T.UnboxContext) -> T? { return try? self.unbox(keyPath: keyPath, context: context) } - + /// Unbox an optional collection of UnboxableWithContext values by key path public func unbox(keyPath: String, context: V.UnboxContext, allowInvalidElements: Bool = false) -> C? where C.UnboxValue == V { - return try? self.unbox(path: .keyPath(keyPath), transform: V.makeCollectionTransform(context: context, allowInvalidElements: allowInvalidElements), unboxedValueIsOptional: true) + return try? self.unbox(path: .keyPath(keyPath), transform: V.makeCollectionTransform(context: context, allowInvalidElements: allowInvalidElements)) } - + /// Unbox an optional value using a formatter by key path public func unbox(keyPath: String, formatter: F) -> F.UnboxFormattedType? { return try? self.unbox(keyPath: keyPath, formatter: formatter) } - + /// Unbox an optional collection of values using a formatter by key path public func unbox(keyPath: String, formatter: F, allowInvalidElements: Bool = false) -> C? where C.UnboxValue == F.UnboxFormattedType { return try? self.unbox(keyPath: keyPath, formatter: formatter, allowInvalidElements: allowInvalidElements) @@ -217,7 +206,7 @@ internal extension Unboxer { func performUnboxing() throws -> T { return try T(unboxer: self) } - + func performUnboxing(context: T.UnboxContext) throws -> T { return try T(unboxer: self, context: context) } @@ -226,7 +215,7 @@ internal extension Unboxer { // MARK: - Private private extension Unboxer { - func unbox(path: UnboxPath, transform: UnboxTransform, unboxedValueIsOptional isOptional: Bool) throws -> R { + func unbox(path: UnboxPath, transform: UnboxTransform) throws -> R { do { switch path { case .key(let key): @@ -236,64 +225,37 @@ private extension Unboxer { var node: UnboxPathNode = self.dictionary let components = keyPath.components(separatedBy: ".") let lastKey = components.last - + for key in components { guard let nextValue = node.unboxPathValue(forKey: key) else { throw UnboxPathError.missingKey(key) } - + if key == lastKey { return try transform(nextValue).orThrow(UnboxPathError.invalidValue(nextValue, key)) } - + guard let nextNode = nextValue as? UnboxPathNode else { throw UnboxPathError.invalidValue(nextValue, key) } - + node = nextNode } - + throw UnboxPathError.emptyKeyPath } } catch { - var processedError: UnboxError? = nil - if let publicError = error as? UnboxError { - processedError = publicError + throw publicError } else if let pathError = error as? UnboxPathError { - processedError = UnboxError.pathError(pathError, path.description) + throw UnboxError.pathError(pathError, path.description) } - // log a warning only if we have a processed error - if let processedError = processedError, - isOptional, - processedError.isOptionalKeyFailedtoUnboxWarning { - - let warning = UnboxWarning.optionalKeyFailedToUnbox(error: processedError) - Unboxer.warningLogger?.log(warning: warning) - - } - - throw processedError ?? error + throw error } } - + func performCustomUnboxing(closure: (Unboxer) throws -> T?) throws -> T { return try closure(self).orThrow(UnboxError.customUnboxingFailed) } } - -private extension UnboxError { - - /// When unboxing an optional value, an error is generated - /// If the key was present in the dictionary we want to log this error as a warning - func logAsWarningForOptionalValueIfNeeded() { - guard isOptionalKeyFailedtoUnboxWarning else { - return - } - - let warning = UnboxWarning.optionalKeyFailedToUnbox(error: self) - Unboxer.warningLogger?.log(warning: warning) - } - -} From 7f390ddf999646b2af048bddbdba81ce7135105c Mon Sep 17 00:00:00 2001 From: Nicolas Jakubowski Date: Tue, 6 Jun 2017 13:09:00 -0500 Subject: [PATCH 3/4] reverted file to upstream --- Sources/Unboxer.swift | 94 +++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/Sources/Unboxer.swift b/Sources/Unboxer.swift index 1a9b021..9e920f1 100644 --- a/Sources/Unboxer.swift +++ b/Sources/Unboxer.swift @@ -17,183 +17,183 @@ import Foundation public final class Unboxer { /// The underlying JSON dictionary that is being unboxed public let dictionary: UnboxableDictionary - + // MARK: - Initializer - + /// Initialize an instance with a dictionary that can then be decoded using the `unbox()` methods. public init(dictionary: UnboxableDictionary) { self.dictionary = dictionary } - + /// Initialize an instance with binary data than can then be decoded using the `unbox()` methods. Throws `UnboxError` for invalid data. public init(data: Data) throws { self.dictionary = try JSONSerialization.unbox(data: data) } - + // MARK: - Custom unboxing API - + /// Perform custom unboxing using an Unboxer (created from a dictionary) passed to a closure, or throw an UnboxError public static func performCustomUnboxing(dictionary: UnboxableDictionary, closure: (Unboxer) throws -> T?) throws -> T { return try Unboxer(dictionary: dictionary).performCustomUnboxing(closure: closure) } - + /// Perform custom unboxing on an array of dictionaries, executing a closure with a new Unboxer for each one, or throw an UnboxError public static func performCustomUnboxing(array: [UnboxableDictionary], allowInvalidElements: Bool = false, closure: (Unboxer) throws -> T?) throws -> [T] { return try array.map(allowInvalidElements: allowInvalidElements) { try Unboxer(dictionary: $0).performCustomUnboxing(closure: closure) } } - + /// Perform custom unboxing using an Unboxer (created from binary data) passed to a closure, or throw an UnboxError public static func performCustomUnboxing(data: Data, closure: @escaping (Unboxer) throws -> T?) throws -> T { return try data.unbox(closure: closure) } - + // MARK: - Unboxing required values (by key) - + /// Unbox a required value by key public func unbox(key: String) throws -> T { return try self.unbox(path: .key(key), transform: T.unbox) } - + /// Unbox a required collection by key public func unbox(key: String, allowInvalidElements: Bool) throws -> T { let transform = T.makeTransform(allowInvalidElements: allowInvalidElements) return try self.unbox(path: .key(key), transform: transform) } - + /// Unbox a required Unboxable type by key public func unbox(key: String) throws -> T { return try self.unbox(path: .key(key), transform: T.makeTransform()) } - + /// Unbox a required UnboxableWithContext type by key public func unbox(key: String, context: T.UnboxContext) throws -> T { return try self.unbox(path: .key(key), transform: T.makeTransform(context: context)) } - + /// Unbox a required collection of UnboxableWithContext values by key public func unbox(key: String, context: V.UnboxContext, allowInvalidElements: Bool = false) throws -> C where C.UnboxValue == V { return try self.unbox(path: .key(key), transform: V.makeCollectionTransform(context: context, allowInvalidElements: allowInvalidElements)) } - + /// Unbox a required value using a formatter by key public func unbox(key: String, formatter: F) throws -> F.UnboxFormattedType { return try self.unbox(path: .key(key), transform: formatter.makeTransform()) } - + /// Unbox a required collection of values using a formatter by key public func unbox(key: String, formatter: F, allowInvalidElements: Bool = false) throws -> C where C.UnboxValue == F.UnboxFormattedType { return try self.unbox(path: .key(key), transform: formatter.makeCollectionTransform(allowInvalidElements: allowInvalidElements)) } - + // MARK: - Unboxing required values (by key path) - + /// Unbox a required value by key path public func unbox(keyPath: String) throws -> T { return try self.unbox(path: .keyPath(keyPath), transform: T.unbox) } - + /// Unbox a required collection by key path public func unbox(keyPath: String, allowInvalidElements: Bool) throws -> T where T: Collection { let transform = T.makeTransform(allowInvalidElements: allowInvalidElements) return try self.unbox(path: .keyPath(keyPath), transform: transform) } - + /// Unbox a required Unboxable by key path public func unbox(keyPath: String) throws -> T { return try self.unbox(path: .keyPath(keyPath), transform: T.makeTransform()) } - + /// Unbox a required UnboxableWithContext type by key path public func unbox(keyPath: String, context: T.UnboxContext) throws -> T { return try self.unbox(path: .keyPath(keyPath), transform: T.makeTransform(context: context)) } - + /// Unbox a required collection of UnboxableWithContext values by key path public func unbox(keyPath: String, context: V.UnboxContext, allowInvalidElements: Bool = false) throws -> C where C.UnboxValue == V { return try self.unbox(path: .keyPath(keyPath), transform: V.makeCollectionTransform(context: context, allowInvalidElements: allowInvalidElements)) } - + /// Unbox a required value using a formatter by key path public func unbox(keyPath: String, formatter: F) throws -> F.UnboxFormattedType { return try self.unbox(path: .keyPath(keyPath), transform: formatter.makeTransform()) } - + /// Unbox a required collection of values using a formatter by key path public func unbox(keyPath: String, formatter: F, allowInvalidElements: Bool = false) throws -> C where C.UnboxValue == F.UnboxFormattedType { return try self.unbox(path: .keyPath(keyPath), transform: formatter.makeCollectionTransform(allowInvalidElements: allowInvalidElements)) } - + // MARK: - Unboxing optional values (by key) - + /// Unbox an optional value by key public func unbox(key: String) -> T? { return try? self.unbox(key: key) } - + /// Unbox an optional collection by key public func unbox(key: String, allowInvalidElements: Bool) -> T? { return try? self.unbox(key: key, allowInvalidElements: allowInvalidElements) } - + /// Unbox an optional Unboxable type by key public func unbox(key: String) -> T? { return try? self.unbox(key: key) } - + /// Unbox an optional UnboxableWithContext type by key public func unbox(key: String, context: T.UnboxContext) -> T? { return try? self.unbox(key: key, context: context) } - + /// Unbox an optional collection of UnboxableWithContext values by key public func unbox(key: String, context: V.UnboxContext, allowInvalidElements: Bool = false) -> C? where C.UnboxValue == V { return try? self.unbox(path: .key(key), transform: V.makeCollectionTransform(context: context, allowInvalidElements: allowInvalidElements)) } - + /// Unbox an optional value using a formatter by key public func unbox(key: String, formatter: F) -> F.UnboxFormattedType? { return try? self.unbox(key: key, formatter: formatter) } - + /// Unbox an optional collection of values using a formatter by key public func unbox(key: String, formatter: F, allowInvalidElements: Bool = false) -> C? where C.UnboxValue == F.UnboxFormattedType { return try? self.unbox(key: key, formatter: formatter, allowInvalidElements: allowInvalidElements) } - + // MARK: - Unboxing optional values (by key path) - + /// Unbox an optional value by key path public func unbox(keyPath: String) -> T? { return try? self.unbox(keyPath: keyPath) } - + /// Unbox an optional collection by key path public func unbox(keyPath: String, allowInvalidElements: Bool) -> T? { return try? self.unbox(keyPath: keyPath, allowInvalidElements: allowInvalidElements) } - + /// Unbox an optional Unboxable type by key path public func unbox(keyPath: String) -> T? { return try? self.unbox(keyPath: keyPath) } - + /// Unbox an optional UnboxableWithContext type by key path public func unbox(keyPath: String, context: T.UnboxContext) -> T? { return try? self.unbox(keyPath: keyPath, context: context) } - + /// Unbox an optional collection of UnboxableWithContext values by key path public func unbox(keyPath: String, context: V.UnboxContext, allowInvalidElements: Bool = false) -> C? where C.UnboxValue == V { return try? self.unbox(path: .keyPath(keyPath), transform: V.makeCollectionTransform(context: context, allowInvalidElements: allowInvalidElements)) } - + /// Unbox an optional value using a formatter by key path public func unbox(keyPath: String, formatter: F) -> F.UnboxFormattedType? { return try? self.unbox(keyPath: keyPath, formatter: formatter) } - + /// Unbox an optional collection of values using a formatter by key path public func unbox(keyPath: String, formatter: F, allowInvalidElements: Bool = false) -> C? where C.UnboxValue == F.UnboxFormattedType { return try? self.unbox(keyPath: keyPath, formatter: formatter, allowInvalidElements: allowInvalidElements) @@ -206,7 +206,7 @@ internal extension Unboxer { func performUnboxing() throws -> T { return try T(unboxer: self) } - + func performUnboxing(context: T.UnboxContext) throws -> T { return try T(unboxer: self, context: context) } @@ -225,23 +225,23 @@ private extension Unboxer { var node: UnboxPathNode = self.dictionary let components = keyPath.components(separatedBy: ".") let lastKey = components.last - + for key in components { guard let nextValue = node.unboxPathValue(forKey: key) else { throw UnboxPathError.missingKey(key) } - + if key == lastKey { return try transform(nextValue).orThrow(UnboxPathError.invalidValue(nextValue, key)) } - + guard let nextNode = nextValue as? UnboxPathNode else { throw UnboxPathError.invalidValue(nextValue, key) } - + node = nextNode } - + throw UnboxPathError.emptyKeyPath } } catch { @@ -250,11 +250,11 @@ private extension Unboxer { } else if let pathError = error as? UnboxPathError { throw UnboxError.pathError(pathError, path.description) } - + throw error } } - + func performCustomUnboxing(closure: (Unboxer) throws -> T?) throws -> T { return try closure(self).orThrow(UnboxError.customUnboxingFailed) } From 7e941fe945d8639887e77250a13e9d75a10f8cc4 Mon Sep 17 00:00:00 2001 From: Nicolas Jakubowski Date: Tue, 6 Jun 2017 13:17:09 -0500 Subject: [PATCH 4/4] added logger --- Sources/UnboxWarning.swift | 28 ++-------------------------- Sources/Unboxer.swift | 6 ++++++ 2 files changed, 8 insertions(+), 26 deletions(-) diff --git a/Sources/UnboxWarning.swift b/Sources/UnboxWarning.swift index 26d5423..656b3ee 100644 --- a/Sources/UnboxWarning.swift +++ b/Sources/UnboxWarning.swift @@ -8,35 +8,11 @@ import Foundation -/// Warnings are things that went wrong during the unbox operation but that didn't break unboxing -/// These aren't thrown, instead they are sent to a logger where you'll be able to keep record of them and research why you're getting this +/// Warnings are things that went wrong during the unbox operation +/// These aren't thrown, instead they are sent to a logger where you'll be able to keep record of them and research why you're getting them public enum UnboxWarning { /// An invalid element was found in an array case invalidElement(error: UnboxError) - /// Failed to unbox an optional key - case optionalKeyFailedToUnbox(error: UnboxError) -} - -internal extension UnboxError { - - /// Defines whether the receiver can be threated as a warning for the `optionalKeyFailedToUnbox` case - /// We only want to generate a warning if the key is not missing - var isOptionalKeyFailedtoUnboxWarning: Bool { - switch self { - case .customUnboxingFailed, .invalidData: - return true - case .pathError(let error, _): - - switch error { - case .emptyKeyPath, .missingKey: - return false - case .invalidArrayElement, .invalidCollectionElementType, .invalidDictionaryKey, .invalidDictionaryKeyType, .invalidDictionaryValue, .invalidValue: - return true - } - - } - } - } diff --git a/Sources/Unboxer.swift b/Sources/Unboxer.swift index 9e920f1..b8cc5bd 100644 --- a/Sources/Unboxer.swift +++ b/Sources/Unboxer.swift @@ -15,6 +15,12 @@ import Foundation * - and the correct type will be returned. If a required (non-optional) value couldn't be unboxed `UnboxError` will be thrown. */ public final class Unboxer { + + /// Takes care of logging warnings found during unbox operation. + /// Warnings are not fatal as errors, they just indicate that something is wrong. + /// An example of a warning is when you use the `allowInvalidElements` for parsing a collection. If an element in that collection fails to unbox, you'll receive a warning in this logger. + public static var warningLogger: UnboxWarningLogger? + /// The underlying JSON dictionary that is being unboxed public let dictionary: UnboxableDictionary