From d255a07fb76962a6aecf9888803d5d5693f07000 Mon Sep 17 00:00:00 2001 From: Oleksii Dykan Date: Wed, 20 Dec 2017 17:01:24 +0100 Subject: [PATCH 1/6] Add FirebaseEncoder class --- CodableFirebase.xcodeproj/project.pbxproj | 4 + CodableFirebase/FirebaseEncoder.swift | 495 ++++++++++++++++++++++ 2 files changed, 499 insertions(+) create mode 100644 CodableFirebase/FirebaseEncoder.swift diff --git a/CodableFirebase.xcodeproj/project.pbxproj b/CodableFirebase.xcodeproj/project.pbxproj index 139459e..fe6bd83 100644 --- a/CodableFirebase.xcodeproj/project.pbxproj +++ b/CodableFirebase.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ CE7DD3831F9D04AE000225C5 /* FirestoreEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7DD3811F9D04AE000225C5 /* FirestoreEncoder.swift */; }; CE7DD3841F9D04AE000225C5 /* FirestoreDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7DD3821F9D04AE000225C5 /* FirestoreDecoder.swift */; }; CE7DD3861F9DE4F7000225C5 /* TestCodableFirestore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7DD3851F9DE4F7000225C5 /* TestCodableFirestore.swift */; }; + CEA66D531FEABF050090CC63 /* FirebaseEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA66D521FEABF050090CC63 /* FirebaseEncoder.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -33,6 +34,7 @@ CE7DD3811F9D04AE000225C5 /* FirestoreEncoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirestoreEncoder.swift; sourceTree = ""; }; CE7DD3821F9D04AE000225C5 /* FirestoreDecoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirestoreDecoder.swift; sourceTree = ""; }; CE7DD3851F9DE4F7000225C5 /* TestCodableFirestore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestCodableFirestore.swift; sourceTree = ""; }; + CEA66D521FEABF050090CC63 /* FirebaseEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseEncoder.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -78,6 +80,7 @@ CE7DD36A1F9CFA81000225C5 /* CodableFirebase.h */, CE7DD3821F9D04AE000225C5 /* FirestoreDecoder.swift */, CE7DD3811F9D04AE000225C5 /* FirestoreEncoder.swift */, + CEA66D521FEABF050090CC63 /* FirebaseEncoder.swift */, CE7DD36B1F9CFA81000225C5 /* Info.plist */, ); path = CodableFirebase; @@ -204,6 +207,7 @@ buildActionMask = 2147483647; files = ( CE7DD3831F9D04AE000225C5 /* FirestoreEncoder.swift in Sources */, + CEA66D531FEABF050090CC63 /* FirebaseEncoder.swift in Sources */, CE7DD3841F9D04AE000225C5 /* FirestoreDecoder.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/CodableFirebase/FirebaseEncoder.swift b/CodableFirebase/FirebaseEncoder.swift new file mode 100644 index 0000000..a990eaf --- /dev/null +++ b/CodableFirebase/FirebaseEncoder.swift @@ -0,0 +1,495 @@ +// +// FirebaseEncoder.swift +// CodableFirebase +// +// Created by Oleksii on 20/12/2017. +// Copyright © 2017 ViolentOctopus. All rights reserved. +// + +import Foundation + + +class _FirebaseEncoder : Encoder { + fileprivate var storage: _FirebaseEncodingStorage + fileprivate(set) public var codingPath: [CodingKey] + public var userInfo: [CodingUserInfoKey : Any] + + init(codingPath: [CodingKey] = []) { + self.storage = _FirebaseEncodingStorage() + self.codingPath = codingPath + userInfo = [:] + } + + /// Returns whether a new element can be encoded at this coding path. + /// + /// `true` if an element has not yet been encoded at this coding path; `false` otherwise. + fileprivate var canEncodeNewValue: Bool { + // Every time a new value gets encoded, the key it's encoded for is pushed onto the coding path (even if it's a nil key from an unkeyed container). + // At the same time, every time a container is requested, a new value gets pushed onto the storage stack. + // If there are more values on the storage stack than on the coding path, it means the value is requesting more than one container, which violates the precondition. + // + // This means that anytime something that can request a new container goes onto the stack, we MUST push a key onto the coding path. + // Things which will not request containers do not need to have the coding path extended for them (but it doesn't matter if it is, because they will not reach here). + return self.storage.count == self.codingPath.count + } + + // MARK: - Encoder Methods + public func container(keyedBy: Key.Type) -> KeyedEncodingContainer { + // If an existing keyed container was already requested, return that one. + let topContainer: NSMutableDictionary + if canEncodeNewValue { + // We haven't yet pushed a container at this level; do so here. + topContainer = storage.pushKeyedContainer() + } else { + guard let container = self.storage.containers.last as? NSMutableDictionary else { + preconditionFailure("Attempt to push new keyed encoding container when already previously encoded at this path.") + } + + topContainer = container + } + + let container = _FirebaseKeyedEncodingContainer(referencing: self, codingPath: self.codingPath, wrapping: topContainer) + return KeyedEncodingContainer(container) + } + + public func unkeyedContainer() -> UnkeyedEncodingContainer { + // If an existing unkeyed container was already requested, return that one. + let topContainer: NSMutableArray + if canEncodeNewValue { + // We haven't yet pushed a container at this level; do so here. + topContainer = self.storage.pushUnkeyedContainer() + } else { + guard let container = self.storage.containers.last as? NSMutableArray else { + preconditionFailure("Attempt to push new unkeyed encoding container when already previously encoded at this path.") + } + + topContainer = container + } + + return _FirebaseUnkeyedEncodingContainer(referencing: self, codingPath: self.codingPath, wrapping: topContainer) + } + + public func singleValueContainer() -> SingleValueEncodingContainer { + return self + } +} + +fileprivate struct _FirebaseEncodingStorage { + // MARK: Properties + /// The container stack. + /// Elements may be any one of the plist types (NSNumber, NSString, NSDate, NSArray, NSDictionary). + private(set) fileprivate var containers: [NSObject] = [] + + // MARK: - Initialization + /// Initializes `self` with no containers. + fileprivate init() {} + + // MARK: - Modifying the Stack + fileprivate var count: Int { + return containers.count + } + + fileprivate mutating func pushKeyedContainer() -> NSMutableDictionary { + let dictionary = NSMutableDictionary() + containers.append(dictionary) + return dictionary + } + + fileprivate mutating func pushUnkeyedContainer() -> NSMutableArray { + let array = NSMutableArray() + containers.append(array) + return array + } + + fileprivate mutating func push(container: NSObject) { + containers.append(container) + } + + fileprivate mutating func popContainer() -> NSObject { + precondition(containers.count > 0, "Empty container stack.") + return containers.popLast()! + } +} + +fileprivate struct _FirebaseKeyedEncodingContainer : KeyedEncodingContainerProtocol { + typealias Key = K + + // MARK: Properties + /// A reference to the encoder we're writing to. + private let encoder: _FirebaseEncoder + + /// A reference to the container we're writing to. + private let container: NSMutableDictionary + + /// The path of coding keys taken to get to this point in encoding. + private(set) public var codingPath: [CodingKey] + + // MARK: - Initialization + /// Initializes `self` with the given references. + fileprivate init(referencing encoder: _FirebaseEncoder, codingPath: [CodingKey], wrapping container: NSMutableDictionary) { + self.encoder = encoder + self.codingPath = codingPath + self.container = container + } + + // MARK: - KeyedEncodingContainerProtocol Methods + public mutating func encodeNil(forKey key: Key) throws { container[key.stringValue] = NSNull() } + public mutating func encode(_ value: Bool, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: Int, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: Int8, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: Int16, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: Int32, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: Int64, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: UInt, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: UInt8, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: UInt16, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: UInt32, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: UInt64, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: String, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: Float, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: Double, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + + public mutating func encode(_ value: T, forKey key: Key) throws { + encoder.codingPath.append(key) + defer { encoder.codingPath.removeLast() } + container[key.stringValue] = try encoder.box(value) + } + + public mutating func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer { + let dictionary = NSMutableDictionary() + self.container[key.stringValue] = dictionary + + codingPath.append(key) + defer { codingPath.removeLast() } + + let container = _FirebaseKeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: dictionary) + return KeyedEncodingContainer(container) + } + + public mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { + let array = NSMutableArray() + container[key.stringValue] = array + + codingPath.append(key) + defer { codingPath.removeLast() } + return _FirebaseUnkeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: array) + } + + public mutating func superEncoder() -> Encoder { + return _FirebaseReferencingEncoder(referencing: encoder, at: _FirebaseKey.super, wrapping: container) + } + + public mutating func superEncoder(forKey key: Key) -> Encoder { + return _FirebaseReferencingEncoder(referencing: encoder, at: key, wrapping: container) + } +} + +fileprivate struct _FirebaseUnkeyedEncodingContainer : UnkeyedEncodingContainer { + // MARK: Properties + /// A reference to the encoder we're writing to. + private let encoder: _FirebaseEncoder + + /// A reference to the container we're writing to. + private let container: NSMutableArray + + /// The path of coding keys taken to get to this point in encoding. + private(set) public var codingPath: [CodingKey] + + /// The number of elements encoded into the container. + public var count: Int { + return container.count + } + + // MARK: - Initialization + /// Initializes `self` with the given references. + fileprivate init(referencing encoder: _FirebaseEncoder, codingPath: [CodingKey], wrapping container: NSMutableArray) { + self.encoder = encoder + self.codingPath = codingPath + self.container = container + } + + // MARK: - UnkeyedEncodingContainer Methods + public mutating func encodeNil() throws { container.add(NSNull()) } + public mutating func encode(_ value: Bool) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: Int) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: Int8) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: Int16) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: Int32) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: Int64) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: UInt) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: UInt8) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: UInt16) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: UInt32) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: UInt64) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: Float) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: Double) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: String) throws { container.add(self.encoder.box(value)) } + + public mutating func encode(_ value: T) throws { + encoder.codingPath.append(_FirebaseKey(index: count)) + defer { encoder.codingPath.removeLast() } + container.add(try encoder.box(value)) + } + + public mutating func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer { + self.codingPath.append(_FirebaseKey(index: self.count)) + defer { self.codingPath.removeLast() } + + let dictionary = NSMutableDictionary() + self.container.add(dictionary) + + let container = _FirebaseKeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: dictionary) + return KeyedEncodingContainer(container) + } + + public mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { + self.codingPath.append(_FirebaseKey(index: self.count)) + defer { self.codingPath.removeLast() } + + let array = NSMutableArray() + self.container.add(array) + return _FirebaseUnkeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: array) + } + + public mutating func superEncoder() -> Encoder { + return _FirebaseReferencingEncoder(referencing: encoder, at: container.count, wrapping: container) + } +} + +struct _FirebaseKey : CodingKey { + public var stringValue: String + public var intValue: Int? + + public init?(stringValue: String) { + self.stringValue = stringValue + self.intValue = nil + } + + public init?(intValue: Int) { + self.stringValue = "\(intValue)" + self.intValue = intValue + } + + init(index: Int) { + self.stringValue = "Index \(index)" + self.intValue = index + } + + static let `super` = _FirebaseKey(stringValue: "super")! +} + +extension _FirebaseEncoder { + + /// Returns the given value boxed in a container appropriate for pushing onto the container stack. + fileprivate func box(_ value: Bool) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: Int) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: Int8) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: Int16) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: Int32) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: Int64) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: UInt) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: UInt8) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: UInt16) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: UInt32) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: UInt64) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: Float) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: Double) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: String) -> NSObject { return NSString(string: value) } + + fileprivate func box(_ value: T) throws -> NSObject { + return try self.box_(value) ?? NSDictionary() + } + + func box_(_ value: T) throws -> NSObject? { + if T.self == Date.self || T.self == NSDate.self { + return (value as! NSDate) + } else if T.self == Data.self || T.self == NSData.self { + return (value as! NSData) + } + + // The value should request a container from the _FirebaseEncoder. + let depth = storage.count + try value.encode(to: self) + + // The top container should be a new container. + guard storage.count > depth else { + return nil + } + + return storage.popContainer() + } +} + +extension _FirebaseEncoder : SingleValueEncodingContainer { + // MARK: - SingleValueEncodingContainer Methods + private func assertCanEncodeNewValue() { + precondition(canEncodeNewValue, "Attempt to encode value through single value container when previously value already encoded.") + } + + public func encodeNil() throws { + assertCanEncodeNewValue() + storage.push(container: NSNull()) + } + + public func encode(_ value: Bool) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int8) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int16) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int32) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int64) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt8) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt16) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt32) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt64) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: String) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Float) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Double) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: T) throws { + assertCanEncodeNewValue() + try storage.push(container: box(value)) + } +} + +fileprivate class _FirebaseReferencingEncoder : _FirebaseEncoder { + // MARK: Reference types. + /// The type of container we're referencing. + private enum Reference { + /// Referencing a specific index in an array container. + case array(NSMutableArray, Int) + + /// Referencing a specific key in a dictionary container. + case dictionary(NSMutableDictionary, String) + } + + // MARK: - Properties + /// The encoder we're referencing. + private let encoder: _FirebaseEncoder + + /// The container reference itself. + private let reference: Reference + + // MARK: - Initialization + /// Initializes `self` by referencing the given array container in the given encoder. + fileprivate init(referencing encoder: _FirebaseEncoder, at index: Int, wrapping array: NSMutableArray) { + self.encoder = encoder + self.reference = .array(array, index) + super.init(codingPath: encoder.codingPath) + + self.codingPath.append(_FirebaseKey(index: index)) + } + + /// Initializes `self` by referencing the given dictionary container in the given encoder. + fileprivate init(referencing encoder: _FirebaseEncoder, at key: CodingKey, wrapping dictionary: NSMutableDictionary) { + self.encoder = encoder + reference = .dictionary(dictionary, key.stringValue) + super.init(codingPath: encoder.codingPath) + codingPath.append(key) + } + + // MARK: - Coding Path Operations + fileprivate override var canEncodeNewValue: Bool { + // With a regular encoder, the storage and coding path grow together. + // A referencing encoder, however, inherits its parents coding path, as well as the key it was created for. + // We have to take this into account. + return storage.count == codingPath.count - encoder.codingPath.count - 1 + } + + // MARK: - Deinitialization + // Finalizes `self` by writing the contents of our storage to the referenced encoder's storage. + deinit { + let value: Any + switch storage.count { + case 0: value = NSDictionary() + case 1: value = self.storage.popContainer() + default: fatalError("Referencing encoder deallocated with multiple containers on stack.") + } + + switch self.reference { + case .array(let array, let index): + array.insert(value, at: index) + + case .dictionary(let dictionary, let key): + dictionary[NSString(string: key)] = value + } + } +} + +internal extension DecodingError { + internal static func _typeMismatch(at path: [CodingKey], expectation: Any.Type, reality: Any) -> DecodingError { + let description = "Expected to decode \(expectation) but found \(_typeDescription(of: reality)) instead." + return .typeMismatch(expectation, Context(codingPath: path, debugDescription: description)) + } + + fileprivate static func _typeDescription(of value: Any) -> String { + if value is NSNull { + return "a null value" + } else if value is NSNumber /* FIXME: If swift-corelibs-foundation isn't updated to use NSNumber, this check will be necessary: || value is Int || value is Double */ { + return "a number" + } else if value is String { + return "a string/data" + } else if value is [Any] { + return "an array" + } else if value is [String : Any] { + return "a dictionary" + } else { + return "\(type(of: value))" + } + } +} From a9480c9f6dbb437d94cdd9700ac2008ec08474b0 Mon Sep 17 00:00:00 2001 From: Oleksii Dykan Date: Wed, 27 Dec 2017 11:50:39 +0100 Subject: [PATCH 2/6] Remove firebase encoder --- CodableFirebase.xcodeproj/project.pbxproj | 4 - CodableFirebase/FirebaseEncoder.swift | 495 ---------------------- 2 files changed, 499 deletions(-) delete mode 100644 CodableFirebase/FirebaseEncoder.swift diff --git a/CodableFirebase.xcodeproj/project.pbxproj b/CodableFirebase.xcodeproj/project.pbxproj index fe6bd83..139459e 100644 --- a/CodableFirebase.xcodeproj/project.pbxproj +++ b/CodableFirebase.xcodeproj/project.pbxproj @@ -12,7 +12,6 @@ CE7DD3831F9D04AE000225C5 /* FirestoreEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7DD3811F9D04AE000225C5 /* FirestoreEncoder.swift */; }; CE7DD3841F9D04AE000225C5 /* FirestoreDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7DD3821F9D04AE000225C5 /* FirestoreDecoder.swift */; }; CE7DD3861F9DE4F7000225C5 /* TestCodableFirestore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7DD3851F9DE4F7000225C5 /* TestCodableFirestore.swift */; }; - CEA66D531FEABF050090CC63 /* FirebaseEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA66D521FEABF050090CC63 /* FirebaseEncoder.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -34,7 +33,6 @@ CE7DD3811F9D04AE000225C5 /* FirestoreEncoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirestoreEncoder.swift; sourceTree = ""; }; CE7DD3821F9D04AE000225C5 /* FirestoreDecoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirestoreDecoder.swift; sourceTree = ""; }; CE7DD3851F9DE4F7000225C5 /* TestCodableFirestore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestCodableFirestore.swift; sourceTree = ""; }; - CEA66D521FEABF050090CC63 /* FirebaseEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseEncoder.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -80,7 +78,6 @@ CE7DD36A1F9CFA81000225C5 /* CodableFirebase.h */, CE7DD3821F9D04AE000225C5 /* FirestoreDecoder.swift */, CE7DD3811F9D04AE000225C5 /* FirestoreEncoder.swift */, - CEA66D521FEABF050090CC63 /* FirebaseEncoder.swift */, CE7DD36B1F9CFA81000225C5 /* Info.plist */, ); path = CodableFirebase; @@ -207,7 +204,6 @@ buildActionMask = 2147483647; files = ( CE7DD3831F9D04AE000225C5 /* FirestoreEncoder.swift in Sources */, - CEA66D531FEABF050090CC63 /* FirebaseEncoder.swift in Sources */, CE7DD3841F9D04AE000225C5 /* FirestoreDecoder.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/CodableFirebase/FirebaseEncoder.swift b/CodableFirebase/FirebaseEncoder.swift deleted file mode 100644 index a990eaf..0000000 --- a/CodableFirebase/FirebaseEncoder.swift +++ /dev/null @@ -1,495 +0,0 @@ -// -// FirebaseEncoder.swift -// CodableFirebase -// -// Created by Oleksii on 20/12/2017. -// Copyright © 2017 ViolentOctopus. All rights reserved. -// - -import Foundation - - -class _FirebaseEncoder : Encoder { - fileprivate var storage: _FirebaseEncodingStorage - fileprivate(set) public var codingPath: [CodingKey] - public var userInfo: [CodingUserInfoKey : Any] - - init(codingPath: [CodingKey] = []) { - self.storage = _FirebaseEncodingStorage() - self.codingPath = codingPath - userInfo = [:] - } - - /// Returns whether a new element can be encoded at this coding path. - /// - /// `true` if an element has not yet been encoded at this coding path; `false` otherwise. - fileprivate var canEncodeNewValue: Bool { - // Every time a new value gets encoded, the key it's encoded for is pushed onto the coding path (even if it's a nil key from an unkeyed container). - // At the same time, every time a container is requested, a new value gets pushed onto the storage stack. - // If there are more values on the storage stack than on the coding path, it means the value is requesting more than one container, which violates the precondition. - // - // This means that anytime something that can request a new container goes onto the stack, we MUST push a key onto the coding path. - // Things which will not request containers do not need to have the coding path extended for them (but it doesn't matter if it is, because they will not reach here). - return self.storage.count == self.codingPath.count - } - - // MARK: - Encoder Methods - public func container(keyedBy: Key.Type) -> KeyedEncodingContainer { - // If an existing keyed container was already requested, return that one. - let topContainer: NSMutableDictionary - if canEncodeNewValue { - // We haven't yet pushed a container at this level; do so here. - topContainer = storage.pushKeyedContainer() - } else { - guard let container = self.storage.containers.last as? NSMutableDictionary else { - preconditionFailure("Attempt to push new keyed encoding container when already previously encoded at this path.") - } - - topContainer = container - } - - let container = _FirebaseKeyedEncodingContainer(referencing: self, codingPath: self.codingPath, wrapping: topContainer) - return KeyedEncodingContainer(container) - } - - public func unkeyedContainer() -> UnkeyedEncodingContainer { - // If an existing unkeyed container was already requested, return that one. - let topContainer: NSMutableArray - if canEncodeNewValue { - // We haven't yet pushed a container at this level; do so here. - topContainer = self.storage.pushUnkeyedContainer() - } else { - guard let container = self.storage.containers.last as? NSMutableArray else { - preconditionFailure("Attempt to push new unkeyed encoding container when already previously encoded at this path.") - } - - topContainer = container - } - - return _FirebaseUnkeyedEncodingContainer(referencing: self, codingPath: self.codingPath, wrapping: topContainer) - } - - public func singleValueContainer() -> SingleValueEncodingContainer { - return self - } -} - -fileprivate struct _FirebaseEncodingStorage { - // MARK: Properties - /// The container stack. - /// Elements may be any one of the plist types (NSNumber, NSString, NSDate, NSArray, NSDictionary). - private(set) fileprivate var containers: [NSObject] = [] - - // MARK: - Initialization - /// Initializes `self` with no containers. - fileprivate init() {} - - // MARK: - Modifying the Stack - fileprivate var count: Int { - return containers.count - } - - fileprivate mutating func pushKeyedContainer() -> NSMutableDictionary { - let dictionary = NSMutableDictionary() - containers.append(dictionary) - return dictionary - } - - fileprivate mutating func pushUnkeyedContainer() -> NSMutableArray { - let array = NSMutableArray() - containers.append(array) - return array - } - - fileprivate mutating func push(container: NSObject) { - containers.append(container) - } - - fileprivate mutating func popContainer() -> NSObject { - precondition(containers.count > 0, "Empty container stack.") - return containers.popLast()! - } -} - -fileprivate struct _FirebaseKeyedEncodingContainer : KeyedEncodingContainerProtocol { - typealias Key = K - - // MARK: Properties - /// A reference to the encoder we're writing to. - private let encoder: _FirebaseEncoder - - /// A reference to the container we're writing to. - private let container: NSMutableDictionary - - /// The path of coding keys taken to get to this point in encoding. - private(set) public var codingPath: [CodingKey] - - // MARK: - Initialization - /// Initializes `self` with the given references. - fileprivate init(referencing encoder: _FirebaseEncoder, codingPath: [CodingKey], wrapping container: NSMutableDictionary) { - self.encoder = encoder - self.codingPath = codingPath - self.container = container - } - - // MARK: - KeyedEncodingContainerProtocol Methods - public mutating func encodeNil(forKey key: Key) throws { container[key.stringValue] = NSNull() } - public mutating func encode(_ value: Bool, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: Int, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: Int8, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: Int16, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: Int32, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: Int64, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: UInt, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: UInt8, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: UInt16, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: UInt32, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: UInt64, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: String, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: Float, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: Double, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - - public mutating func encode(_ value: T, forKey key: Key) throws { - encoder.codingPath.append(key) - defer { encoder.codingPath.removeLast() } - container[key.stringValue] = try encoder.box(value) - } - - public mutating func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer { - let dictionary = NSMutableDictionary() - self.container[key.stringValue] = dictionary - - codingPath.append(key) - defer { codingPath.removeLast() } - - let container = _FirebaseKeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: dictionary) - return KeyedEncodingContainer(container) - } - - public mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { - let array = NSMutableArray() - container[key.stringValue] = array - - codingPath.append(key) - defer { codingPath.removeLast() } - return _FirebaseUnkeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: array) - } - - public mutating func superEncoder() -> Encoder { - return _FirebaseReferencingEncoder(referencing: encoder, at: _FirebaseKey.super, wrapping: container) - } - - public mutating func superEncoder(forKey key: Key) -> Encoder { - return _FirebaseReferencingEncoder(referencing: encoder, at: key, wrapping: container) - } -} - -fileprivate struct _FirebaseUnkeyedEncodingContainer : UnkeyedEncodingContainer { - // MARK: Properties - /// A reference to the encoder we're writing to. - private let encoder: _FirebaseEncoder - - /// A reference to the container we're writing to. - private let container: NSMutableArray - - /// The path of coding keys taken to get to this point in encoding. - private(set) public var codingPath: [CodingKey] - - /// The number of elements encoded into the container. - public var count: Int { - return container.count - } - - // MARK: - Initialization - /// Initializes `self` with the given references. - fileprivate init(referencing encoder: _FirebaseEncoder, codingPath: [CodingKey], wrapping container: NSMutableArray) { - self.encoder = encoder - self.codingPath = codingPath - self.container = container - } - - // MARK: - UnkeyedEncodingContainer Methods - public mutating func encodeNil() throws { container.add(NSNull()) } - public mutating func encode(_ value: Bool) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: Int) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: Int8) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: Int16) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: Int32) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: Int64) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: UInt) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: UInt8) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: UInt16) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: UInt32) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: UInt64) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: Float) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: Double) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: String) throws { container.add(self.encoder.box(value)) } - - public mutating func encode(_ value: T) throws { - encoder.codingPath.append(_FirebaseKey(index: count)) - defer { encoder.codingPath.removeLast() } - container.add(try encoder.box(value)) - } - - public mutating func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer { - self.codingPath.append(_FirebaseKey(index: self.count)) - defer { self.codingPath.removeLast() } - - let dictionary = NSMutableDictionary() - self.container.add(dictionary) - - let container = _FirebaseKeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: dictionary) - return KeyedEncodingContainer(container) - } - - public mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { - self.codingPath.append(_FirebaseKey(index: self.count)) - defer { self.codingPath.removeLast() } - - let array = NSMutableArray() - self.container.add(array) - return _FirebaseUnkeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: array) - } - - public mutating func superEncoder() -> Encoder { - return _FirebaseReferencingEncoder(referencing: encoder, at: container.count, wrapping: container) - } -} - -struct _FirebaseKey : CodingKey { - public var stringValue: String - public var intValue: Int? - - public init?(stringValue: String) { - self.stringValue = stringValue - self.intValue = nil - } - - public init?(intValue: Int) { - self.stringValue = "\(intValue)" - self.intValue = intValue - } - - init(index: Int) { - self.stringValue = "Index \(index)" - self.intValue = index - } - - static let `super` = _FirebaseKey(stringValue: "super")! -} - -extension _FirebaseEncoder { - - /// Returns the given value boxed in a container appropriate for pushing onto the container stack. - fileprivate func box(_ value: Bool) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Int) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Int8) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Int16) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Int32) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Int64) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: UInt) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: UInt8) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: UInt16) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: UInt32) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: UInt64) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Float) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Double) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: String) -> NSObject { return NSString(string: value) } - - fileprivate func box(_ value: T) throws -> NSObject { - return try self.box_(value) ?? NSDictionary() - } - - func box_(_ value: T) throws -> NSObject? { - if T.self == Date.self || T.self == NSDate.self { - return (value as! NSDate) - } else if T.self == Data.self || T.self == NSData.self { - return (value as! NSData) - } - - // The value should request a container from the _FirebaseEncoder. - let depth = storage.count - try value.encode(to: self) - - // The top container should be a new container. - guard storage.count > depth else { - return nil - } - - return storage.popContainer() - } -} - -extension _FirebaseEncoder : SingleValueEncodingContainer { - // MARK: - SingleValueEncodingContainer Methods - private func assertCanEncodeNewValue() { - precondition(canEncodeNewValue, "Attempt to encode value through single value container when previously value already encoded.") - } - - public func encodeNil() throws { - assertCanEncodeNewValue() - storage.push(container: NSNull()) - } - - public func encode(_ value: Bool) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: Int) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: Int8) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: Int16) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: Int32) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: Int64) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: UInt) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: UInt8) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: UInt16) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: UInt32) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: UInt64) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: String) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: Float) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: Double) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: T) throws { - assertCanEncodeNewValue() - try storage.push(container: box(value)) - } -} - -fileprivate class _FirebaseReferencingEncoder : _FirebaseEncoder { - // MARK: Reference types. - /// The type of container we're referencing. - private enum Reference { - /// Referencing a specific index in an array container. - case array(NSMutableArray, Int) - - /// Referencing a specific key in a dictionary container. - case dictionary(NSMutableDictionary, String) - } - - // MARK: - Properties - /// The encoder we're referencing. - private let encoder: _FirebaseEncoder - - /// The container reference itself. - private let reference: Reference - - // MARK: - Initialization - /// Initializes `self` by referencing the given array container in the given encoder. - fileprivate init(referencing encoder: _FirebaseEncoder, at index: Int, wrapping array: NSMutableArray) { - self.encoder = encoder - self.reference = .array(array, index) - super.init(codingPath: encoder.codingPath) - - self.codingPath.append(_FirebaseKey(index: index)) - } - - /// Initializes `self` by referencing the given dictionary container in the given encoder. - fileprivate init(referencing encoder: _FirebaseEncoder, at key: CodingKey, wrapping dictionary: NSMutableDictionary) { - self.encoder = encoder - reference = .dictionary(dictionary, key.stringValue) - super.init(codingPath: encoder.codingPath) - codingPath.append(key) - } - - // MARK: - Coding Path Operations - fileprivate override var canEncodeNewValue: Bool { - // With a regular encoder, the storage and coding path grow together. - // A referencing encoder, however, inherits its parents coding path, as well as the key it was created for. - // We have to take this into account. - return storage.count == codingPath.count - encoder.codingPath.count - 1 - } - - // MARK: - Deinitialization - // Finalizes `self` by writing the contents of our storage to the referenced encoder's storage. - deinit { - let value: Any - switch storage.count { - case 0: value = NSDictionary() - case 1: value = self.storage.popContainer() - default: fatalError("Referencing encoder deallocated with multiple containers on stack.") - } - - switch self.reference { - case .array(let array, let index): - array.insert(value, at: index) - - case .dictionary(let dictionary, let key): - dictionary[NSString(string: key)] = value - } - } -} - -internal extension DecodingError { - internal static func _typeMismatch(at path: [CodingKey], expectation: Any.Type, reality: Any) -> DecodingError { - let description = "Expected to decode \(expectation) but found \(_typeDescription(of: reality)) instead." - return .typeMismatch(expectation, Context(codingPath: path, debugDescription: description)) - } - - fileprivate static func _typeDescription(of value: Any) -> String { - if value is NSNull { - return "a null value" - } else if value is NSNumber /* FIXME: If swift-corelibs-foundation isn't updated to use NSNumber, this check will be necessary: || value is Int || value is Double */ { - return "a number" - } else if value is String { - return "a string/data" - } else if value is [Any] { - return "an array" - } else if value is [String : Any] { - return "a dictionary" - } else { - return "\(type(of: value))" - } - } -} From 37b70fddb48605e6f368e8c9e508fd87901e5578 Mon Sep 17 00:00:00 2001 From: Oleksii Dykan Date: Wed, 27 Dec 2017 11:53:49 +0100 Subject: [PATCH 3/6] Rename to firebase decider and encode --- CodableFirebase/FirestoreDecoder.swift | 142 ++++++++++++------------- CodableFirebase/FirestoreEncoder.swift | 14 +-- 2 files changed, 78 insertions(+), 78 deletions(-) diff --git a/CodableFirebase/FirestoreDecoder.swift b/CodableFirebase/FirestoreDecoder.swift index 83333f0..cf60c59 100644 --- a/CodableFirebase/FirestoreDecoder.swift +++ b/CodableFirebase/FirestoreDecoder.swift @@ -12,7 +12,7 @@ open class FirestoreDecoder { public init() {} open func decode(_ type: T.Type, from container: [String: Any]) throws -> T { - let decoder = _FirestoreDecoder(referencing: container) + let decoder = _FirebaseDecoder(referencing: container) guard let value = try decoder.unbox(container, as: T.self) else { throw DecodingError.valueNotFound(T.self, DecodingError.Context(codingPath: [], debugDescription: "The given dictionary was invalid")) } @@ -21,10 +21,10 @@ open class FirestoreDecoder { } } -fileprivate class _FirestoreDecoder : Decoder { +fileprivate class _FirebaseDecoder : Decoder { // MARK: Properties /// The decoder's storage. - fileprivate var storage: _FirestoreDecodingStorage + fileprivate var storage: _FirebaseDecodingStorage /// The path to the current point in encoding. fileprivate(set) public var codingPath: [CodingKey] @@ -35,7 +35,7 @@ fileprivate class _FirestoreDecoder : Decoder { // MARK: - Initialization /// Initializes `self` with the given top-level container and options. fileprivate init(referencing container: Any, at codingPath: [CodingKey] = []) { - self.storage = _FirestoreDecodingStorage() + self.storage = _FirebaseDecodingStorage() self.storage.push(container: container) self.codingPath = codingPath userInfo = [:] @@ -54,7 +54,7 @@ fileprivate class _FirestoreDecoder : Decoder { throw DecodingError.typeMismatch([String: Any].self, context) } - let container = _FirestoreKeyedDecodingContainer(referencing: self, wrapping: topContainer) + let container = _FirebaseKeyedDecodingContainer(referencing: self, wrapping: topContainer) return KeyedDecodingContainer(container) } @@ -70,7 +70,7 @@ fileprivate class _FirestoreDecoder : Decoder { throw DecodingError.typeMismatch([Any].self, context) } - return _FirestoreUnkeyedDecodingContainer(referencing: self, wrapping: topContainer) + return _FirebaseUnkeyedDecodingContainer(referencing: self, wrapping: topContainer) } public func singleValueContainer() throws -> SingleValueDecodingContainer { @@ -78,7 +78,7 @@ fileprivate class _FirestoreDecoder : Decoder { } } -fileprivate struct _FirestoreDecodingStorage { +fileprivate struct _FirebaseDecodingStorage { // MARK: Properties /// The container stack. /// Elements may be any one of the plist types (NSNumber, Date, String, Array, [String : Any]). @@ -108,12 +108,12 @@ fileprivate struct _FirestoreDecodingStorage { } } -fileprivate struct _FirestoreKeyedDecodingContainer : KeyedDecodingContainerProtocol { +fileprivate struct _FirebaseKeyedDecodingContainer : KeyedDecodingContainerProtocol { typealias Key = K // MARK: Properties /// A reference to the decoder we're reading from. - private let decoder: _FirestoreDecoder + private let decoder: _FirebaseDecoder /// A reference to the container we're reading from. private let container: [String : Any] @@ -123,7 +123,7 @@ fileprivate struct _FirestoreKeyedDecodingContainer : KeyedDecodi // MARK: - Initialization /// Initializes `self` by referencing the given decoder and container. - fileprivate init(referencing decoder: _FirestoreDecoder, wrapping container: [String : Any]) { + fileprivate init(referencing decoder: _FirebaseDecoder, wrapping container: [String : Any]) { self.decoder = decoder self.container = container self.codingPath = decoder.codingPath @@ -384,7 +384,7 @@ fileprivate struct _FirestoreKeyedDecodingContainer : KeyedDecodi throw DecodingError._typeMismatch(at: self.codingPath, expectation: [String : Any].self, reality: value) } - let container = _FirestoreKeyedDecodingContainer(referencing: self.decoder, wrapping: dictionary) + let container = _FirebaseKeyedDecodingContainer(referencing: self.decoder, wrapping: dictionary) return KeyedDecodingContainer(container) } @@ -403,7 +403,7 @@ fileprivate struct _FirestoreKeyedDecodingContainer : KeyedDecodi throw DecodingError.typeMismatch([Any].self, context) } - return _FirestoreUnkeyedDecodingContainer(referencing: self.decoder, wrapping: array) + return _FirebaseUnkeyedDecodingContainer(referencing: self.decoder, wrapping: array) } private func _superDecoder(forKey key: CodingKey) throws -> Decoder { @@ -411,11 +411,11 @@ fileprivate struct _FirestoreKeyedDecodingContainer : KeyedDecodi defer { self.decoder.codingPath.removeLast() } let value: Any = container[key.stringValue] ?? NSNull() - return _FirestoreDecoder(referencing: value, at: self.decoder.codingPath) + return _FirebaseDecoder(referencing: value, at: self.decoder.codingPath) } public func superDecoder() throws -> Decoder { - return try _superDecoder(forKey: _FirestoreKey.super) + return try _superDecoder(forKey: _FirebaseKey.super) } public func superDecoder(forKey key: Key) throws -> Decoder { @@ -423,10 +423,10 @@ fileprivate struct _FirestoreKeyedDecodingContainer : KeyedDecodi } } -fileprivate struct _FirestoreUnkeyedDecodingContainer : UnkeyedDecodingContainer { +fileprivate struct _FirebaseUnkeyedDecodingContainer : UnkeyedDecodingContainer { // MARK: Properties /// A reference to the decoder we're reading from. - private let decoder: _FirestoreDecoder + private let decoder: _FirebaseDecoder /// A reference to the container we're reading from. private let container: [Any] @@ -439,7 +439,7 @@ fileprivate struct _FirestoreUnkeyedDecodingContainer : UnkeyedDecodingContainer // MARK: - Initialization /// Initializes `self` by referencing the given decoder and container. - fileprivate init(referencing decoder: _FirestoreDecoder, wrapping container: [Any]) { + fileprivate init(referencing decoder: _FirebaseDecoder, wrapping container: [Any]) { self.decoder = decoder self.container = container self.codingPath = decoder.codingPath @@ -457,7 +457,7 @@ fileprivate struct _FirestoreUnkeyedDecodingContainer : UnkeyedDecodingContainer public mutating func decodeNil() throws -> Bool { guard !isAtEnd else { - throw DecodingError.valueNotFound(Any?.self, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirestoreKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) + throw DecodingError.valueNotFound(Any?.self, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) } if container[currentIndex] is NSNull { @@ -470,14 +470,14 @@ fileprivate struct _FirestoreUnkeyedDecodingContainer : UnkeyedDecodingContainer public mutating func decode(_ type: Bool.Type) throws -> Bool { guard !isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirestoreKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) } - decoder.codingPath.append(_FirestoreKey(index: currentIndex)) + decoder.codingPath.append(_FirebaseKey(index: currentIndex)) defer { decoder.codingPath.removeLast() } guard let decoded = try decoder.unbox(container[currentIndex], as: Bool.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirestoreKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) } currentIndex += 1 @@ -486,14 +486,14 @@ fileprivate struct _FirestoreUnkeyedDecodingContainer : UnkeyedDecodingContainer public mutating func decode(_ type: Int.Type) throws -> Int { guard !isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirestoreKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) } - decoder.codingPath.append(_FirestoreKey(index: currentIndex)) + decoder.codingPath.append(_FirebaseKey(index: currentIndex)) defer { decoder.codingPath.removeLast() } guard let decoded = try decoder.unbox(container[currentIndex], as: Int.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirestoreKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) } currentIndex += 1 @@ -502,14 +502,14 @@ fileprivate struct _FirestoreUnkeyedDecodingContainer : UnkeyedDecodingContainer public mutating func decode(_ type: Int8.Type) throws -> Int8 { guard !isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirestoreKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) } - decoder.codingPath.append(_FirestoreKey(index: currentIndex)) + decoder.codingPath.append(_FirebaseKey(index: currentIndex)) defer { decoder.codingPath.removeLast() } guard let decoded = try decoder.unbox(container[currentIndex], as: Int8.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirestoreKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) } currentIndex += 1 @@ -518,14 +518,14 @@ fileprivate struct _FirestoreUnkeyedDecodingContainer : UnkeyedDecodingContainer public mutating func decode(_ type: Int16.Type) throws -> Int16 { guard !isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirestoreKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) } - decoder.codingPath.append(_FirestoreKey(index: currentIndex)) + decoder.codingPath.append(_FirebaseKey(index: currentIndex)) defer { decoder.codingPath.removeLast() } guard let decoded = try decoder.unbox(container[currentIndex], as: Int16.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirestoreKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) } currentIndex += 1 @@ -534,14 +534,14 @@ fileprivate struct _FirestoreUnkeyedDecodingContainer : UnkeyedDecodingContainer public mutating func decode(_ type: Int32.Type) throws -> Int32 { guard !isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirestoreKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) } - decoder.codingPath.append(_FirestoreKey(index: currentIndex)) + decoder.codingPath.append(_FirebaseKey(index: currentIndex)) defer { decoder.codingPath.removeLast() } guard let decoded = try decoder.unbox(container[currentIndex], as: Int32.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirestoreKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) } currentIndex += 1 @@ -550,14 +550,14 @@ fileprivate struct _FirestoreUnkeyedDecodingContainer : UnkeyedDecodingContainer public mutating func decode(_ type: Int64.Type) throws -> Int64 { guard !isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirestoreKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) } - decoder.codingPath.append(_FirestoreKey(index: currentIndex)) + decoder.codingPath.append(_FirebaseKey(index: currentIndex)) defer { decoder.codingPath.removeLast() } guard let decoded = try decoder.unbox(container[currentIndex], as: Int64.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirestoreKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) } currentIndex += 1 @@ -566,14 +566,14 @@ fileprivate struct _FirestoreUnkeyedDecodingContainer : UnkeyedDecodingContainer public mutating func decode(_ type: UInt.Type) throws -> UInt { guard !isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirestoreKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) } - decoder.codingPath.append(_FirestoreKey(index: currentIndex)) + decoder.codingPath.append(_FirebaseKey(index: currentIndex)) defer { decoder.codingPath.removeLast() } guard let decoded = try decoder.unbox(container[currentIndex], as: UInt.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirestoreKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) } currentIndex += 1 @@ -582,14 +582,14 @@ fileprivate struct _FirestoreUnkeyedDecodingContainer : UnkeyedDecodingContainer public mutating func decode(_ type: UInt8.Type) throws -> UInt8 { guard !isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirestoreKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) } - decoder.codingPath.append(_FirestoreKey(index: currentIndex)) + decoder.codingPath.append(_FirebaseKey(index: currentIndex)) defer { decoder.codingPath.removeLast() } guard let decoded = try decoder.unbox(container[currentIndex], as: UInt8.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirestoreKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) } currentIndex += 1 @@ -598,14 +598,14 @@ fileprivate struct _FirestoreUnkeyedDecodingContainer : UnkeyedDecodingContainer public mutating func decode(_ type: UInt16.Type) throws -> UInt16 { guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirestoreKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) } - self.decoder.codingPath.append(_FirestoreKey(index: currentIndex)) + self.decoder.codingPath.append(_FirebaseKey(index: currentIndex)) defer { self.decoder.codingPath.removeLast() } guard let decoded = try decoder.unbox(container[currentIndex], as: UInt16.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirestoreKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) } currentIndex += 1 @@ -614,14 +614,14 @@ fileprivate struct _FirestoreUnkeyedDecodingContainer : UnkeyedDecodingContainer public mutating func decode(_ type: UInt32.Type) throws -> UInt32 { guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirestoreKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) } - decoder.codingPath.append(_FirestoreKey(index: currentIndex)) + decoder.codingPath.append(_FirebaseKey(index: currentIndex)) defer { decoder.codingPath.removeLast() } guard let decoded = try decoder.unbox(container[currentIndex], as: UInt32.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirestoreKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) } currentIndex += 1 @@ -630,14 +630,14 @@ fileprivate struct _FirestoreUnkeyedDecodingContainer : UnkeyedDecodingContainer public mutating func decode(_ type: UInt64.Type) throws -> UInt64 { guard !isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirestoreKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) } - decoder.codingPath.append(_FirestoreKey(index: currentIndex)) + decoder.codingPath.append(_FirebaseKey(index: currentIndex)) defer { decoder.codingPath.removeLast() } guard let decoded = try decoder.unbox(container[currentIndex], as: UInt64.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirestoreKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) } currentIndex += 1 @@ -646,14 +646,14 @@ fileprivate struct _FirestoreUnkeyedDecodingContainer : UnkeyedDecodingContainer public mutating func decode(_ type: Float.Type) throws -> Float { guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirestoreKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) } - self.decoder.codingPath.append(_FirestoreKey(index: self.currentIndex)) + self.decoder.codingPath.append(_FirebaseKey(index: self.currentIndex)) defer { self.decoder.codingPath.removeLast() } guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Float.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirestoreKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) } self.currentIndex += 1 @@ -662,14 +662,14 @@ fileprivate struct _FirestoreUnkeyedDecodingContainer : UnkeyedDecodingContainer public mutating func decode(_ type: Double.Type) throws -> Double { guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirestoreKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) } - self.decoder.codingPath.append(_FirestoreKey(index: self.currentIndex)) + self.decoder.codingPath.append(_FirebaseKey(index: self.currentIndex)) defer { self.decoder.codingPath.removeLast() } guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Double.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirestoreKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) } self.currentIndex += 1 @@ -678,14 +678,14 @@ fileprivate struct _FirestoreUnkeyedDecodingContainer : UnkeyedDecodingContainer public mutating func decode(_ type: String.Type) throws -> String { guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirestoreKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) } - self.decoder.codingPath.append(_FirestoreKey(index: self.currentIndex)) + self.decoder.codingPath.append(_FirebaseKey(index: self.currentIndex)) defer { self.decoder.codingPath.removeLast() } guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: String.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirestoreKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) } self.currentIndex += 1 @@ -694,14 +694,14 @@ fileprivate struct _FirestoreUnkeyedDecodingContainer : UnkeyedDecodingContainer public mutating func decode(_ type: T.Type) throws -> T { guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirestoreKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) } - self.decoder.codingPath.append(_FirestoreKey(index: self.currentIndex)) + self.decoder.codingPath.append(_FirebaseKey(index: self.currentIndex)) defer { self.decoder.codingPath.removeLast() } guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: T.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirestoreKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) } self.currentIndex += 1 @@ -709,7 +709,7 @@ fileprivate struct _FirestoreUnkeyedDecodingContainer : UnkeyedDecodingContainer } public mutating func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer { - self.decoder.codingPath.append(_FirestoreKey(index: self.currentIndex)) + self.decoder.codingPath.append(_FirebaseKey(index: self.currentIndex)) defer { self.decoder.codingPath.removeLast() } guard !self.isAtEnd else { @@ -731,12 +731,12 @@ fileprivate struct _FirestoreUnkeyedDecodingContainer : UnkeyedDecodingContainer } self.currentIndex += 1 - let container = _FirestoreKeyedDecodingContainer(referencing: self.decoder, wrapping: dictionary) + let container = _FirebaseKeyedDecodingContainer(referencing: self.decoder, wrapping: dictionary) return KeyedDecodingContainer(container) } public mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { - self.decoder.codingPath.append(_FirestoreKey(index: self.currentIndex)) + self.decoder.codingPath.append(_FirebaseKey(index: self.currentIndex)) defer { self.decoder.codingPath.removeLast() } guard !self.isAtEnd else { @@ -758,11 +758,11 @@ fileprivate struct _FirestoreUnkeyedDecodingContainer : UnkeyedDecodingContainer } self.currentIndex += 1 - return _FirestoreUnkeyedDecodingContainer(referencing: self.decoder, wrapping: array) + return _FirebaseUnkeyedDecodingContainer(referencing: self.decoder, wrapping: array) } public mutating func superDecoder() throws -> Decoder { - self.decoder.codingPath.append(_FirestoreKey(index: self.currentIndex)) + self.decoder.codingPath.append(_FirebaseKey(index: self.currentIndex)) defer { self.decoder.codingPath.removeLast() } guard !self.isAtEnd else { @@ -772,11 +772,11 @@ fileprivate struct _FirestoreUnkeyedDecodingContainer : UnkeyedDecodingContainer let value = self.container[self.currentIndex] self.currentIndex += 1 - return _FirestoreDecoder(referencing: value, at: self.decoder.codingPath) + return _FirebaseDecoder(referencing: value, at: self.decoder.codingPath) } } -extension _FirestoreDecoder : SingleValueDecodingContainer { +extension _FirebaseDecoder : SingleValueDecodingContainer { // MARK: SingleValueDecodingContainer Methods private func expectNonNull(_ type: T.Type) throws { guard !decodeNil() else { @@ -864,7 +864,7 @@ extension _FirestoreDecoder : SingleValueDecodingContainer { } } -extension _FirestoreDecoder { +extension _FirebaseDecoder { /// Returns the given value unboxed from a container. fileprivate func unbox(_ value: Any, as type: Bool.Type) throws -> Bool? { guard !(value is NSNull) else { return nil } diff --git a/CodableFirebase/FirestoreEncoder.swift b/CodableFirebase/FirestoreEncoder.swift index 87ddd2a..0e8b6fc 100644 --- a/CodableFirebase/FirestoreEncoder.swift +++ b/CodableFirebase/FirestoreEncoder.swift @@ -202,7 +202,7 @@ fileprivate struct _FirestoreKeyedEncodingContainer : KeyedEncodi } public mutating func superEncoder() -> Encoder { - return _FirestoreReferencingEncoder(referencing: encoder, at: _FirestoreKey.super, wrapping: container) + return _FirestoreReferencingEncoder(referencing: encoder, at: _FirebaseKey.super, wrapping: container) } public mutating func superEncoder(forKey key: Key) -> Encoder { @@ -252,13 +252,13 @@ fileprivate struct _FirestoreUnkeyedEncodingContainer : UnkeyedEncodingContainer public mutating func encode(_ value: String) throws { container.add(self.encoder.box(value)) } public mutating func encode(_ value: T) throws { - encoder.codingPath.append(_FirestoreKey(index: count)) + encoder.codingPath.append(_FirebaseKey(index: count)) defer { encoder.codingPath.removeLast() } container.add(try encoder.box(value)) } public mutating func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer { - self.codingPath.append(_FirestoreKey(index: self.count)) + self.codingPath.append(_FirebaseKey(index: self.count)) defer { self.codingPath.removeLast() } let dictionary = NSMutableDictionary() @@ -269,7 +269,7 @@ fileprivate struct _FirestoreUnkeyedEncodingContainer : UnkeyedEncodingContainer } public mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { - self.codingPath.append(_FirestoreKey(index: self.count)) + self.codingPath.append(_FirebaseKey(index: self.count)) defer { self.codingPath.removeLast() } let array = NSMutableArray() @@ -282,7 +282,7 @@ fileprivate struct _FirestoreUnkeyedEncodingContainer : UnkeyedEncodingContainer } } -struct _FirestoreKey : CodingKey { +struct _FirebaseKey : CodingKey { public var stringValue: String public var intValue: Int? @@ -301,7 +301,7 @@ struct _FirestoreKey : CodingKey { self.intValue = index } - static let `super` = _FirestoreKey(stringValue: "super")! + static let `super` = _FirebaseKey(stringValue: "super")! } extension _FirestoreEncoder { @@ -458,7 +458,7 @@ fileprivate class _FirestoreReferencingEncoder : _FirestoreEncoder { self.reference = .array(array, index) super.init(codingPath: encoder.codingPath) - self.codingPath.append(_FirestoreKey(index: index)) + self.codingPath.append(_FirebaseKey(index: index)) } /// Initializes `self` by referencing the given dictionary container in the given encoder. From f339062e3e7a64c0854dc98475c70d205286df1f Mon Sep 17 00:00:00 2001 From: Oleksii Dykan Date: Wed, 27 Dec 2017 12:00:48 +0100 Subject: [PATCH 4/6] Move firebase encoder to separate file --- CodableFirebase.xcodeproj/project.pbxproj | 4 + CodableFirebase/Encoder.swift | 494 ++++++++++++++++++++++ CodableFirebase/FirestoreEncoder.swift | 486 --------------------- 3 files changed, 498 insertions(+), 486 deletions(-) create mode 100644 CodableFirebase/Encoder.swift diff --git a/CodableFirebase.xcodeproj/project.pbxproj b/CodableFirebase.xcodeproj/project.pbxproj index 139459e..8c37632 100644 --- a/CodableFirebase.xcodeproj/project.pbxproj +++ b/CodableFirebase.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ CE7DD3831F9D04AE000225C5 /* FirestoreEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7DD3811F9D04AE000225C5 /* FirestoreEncoder.swift */; }; CE7DD3841F9D04AE000225C5 /* FirestoreDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7DD3821F9D04AE000225C5 /* FirestoreDecoder.swift */; }; CE7DD3861F9DE4F7000225C5 /* TestCodableFirestore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7DD3851F9DE4F7000225C5 /* TestCodableFirestore.swift */; }; + CEFDBF821FF3B35B00745EBE /* Encoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFDBF811FF3B35B00745EBE /* Encoder.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -33,6 +34,7 @@ CE7DD3811F9D04AE000225C5 /* FirestoreEncoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirestoreEncoder.swift; sourceTree = ""; }; CE7DD3821F9D04AE000225C5 /* FirestoreDecoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirestoreDecoder.swift; sourceTree = ""; }; CE7DD3851F9DE4F7000225C5 /* TestCodableFirestore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestCodableFirestore.swift; sourceTree = ""; }; + CEFDBF811FF3B35B00745EBE /* Encoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encoder.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -78,6 +80,7 @@ CE7DD36A1F9CFA81000225C5 /* CodableFirebase.h */, CE7DD3821F9D04AE000225C5 /* FirestoreDecoder.swift */, CE7DD3811F9D04AE000225C5 /* FirestoreEncoder.swift */, + CEFDBF811FF3B35B00745EBE /* Encoder.swift */, CE7DD36B1F9CFA81000225C5 /* Info.plist */, ); path = CodableFirebase; @@ -204,6 +207,7 @@ buildActionMask = 2147483647; files = ( CE7DD3831F9D04AE000225C5 /* FirestoreEncoder.swift in Sources */, + CEFDBF821FF3B35B00745EBE /* Encoder.swift in Sources */, CE7DD3841F9D04AE000225C5 /* FirestoreDecoder.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/CodableFirebase/Encoder.swift b/CodableFirebase/Encoder.swift new file mode 100644 index 0000000..57b603e --- /dev/null +++ b/CodableFirebase/Encoder.swift @@ -0,0 +1,494 @@ +// +// Encoder.swift +// CodableFirebase +// +// Created by Oleksii on 27/12/2017. +// Copyright © 2017 ViolentOctopus. All rights reserved. +// + +import Foundation + +class _FirestoreEncoder : Encoder { + fileprivate var storage: _FirestoreEncodingStorage + fileprivate(set) public var codingPath: [CodingKey] + public var userInfo: [CodingUserInfoKey : Any] + + init(codingPath: [CodingKey] = []) { + self.storage = _FirestoreEncodingStorage() + self.codingPath = codingPath + userInfo = [:] + } + + /// Returns whether a new element can be encoded at this coding path. + /// + /// `true` if an element has not yet been encoded at this coding path; `false` otherwise. + fileprivate var canEncodeNewValue: Bool { + // Every time a new value gets encoded, the key it's encoded for is pushed onto the coding path (even if it's a nil key from an unkeyed container). + // At the same time, every time a container is requested, a new value gets pushed onto the storage stack. + // If there are more values on the storage stack than on the coding path, it means the value is requesting more than one container, which violates the precondition. + // + // This means that anytime something that can request a new container goes onto the stack, we MUST push a key onto the coding path. + // Things which will not request containers do not need to have the coding path extended for them (but it doesn't matter if it is, because they will not reach here). + return self.storage.count == self.codingPath.count + } + + // MARK: - Encoder Methods + public func container(keyedBy: Key.Type) -> KeyedEncodingContainer { + // If an existing keyed container was already requested, return that one. + let topContainer: NSMutableDictionary + if canEncodeNewValue { + // We haven't yet pushed a container at this level; do so here. + topContainer = storage.pushKeyedContainer() + } else { + guard let container = self.storage.containers.last as? NSMutableDictionary else { + preconditionFailure("Attempt to push new keyed encoding container when already previously encoded at this path.") + } + + topContainer = container + } + + let container = _FirestoreKeyedEncodingContainer(referencing: self, codingPath: self.codingPath, wrapping: topContainer) + return KeyedEncodingContainer(container) + } + + public func unkeyedContainer() -> UnkeyedEncodingContainer { + // If an existing unkeyed container was already requested, return that one. + let topContainer: NSMutableArray + if canEncodeNewValue { + // We haven't yet pushed a container at this level; do so here. + topContainer = self.storage.pushUnkeyedContainer() + } else { + guard let container = self.storage.containers.last as? NSMutableArray else { + preconditionFailure("Attempt to push new unkeyed encoding container when already previously encoded at this path.") + } + + topContainer = container + } + + return _FirestoreUnkeyedEncodingContainer(referencing: self, codingPath: self.codingPath, wrapping: topContainer) + } + + public func singleValueContainer() -> SingleValueEncodingContainer { + return self + } +} + +fileprivate struct _FirestoreEncodingStorage { + // MARK: Properties + /// The container stack. + /// Elements may be any one of the plist types (NSNumber, NSString, NSDate, NSArray, NSDictionary). + private(set) fileprivate var containers: [NSObject] = [] + + // MARK: - Initialization + /// Initializes `self` with no containers. + fileprivate init() {} + + // MARK: - Modifying the Stack + fileprivate var count: Int { + return containers.count + } + + fileprivate mutating func pushKeyedContainer() -> NSMutableDictionary { + let dictionary = NSMutableDictionary() + containers.append(dictionary) + return dictionary + } + + fileprivate mutating func pushUnkeyedContainer() -> NSMutableArray { + let array = NSMutableArray() + containers.append(array) + return array + } + + fileprivate mutating func push(container: NSObject) { + containers.append(container) + } + + fileprivate mutating func popContainer() -> NSObject { + precondition(containers.count > 0, "Empty container stack.") + return containers.popLast()! + } +} + +fileprivate struct _FirestoreKeyedEncodingContainer : KeyedEncodingContainerProtocol { + typealias Key = K + + // MARK: Properties + /// A reference to the encoder we're writing to. + private let encoder: _FirestoreEncoder + + /// A reference to the container we're writing to. + private let container: NSMutableDictionary + + /// The path of coding keys taken to get to this point in encoding. + private(set) public var codingPath: [CodingKey] + + // MARK: - Initialization + /// Initializes `self` with the given references. + fileprivate init(referencing encoder: _FirestoreEncoder, codingPath: [CodingKey], wrapping container: NSMutableDictionary) { + self.encoder = encoder + self.codingPath = codingPath + self.container = container + } + + // MARK: - KeyedEncodingContainerProtocol Methods + public mutating func encodeNil(forKey key: Key) throws { container[key.stringValue] = NSNull() } + public mutating func encode(_ value: Bool, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: Int, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: Int8, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: Int16, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: Int32, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: Int64, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: UInt, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: UInt8, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: UInt16, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: UInt32, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: UInt64, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: String, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: Float, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + public mutating func encode(_ value: Double, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } + + public mutating func encode(_ value: T, forKey key: Key) throws { + encoder.codingPath.append(key) + defer { encoder.codingPath.removeLast() } + container[key.stringValue] = try encoder.box(value) + } + + public mutating func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer { + let dictionary = NSMutableDictionary() + self.container[key.stringValue] = dictionary + + codingPath.append(key) + defer { codingPath.removeLast() } + + let container = _FirestoreKeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: dictionary) + return KeyedEncodingContainer(container) + } + + public mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { + let array = NSMutableArray() + container[key.stringValue] = array + + codingPath.append(key) + defer { codingPath.removeLast() } + return _FirestoreUnkeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: array) + } + + public mutating func superEncoder() -> Encoder { + return _FirestoreReferencingEncoder(referencing: encoder, at: _FirebaseKey.super, wrapping: container) + } + + public mutating func superEncoder(forKey key: Key) -> Encoder { + return _FirestoreReferencingEncoder(referencing: encoder, at: key, wrapping: container) + } +} + +fileprivate struct _FirestoreUnkeyedEncodingContainer : UnkeyedEncodingContainer { + // MARK: Properties + /// A reference to the encoder we're writing to. + private let encoder: _FirestoreEncoder + + /// A reference to the container we're writing to. + private let container: NSMutableArray + + /// The path of coding keys taken to get to this point in encoding. + private(set) public var codingPath: [CodingKey] + + /// The number of elements encoded into the container. + public var count: Int { + return container.count + } + + // MARK: - Initialization + /// Initializes `self` with the given references. + fileprivate init(referencing encoder: _FirestoreEncoder, codingPath: [CodingKey], wrapping container: NSMutableArray) { + self.encoder = encoder + self.codingPath = codingPath + self.container = container + } + + // MARK: - UnkeyedEncodingContainer Methods + public mutating func encodeNil() throws { container.add(NSNull()) } + public mutating func encode(_ value: Bool) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: Int) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: Int8) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: Int16) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: Int32) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: Int64) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: UInt) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: UInt8) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: UInt16) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: UInt32) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: UInt64) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: Float) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: Double) throws { container.add(self.encoder.box(value)) } + public mutating func encode(_ value: String) throws { container.add(self.encoder.box(value)) } + + public mutating func encode(_ value: T) throws { + encoder.codingPath.append(_FirebaseKey(index: count)) + defer { encoder.codingPath.removeLast() } + container.add(try encoder.box(value)) + } + + public mutating func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer { + self.codingPath.append(_FirebaseKey(index: self.count)) + defer { self.codingPath.removeLast() } + + let dictionary = NSMutableDictionary() + self.container.add(dictionary) + + let container = _FirestoreKeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: dictionary) + return KeyedEncodingContainer(container) + } + + public mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { + self.codingPath.append(_FirebaseKey(index: self.count)) + defer { self.codingPath.removeLast() } + + let array = NSMutableArray() + self.container.add(array) + return _FirestoreUnkeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: array) + } + + public mutating func superEncoder() -> Encoder { + return _FirestoreReferencingEncoder(referencing: encoder, at: container.count, wrapping: container) + } +} + +struct _FirebaseKey : CodingKey { + public var stringValue: String + public var intValue: Int? + + public init?(stringValue: String) { + self.stringValue = stringValue + self.intValue = nil + } + + public init?(intValue: Int) { + self.stringValue = "\(intValue)" + self.intValue = intValue + } + + init(index: Int) { + self.stringValue = "Index \(index)" + self.intValue = index + } + + static let `super` = _FirebaseKey(stringValue: "super")! +} + +extension _FirestoreEncoder { + + /// Returns the given value boxed in a container appropriate for pushing onto the container stack. + fileprivate func box(_ value: Bool) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: Int) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: Int8) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: Int16) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: Int32) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: Int64) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: UInt) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: UInt8) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: UInt16) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: UInt32) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: UInt64) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: Float) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: Double) -> NSObject { return NSNumber(value: value) } + fileprivate func box(_ value: String) -> NSObject { return NSString(string: value) } + + fileprivate func box(_ value: T) throws -> NSObject { + return try self.box_(value) ?? NSDictionary() + } + + func box_(_ value: T) throws -> NSObject? { + if T.self == Date.self || T.self == NSDate.self { + return (value as! NSDate) + } else if T.self == Data.self || T.self == NSData.self { + return (value as! NSData) + } + + // The value should request a container from the _FirestoreEncoder. + let depth = storage.count + try value.encode(to: self) + + // The top container should be a new container. + guard storage.count > depth else { + return nil + } + + return storage.popContainer() + } +} + +extension _FirestoreEncoder : SingleValueEncodingContainer { + // MARK: - SingleValueEncodingContainer Methods + private func assertCanEncodeNewValue() { + precondition(canEncodeNewValue, "Attempt to encode value through single value container when previously value already encoded.") + } + + public func encodeNil() throws { + assertCanEncodeNewValue() + storage.push(container: NSNull()) + } + + public func encode(_ value: Bool) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int8) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int16) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int32) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int64) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt8) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt16) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt32) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt64) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: String) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Float) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Double) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: T) throws { + assertCanEncodeNewValue() + try storage.push(container: box(value)) + } +} + +fileprivate class _FirestoreReferencingEncoder : _FirestoreEncoder { + // MARK: Reference types. + /// The type of container we're referencing. + private enum Reference { + /// Referencing a specific index in an array container. + case array(NSMutableArray, Int) + + /// Referencing a specific key in a dictionary container. + case dictionary(NSMutableDictionary, String) + } + + // MARK: - Properties + /// The encoder we're referencing. + private let encoder: _FirestoreEncoder + + /// The container reference itself. + private let reference: Reference + + // MARK: - Initialization + /// Initializes `self` by referencing the given array container in the given encoder. + fileprivate init(referencing encoder: _FirestoreEncoder, at index: Int, wrapping array: NSMutableArray) { + self.encoder = encoder + self.reference = .array(array, index) + super.init(codingPath: encoder.codingPath) + + self.codingPath.append(_FirebaseKey(index: index)) + } + + /// Initializes `self` by referencing the given dictionary container in the given encoder. + fileprivate init(referencing encoder: _FirestoreEncoder, at key: CodingKey, wrapping dictionary: NSMutableDictionary) { + self.encoder = encoder + reference = .dictionary(dictionary, key.stringValue) + super.init(codingPath: encoder.codingPath) + codingPath.append(key) + } + + // MARK: - Coding Path Operations + fileprivate override var canEncodeNewValue: Bool { + // With a regular encoder, the storage and coding path grow together. + // A referencing encoder, however, inherits its parents coding path, as well as the key it was created for. + // We have to take this into account. + return storage.count == codingPath.count - encoder.codingPath.count - 1 + } + + // MARK: - Deinitialization + // Finalizes `self` by writing the contents of our storage to the referenced encoder's storage. + deinit { + let value: Any + switch storage.count { + case 0: value = NSDictionary() + case 1: value = self.storage.popContainer() + default: fatalError("Referencing encoder deallocated with multiple containers on stack.") + } + + switch self.reference { + case .array(let array, let index): + array.insert(value, at: index) + + case .dictionary(let dictionary, let key): + dictionary[NSString(string: key)] = value + } + } +} + +internal extension DecodingError { + internal static func _typeMismatch(at path: [CodingKey], expectation: Any.Type, reality: Any) -> DecodingError { + let description = "Expected to decode \(expectation) but found \(_typeDescription(of: reality)) instead." + return .typeMismatch(expectation, Context(codingPath: path, debugDescription: description)) + } + + fileprivate static func _typeDescription(of value: Any) -> String { + if value is NSNull { + return "a null value" + } else if value is NSNumber /* FIXME: If swift-corelibs-foundation isn't updated to use NSNumber, this check will be necessary: || value is Int || value is Double */ { + return "a number" + } else if value is String { + return "a string/data" + } else if value is [Any] { + return "an array" + } else if value is [String : Any] { + return "a dictionary" + } else { + return "\(type(of: value))" + } + } +} diff --git a/CodableFirebase/FirestoreEncoder.swift b/CodableFirebase/FirestoreEncoder.swift index 0e8b6fc..142b55c 100644 --- a/CodableFirebase/FirestoreEncoder.swift +++ b/CodableFirebase/FirestoreEncoder.swift @@ -34,489 +34,3 @@ open class FirestoreEncoder { return topLevel } } - -fileprivate class _FirestoreEncoder : Encoder { - fileprivate var storage: _FirestoreEncodingStorage - fileprivate(set) public var codingPath: [CodingKey] - public var userInfo: [CodingUserInfoKey : Any] - - fileprivate init(codingPath: [CodingKey] = []) { - self.storage = _FirestoreEncodingStorage() - self.codingPath = codingPath - userInfo = [:] - } - - /// Returns whether a new element can be encoded at this coding path. - /// - /// `true` if an element has not yet been encoded at this coding path; `false` otherwise. - fileprivate var canEncodeNewValue: Bool { - // Every time a new value gets encoded, the key it's encoded for is pushed onto the coding path (even if it's a nil key from an unkeyed container). - // At the same time, every time a container is requested, a new value gets pushed onto the storage stack. - // If there are more values on the storage stack than on the coding path, it means the value is requesting more than one container, which violates the precondition. - // - // This means that anytime something that can request a new container goes onto the stack, we MUST push a key onto the coding path. - // Things which will not request containers do not need to have the coding path extended for them (but it doesn't matter if it is, because they will not reach here). - return self.storage.count == self.codingPath.count - } - - // MARK: - Encoder Methods - public func container(keyedBy: Key.Type) -> KeyedEncodingContainer { - // If an existing keyed container was already requested, return that one. - let topContainer: NSMutableDictionary - if canEncodeNewValue { - // We haven't yet pushed a container at this level; do so here. - topContainer = storage.pushKeyedContainer() - } else { - guard let container = self.storage.containers.last as? NSMutableDictionary else { - preconditionFailure("Attempt to push new keyed encoding container when already previously encoded at this path.") - } - - topContainer = container - } - - let container = _FirestoreKeyedEncodingContainer(referencing: self, codingPath: self.codingPath, wrapping: topContainer) - return KeyedEncodingContainer(container) - } - - public func unkeyedContainer() -> UnkeyedEncodingContainer { - // If an existing unkeyed container was already requested, return that one. - let topContainer: NSMutableArray - if canEncodeNewValue { - // We haven't yet pushed a container at this level; do so here. - topContainer = self.storage.pushUnkeyedContainer() - } else { - guard let container = self.storage.containers.last as? NSMutableArray else { - preconditionFailure("Attempt to push new unkeyed encoding container when already previously encoded at this path.") - } - - topContainer = container - } - - return _FirestoreUnkeyedEncodingContainer(referencing: self, codingPath: self.codingPath, wrapping: topContainer) - } - - public func singleValueContainer() -> SingleValueEncodingContainer { - return self - } -} - -fileprivate struct _FirestoreEncodingStorage { - // MARK: Properties - /// The container stack. - /// Elements may be any one of the plist types (NSNumber, NSString, NSDate, NSArray, NSDictionary). - private(set) fileprivate var containers: [NSObject] = [] - - // MARK: - Initialization - /// Initializes `self` with no containers. - fileprivate init() {} - - // MARK: - Modifying the Stack - fileprivate var count: Int { - return containers.count - } - - fileprivate mutating func pushKeyedContainer() -> NSMutableDictionary { - let dictionary = NSMutableDictionary() - containers.append(dictionary) - return dictionary - } - - fileprivate mutating func pushUnkeyedContainer() -> NSMutableArray { - let array = NSMutableArray() - containers.append(array) - return array - } - - fileprivate mutating func push(container: NSObject) { - containers.append(container) - } - - fileprivate mutating func popContainer() -> NSObject { - precondition(containers.count > 0, "Empty container stack.") - return containers.popLast()! - } -} - -fileprivate struct _FirestoreKeyedEncodingContainer : KeyedEncodingContainerProtocol { - typealias Key = K - - // MARK: Properties - /// A reference to the encoder we're writing to. - private let encoder: _FirestoreEncoder - - /// A reference to the container we're writing to. - private let container: NSMutableDictionary - - /// The path of coding keys taken to get to this point in encoding. - private(set) public var codingPath: [CodingKey] - - // MARK: - Initialization - /// Initializes `self` with the given references. - fileprivate init(referencing encoder: _FirestoreEncoder, codingPath: [CodingKey], wrapping container: NSMutableDictionary) { - self.encoder = encoder - self.codingPath = codingPath - self.container = container - } - - // MARK: - KeyedEncodingContainerProtocol Methods - public mutating func encodeNil(forKey key: Key) throws { container[key.stringValue] = NSNull() } - public mutating func encode(_ value: Bool, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: Int, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: Int8, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: Int16, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: Int32, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: Int64, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: UInt, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: UInt8, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: UInt16, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: UInt32, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: UInt64, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: String, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: Float, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - public mutating func encode(_ value: Double, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) } - - public mutating func encode(_ value: T, forKey key: Key) throws { - encoder.codingPath.append(key) - defer { encoder.codingPath.removeLast() } - container[key.stringValue] = try encoder.box(value) - } - - public mutating func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer { - let dictionary = NSMutableDictionary() - self.container[key.stringValue] = dictionary - - codingPath.append(key) - defer { codingPath.removeLast() } - - let container = _FirestoreKeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: dictionary) - return KeyedEncodingContainer(container) - } - - public mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { - let array = NSMutableArray() - container[key.stringValue] = array - - codingPath.append(key) - defer { codingPath.removeLast() } - return _FirestoreUnkeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: array) - } - - public mutating func superEncoder() -> Encoder { - return _FirestoreReferencingEncoder(referencing: encoder, at: _FirebaseKey.super, wrapping: container) - } - - public mutating func superEncoder(forKey key: Key) -> Encoder { - return _FirestoreReferencingEncoder(referencing: encoder, at: key, wrapping: container) - } -} - -fileprivate struct _FirestoreUnkeyedEncodingContainer : UnkeyedEncodingContainer { - // MARK: Properties - /// A reference to the encoder we're writing to. - private let encoder: _FirestoreEncoder - - /// A reference to the container we're writing to. - private let container: NSMutableArray - - /// The path of coding keys taken to get to this point in encoding. - private(set) public var codingPath: [CodingKey] - - /// The number of elements encoded into the container. - public var count: Int { - return container.count - } - - // MARK: - Initialization - /// Initializes `self` with the given references. - fileprivate init(referencing encoder: _FirestoreEncoder, codingPath: [CodingKey], wrapping container: NSMutableArray) { - self.encoder = encoder - self.codingPath = codingPath - self.container = container - } - - // MARK: - UnkeyedEncodingContainer Methods - public mutating func encodeNil() throws { container.add(NSNull()) } - public mutating func encode(_ value: Bool) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: Int) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: Int8) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: Int16) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: Int32) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: Int64) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: UInt) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: UInt8) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: UInt16) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: UInt32) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: UInt64) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: Float) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: Double) throws { container.add(self.encoder.box(value)) } - public mutating func encode(_ value: String) throws { container.add(self.encoder.box(value)) } - - public mutating func encode(_ value: T) throws { - encoder.codingPath.append(_FirebaseKey(index: count)) - defer { encoder.codingPath.removeLast() } - container.add(try encoder.box(value)) - } - - public mutating func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer { - self.codingPath.append(_FirebaseKey(index: self.count)) - defer { self.codingPath.removeLast() } - - let dictionary = NSMutableDictionary() - self.container.add(dictionary) - - let container = _FirestoreKeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: dictionary) - return KeyedEncodingContainer(container) - } - - public mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { - self.codingPath.append(_FirebaseKey(index: self.count)) - defer { self.codingPath.removeLast() } - - let array = NSMutableArray() - self.container.add(array) - return _FirestoreUnkeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: array) - } - - public mutating func superEncoder() -> Encoder { - return _FirestoreReferencingEncoder(referencing: encoder, at: container.count, wrapping: container) - } -} - -struct _FirebaseKey : CodingKey { - public var stringValue: String - public var intValue: Int? - - public init?(stringValue: String) { - self.stringValue = stringValue - self.intValue = nil - } - - public init?(intValue: Int) { - self.stringValue = "\(intValue)" - self.intValue = intValue - } - - init(index: Int) { - self.stringValue = "Index \(index)" - self.intValue = index - } - - static let `super` = _FirebaseKey(stringValue: "super")! -} - -extension _FirestoreEncoder { - - /// Returns the given value boxed in a container appropriate for pushing onto the container stack. - fileprivate func box(_ value: Bool) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Int) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Int8) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Int16) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Int32) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Int64) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: UInt) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: UInt8) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: UInt16) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: UInt32) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: UInt64) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Float) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Double) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: String) -> NSObject { return NSString(string: value) } - - fileprivate func box(_ value: T) throws -> NSObject { - return try self.box_(value) ?? NSDictionary() - } - - fileprivate func box_(_ value: T) throws -> NSObject? { - if T.self == Date.self || T.self == NSDate.self { - return (value as! NSDate) - } else if T.self == Data.self || T.self == NSData.self { - return (value as! NSData) - } - - // The value should request a container from the _FirestoreEncoder. - let depth = storage.count - try value.encode(to: self) - - // The top container should be a new container. - guard storage.count > depth else { - return nil - } - - return storage.popContainer() - } -} - -extension _FirestoreEncoder : SingleValueEncodingContainer { - // MARK: - SingleValueEncodingContainer Methods - private func assertCanEncodeNewValue() { - precondition(canEncodeNewValue, "Attempt to encode value through single value container when previously value already encoded.") - } - - public func encodeNil() throws { - assertCanEncodeNewValue() - storage.push(container: NSNull()) - } - - public func encode(_ value: Bool) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: Int) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: Int8) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: Int16) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: Int32) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: Int64) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: UInt) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: UInt8) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: UInt16) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: UInt32) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: UInt64) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: String) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: Float) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: Double) throws { - assertCanEncodeNewValue() - storage.push(container: box(value)) - } - - public func encode(_ value: T) throws { - assertCanEncodeNewValue() - try storage.push(container: box(value)) - } -} - -fileprivate class _FirestoreReferencingEncoder : _FirestoreEncoder { - // MARK: Reference types. - /// The type of container we're referencing. - private enum Reference { - /// Referencing a specific index in an array container. - case array(NSMutableArray, Int) - - /// Referencing a specific key in a dictionary container. - case dictionary(NSMutableDictionary, String) - } - - // MARK: - Properties - /// The encoder we're referencing. - private let encoder: _FirestoreEncoder - - /// The container reference itself. - private let reference: Reference - - // MARK: - Initialization - /// Initializes `self` by referencing the given array container in the given encoder. - fileprivate init(referencing encoder: _FirestoreEncoder, at index: Int, wrapping array: NSMutableArray) { - self.encoder = encoder - self.reference = .array(array, index) - super.init(codingPath: encoder.codingPath) - - self.codingPath.append(_FirebaseKey(index: index)) - } - - /// Initializes `self` by referencing the given dictionary container in the given encoder. - fileprivate init(referencing encoder: _FirestoreEncoder, at key: CodingKey, wrapping dictionary: NSMutableDictionary) { - self.encoder = encoder - reference = .dictionary(dictionary, key.stringValue) - super.init(codingPath: encoder.codingPath) - codingPath.append(key) - } - - // MARK: - Coding Path Operations - fileprivate override var canEncodeNewValue: Bool { - // With a regular encoder, the storage and coding path grow together. - // A referencing encoder, however, inherits its parents coding path, as well as the key it was created for. - // We have to take this into account. - return storage.count == codingPath.count - encoder.codingPath.count - 1 - } - - // MARK: - Deinitialization - // Finalizes `self` by writing the contents of our storage to the referenced encoder's storage. - deinit { - let value: Any - switch storage.count { - case 0: value = NSDictionary() - case 1: value = self.storage.popContainer() - default: fatalError("Referencing encoder deallocated with multiple containers on stack.") - } - - switch self.reference { - case .array(let array, let index): - array.insert(value, at: index) - - case .dictionary(let dictionary, let key): - dictionary[NSString(string: key)] = value - } - } -} - -internal extension DecodingError { - internal static func _typeMismatch(at path: [CodingKey], expectation: Any.Type, reality: Any) -> DecodingError { - let description = "Expected to decode \(expectation) but found \(_typeDescription(of: reality)) instead." - return .typeMismatch(expectation, Context(codingPath: path, debugDescription: description)) - } - - fileprivate static func _typeDescription(of value: Any) -> String { - if value is NSNull { - return "a null value" - } else if value is NSNumber /* FIXME: If swift-corelibs-foundation isn't updated to use NSNumber, this check will be necessary: || value is Int || value is Double */ { - return "a number" - } else if value is String { - return "a string/data" - } else if value is [Any] { - return "an array" - } else if value is [String : Any] { - return "a dictionary" - } else { - return "\(type(of: value))" - } - } -} - From 3029b533169250a6d3215e6ca168d2254a4af4d8 Mon Sep 17 00:00:00 2001 From: Oleksii Dykan Date: Wed, 27 Dec 2017 12:02:41 +0100 Subject: [PATCH 5/6] Rename firestore to firebase --- CodableFirebase/Encoder.swift | 52 +++++++++++++------------- CodableFirebase/FirestoreEncoder.swift | 2 +- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/CodableFirebase/Encoder.swift b/CodableFirebase/Encoder.swift index 57b603e..4c76dba 100644 --- a/CodableFirebase/Encoder.swift +++ b/CodableFirebase/Encoder.swift @@ -8,13 +8,13 @@ import Foundation -class _FirestoreEncoder : Encoder { - fileprivate var storage: _FirestoreEncodingStorage +class _FirebaseEncoder : Encoder { + fileprivate var storage: _FirebaseEncodingStorage fileprivate(set) public var codingPath: [CodingKey] public var userInfo: [CodingUserInfoKey : Any] init(codingPath: [CodingKey] = []) { - self.storage = _FirestoreEncodingStorage() + self.storage = _FirebaseEncodingStorage() self.codingPath = codingPath userInfo = [:] } @@ -47,7 +47,7 @@ class _FirestoreEncoder : Encoder { topContainer = container } - let container = _FirestoreKeyedEncodingContainer(referencing: self, codingPath: self.codingPath, wrapping: topContainer) + let container = _FirebaseKeyedEncodingContainer(referencing: self, codingPath: self.codingPath, wrapping: topContainer) return KeyedEncodingContainer(container) } @@ -65,7 +65,7 @@ class _FirestoreEncoder : Encoder { topContainer = container } - return _FirestoreUnkeyedEncodingContainer(referencing: self, codingPath: self.codingPath, wrapping: topContainer) + return _FirebaseUnkeyedEncodingContainer(referencing: self, codingPath: self.codingPath, wrapping: topContainer) } public func singleValueContainer() -> SingleValueEncodingContainer { @@ -73,7 +73,7 @@ class _FirestoreEncoder : Encoder { } } -fileprivate struct _FirestoreEncodingStorage { +fileprivate struct _FirebaseEncodingStorage { // MARK: Properties /// The container stack. /// Elements may be any one of the plist types (NSNumber, NSString, NSDate, NSArray, NSDictionary). @@ -110,12 +110,12 @@ fileprivate struct _FirestoreEncodingStorage { } } -fileprivate struct _FirestoreKeyedEncodingContainer : KeyedEncodingContainerProtocol { +fileprivate struct _FirebaseKeyedEncodingContainer : KeyedEncodingContainerProtocol { typealias Key = K // MARK: Properties /// A reference to the encoder we're writing to. - private let encoder: _FirestoreEncoder + private let encoder: _FirebaseEncoder /// A reference to the container we're writing to. private let container: NSMutableDictionary @@ -125,7 +125,7 @@ fileprivate struct _FirestoreKeyedEncodingContainer : KeyedEncodi // MARK: - Initialization /// Initializes `self` with the given references. - fileprivate init(referencing encoder: _FirestoreEncoder, codingPath: [CodingKey], wrapping container: NSMutableDictionary) { + fileprivate init(referencing encoder: _FirebaseEncoder, codingPath: [CodingKey], wrapping container: NSMutableDictionary) { self.encoder = encoder self.codingPath = codingPath self.container = container @@ -161,7 +161,7 @@ fileprivate struct _FirestoreKeyedEncodingContainer : KeyedEncodi codingPath.append(key) defer { codingPath.removeLast() } - let container = _FirestoreKeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: dictionary) + let container = _FirebaseKeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: dictionary) return KeyedEncodingContainer(container) } @@ -171,22 +171,22 @@ fileprivate struct _FirestoreKeyedEncodingContainer : KeyedEncodi codingPath.append(key) defer { codingPath.removeLast() } - return _FirestoreUnkeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: array) + return _FirebaseUnkeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: array) } public mutating func superEncoder() -> Encoder { - return _FirestoreReferencingEncoder(referencing: encoder, at: _FirebaseKey.super, wrapping: container) + return _FirebaseReferencingEncoder(referencing: encoder, at: _FirebaseKey.super, wrapping: container) } public mutating func superEncoder(forKey key: Key) -> Encoder { - return _FirestoreReferencingEncoder(referencing: encoder, at: key, wrapping: container) + return _FirebaseReferencingEncoder(referencing: encoder, at: key, wrapping: container) } } -fileprivate struct _FirestoreUnkeyedEncodingContainer : UnkeyedEncodingContainer { +fileprivate struct _FirebaseUnkeyedEncodingContainer : UnkeyedEncodingContainer { // MARK: Properties /// A reference to the encoder we're writing to. - private let encoder: _FirestoreEncoder + private let encoder: _FirebaseEncoder /// A reference to the container we're writing to. private let container: NSMutableArray @@ -201,7 +201,7 @@ fileprivate struct _FirestoreUnkeyedEncodingContainer : UnkeyedEncodingContainer // MARK: - Initialization /// Initializes `self` with the given references. - fileprivate init(referencing encoder: _FirestoreEncoder, codingPath: [CodingKey], wrapping container: NSMutableArray) { + fileprivate init(referencing encoder: _FirebaseEncoder, codingPath: [CodingKey], wrapping container: NSMutableArray) { self.encoder = encoder self.codingPath = codingPath self.container = container @@ -237,7 +237,7 @@ fileprivate struct _FirestoreUnkeyedEncodingContainer : UnkeyedEncodingContainer let dictionary = NSMutableDictionary() self.container.add(dictionary) - let container = _FirestoreKeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: dictionary) + let container = _FirebaseKeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: dictionary) return KeyedEncodingContainer(container) } @@ -247,11 +247,11 @@ fileprivate struct _FirestoreUnkeyedEncodingContainer : UnkeyedEncodingContainer let array = NSMutableArray() self.container.add(array) - return _FirestoreUnkeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: array) + return _FirebaseUnkeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: array) } public mutating func superEncoder() -> Encoder { - return _FirestoreReferencingEncoder(referencing: encoder, at: container.count, wrapping: container) + return _FirebaseReferencingEncoder(referencing: encoder, at: container.count, wrapping: container) } } @@ -277,7 +277,7 @@ struct _FirebaseKey : CodingKey { static let `super` = _FirebaseKey(stringValue: "super")! } -extension _FirestoreEncoder { +extension _FirebaseEncoder { /// Returns the given value boxed in a container appropriate for pushing onto the container stack. fileprivate func box(_ value: Bool) -> NSObject { return NSNumber(value: value) } @@ -306,7 +306,7 @@ extension _FirestoreEncoder { return (value as! NSData) } - // The value should request a container from the _FirestoreEncoder. + // The value should request a container from the _FirebaseEncoder. let depth = storage.count try value.encode(to: self) @@ -319,7 +319,7 @@ extension _FirestoreEncoder { } } -extension _FirestoreEncoder : SingleValueEncodingContainer { +extension _FirebaseEncoder : SingleValueEncodingContainer { // MARK: - SingleValueEncodingContainer Methods private func assertCanEncodeNewValue() { precondition(canEncodeNewValue, "Attempt to encode value through single value container when previously value already encoded.") @@ -406,7 +406,7 @@ extension _FirestoreEncoder : SingleValueEncodingContainer { } } -fileprivate class _FirestoreReferencingEncoder : _FirestoreEncoder { +fileprivate class _FirebaseReferencingEncoder : _FirebaseEncoder { // MARK: Reference types. /// The type of container we're referencing. private enum Reference { @@ -419,14 +419,14 @@ fileprivate class _FirestoreReferencingEncoder : _FirestoreEncoder { // MARK: - Properties /// The encoder we're referencing. - private let encoder: _FirestoreEncoder + private let encoder: _FirebaseEncoder /// The container reference itself. private let reference: Reference // MARK: - Initialization /// Initializes `self` by referencing the given array container in the given encoder. - fileprivate init(referencing encoder: _FirestoreEncoder, at index: Int, wrapping array: NSMutableArray) { + fileprivate init(referencing encoder: _FirebaseEncoder, at index: Int, wrapping array: NSMutableArray) { self.encoder = encoder self.reference = .array(array, index) super.init(codingPath: encoder.codingPath) @@ -435,7 +435,7 @@ fileprivate class _FirestoreReferencingEncoder : _FirestoreEncoder { } /// Initializes `self` by referencing the given dictionary container in the given encoder. - fileprivate init(referencing encoder: _FirestoreEncoder, at key: CodingKey, wrapping dictionary: NSMutableDictionary) { + fileprivate init(referencing encoder: _FirebaseEncoder, at key: CodingKey, wrapping dictionary: NSMutableDictionary) { self.encoder = encoder reference = .dictionary(dictionary, key.stringValue) super.init(codingPath: encoder.codingPath) diff --git a/CodableFirebase/FirestoreEncoder.swift b/CodableFirebase/FirestoreEncoder.swift index 142b55c..0b86265 100644 --- a/CodableFirebase/FirestoreEncoder.swift +++ b/CodableFirebase/FirestoreEncoder.swift @@ -24,7 +24,7 @@ open class FirestoreEncoder { } internal func encodeToTopLevelContainer(_ value: Value) throws -> Any { - let encoder = _FirestoreEncoder() + let encoder = _FirebaseEncoder() guard let topLevel = try encoder.box_(value) else { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], From 31f708f215db4286f8470c8d73049950663501dc Mon Sep 17 00:00:00 2001 From: Oleksii Dykan Date: Wed, 27 Dec 2017 12:06:07 +0100 Subject: [PATCH 6/6] Move decoder to separate file --- CodableFirebase.xcodeproj/project.pbxproj | 4 + CodableFirebase/Decoder.swift | 1159 +++++++++++++++++++++ CodableFirebase/FirestoreDecoder.swift | 1150 -------------------- 3 files changed, 1163 insertions(+), 1150 deletions(-) create mode 100644 CodableFirebase/Decoder.swift diff --git a/CodableFirebase.xcodeproj/project.pbxproj b/CodableFirebase.xcodeproj/project.pbxproj index 8c37632..5416338 100644 --- a/CodableFirebase.xcodeproj/project.pbxproj +++ b/CodableFirebase.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ CE7DD3841F9D04AE000225C5 /* FirestoreDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7DD3821F9D04AE000225C5 /* FirestoreDecoder.swift */; }; CE7DD3861F9DE4F7000225C5 /* TestCodableFirestore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7DD3851F9DE4F7000225C5 /* TestCodableFirestore.swift */; }; CEFDBF821FF3B35B00745EBE /* Encoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFDBF811FF3B35B00745EBE /* Encoder.swift */; }; + CEFDBF861FF3B56200745EBE /* Decoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFDBF851FF3B56200745EBE /* Decoder.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -35,6 +36,7 @@ CE7DD3821F9D04AE000225C5 /* FirestoreDecoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirestoreDecoder.swift; sourceTree = ""; }; CE7DD3851F9DE4F7000225C5 /* TestCodableFirestore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestCodableFirestore.swift; sourceTree = ""; }; CEFDBF811FF3B35B00745EBE /* Encoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encoder.swift; sourceTree = ""; }; + CEFDBF851FF3B56200745EBE /* Decoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Decoder.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -80,6 +82,7 @@ CE7DD36A1F9CFA81000225C5 /* CodableFirebase.h */, CE7DD3821F9D04AE000225C5 /* FirestoreDecoder.swift */, CE7DD3811F9D04AE000225C5 /* FirestoreEncoder.swift */, + CEFDBF851FF3B56200745EBE /* Decoder.swift */, CEFDBF811FF3B35B00745EBE /* Encoder.swift */, CE7DD36B1F9CFA81000225C5 /* Info.plist */, ); @@ -208,6 +211,7 @@ files = ( CE7DD3831F9D04AE000225C5 /* FirestoreEncoder.swift in Sources */, CEFDBF821FF3B35B00745EBE /* Encoder.swift in Sources */, + CEFDBF861FF3B56200745EBE /* Decoder.swift in Sources */, CE7DD3841F9D04AE000225C5 /* FirestoreDecoder.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/CodableFirebase/Decoder.swift b/CodableFirebase/Decoder.swift new file mode 100644 index 0000000..4215399 --- /dev/null +++ b/CodableFirebase/Decoder.swift @@ -0,0 +1,1159 @@ +// +// Decoder.swift +// CodableFirebase +// +// Created by Oleksii on 27/12/2017. +// Copyright © 2017 ViolentOctopus. All rights reserved. +// + +import Foundation + +class _FirebaseDecoder : Decoder { + // MARK: Properties + /// The decoder's storage. + fileprivate var storage: _FirebaseDecodingStorage + + /// The path to the current point in encoding. + fileprivate(set) public var codingPath: [CodingKey] + + /// Contextual user-provided information for use during encoding. + public var userInfo: [CodingUserInfoKey : Any] + + // MARK: - Initialization + /// Initializes `self` with the given top-level container and options. + init(referencing container: Any, at codingPath: [CodingKey] = []) { + self.storage = _FirebaseDecodingStorage() + self.storage.push(container: container) + self.codingPath = codingPath + userInfo = [:] + } + + // MARK: - Decoder Methods + public func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer { + guard !(self.storage.topContainer is NSNull) else { + throw DecodingError.valueNotFound(KeyedDecodingContainer.self, + DecodingError.Context(codingPath: self.codingPath, + debugDescription: "Cannot get keyed decoding container -- found null value instead.")) + } + + guard let topContainer = self.storage.topContainer as? [String : Any] else { + let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Not a dictionary") + throw DecodingError.typeMismatch([String: Any].self, context) + } + + let container = _FirebaseKeyedDecodingContainer(referencing: self, wrapping: topContainer) + return KeyedDecodingContainer(container) + } + + public func unkeyedContainer() throws -> UnkeyedDecodingContainer { + guard !(self.storage.topContainer is NSNull) else { + throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self, + DecodingError.Context(codingPath: self.codingPath, + debugDescription: "Cannot get unkeyed decoding container -- found null value instead.")) + } + + guard let topContainer = self.storage.topContainer as? [Any] else { + let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Not an array") + throw DecodingError.typeMismatch([Any].self, context) + } + + return _FirebaseUnkeyedDecodingContainer(referencing: self, wrapping: topContainer) + } + + public func singleValueContainer() throws -> SingleValueDecodingContainer { + return self + } +} + +fileprivate struct _FirebaseDecodingStorage { + // MARK: Properties + /// The container stack. + /// Elements may be any one of the plist types (NSNumber, Date, String, Array, [String : Any]). + private(set) fileprivate var containers: [Any] = [] + + // MARK: - Initialization + /// Initializes `self` with no containers. + fileprivate init() {} + + // MARK: - Modifying the Stack + fileprivate var count: Int { + return containers.count + } + + fileprivate var topContainer: Any { + precondition(containers.count > 0, "Empty container stack.") + return containers.last! + } + + fileprivate mutating func push(container: Any) { + containers.append(container) + } + + fileprivate mutating func popContainer() { + precondition(containers.count > 0, "Empty container stack.") + containers.removeLast() + } +} + +fileprivate struct _FirebaseKeyedDecodingContainer : KeyedDecodingContainerProtocol { + typealias Key = K + + // MARK: Properties + /// A reference to the decoder we're reading from. + private let decoder: _FirebaseDecoder + + /// A reference to the container we're reading from. + private let container: [String : Any] + + /// The path of coding keys taken to get to this point in decoding. + private(set) public var codingPath: [CodingKey] + + // MARK: - Initialization + /// Initializes `self` by referencing the given decoder and container. + fileprivate init(referencing decoder: _FirebaseDecoder, wrapping container: [String : Any]) { + self.decoder = decoder + self.container = container + self.codingPath = decoder.codingPath + } + + // MARK: - KeyedDecodingContainerProtocol Methods + public var allKeys: [Key] { + return container.keys.flatMap { Key(stringValue: $0) } + } + + public func contains(_ key: Key) -> Bool { + return container[key.stringValue] != nil + } + + public func decodeNil(forKey key: Key) throws -> Bool { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + return entry is NSNull + } + + public func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: Bool.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: Int.Type, forKey key: Key) throws -> Int { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: Int.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: Int8.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: Int16.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: Int32.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: Int64.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: UInt.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { + guard let entry = container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: UInt8.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { + guard let entry = container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: UInt16.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: UInt32.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: UInt64.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: Float.Type, forKey key: Key) throws -> Float { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + guard let value = try self.decoder.unbox(entry, as: Float.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: Double.Type, forKey key: Key) throws -> Double { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: Double.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: String.Type, forKey key: Key) throws -> String { + guard let entry = self.container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try self.decoder.unbox(entry, as: String.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func decode(_ type: T.Type, forKey key: Key) throws -> T { + guard let entry = container[key.stringValue] else { + throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) + } + + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = try decoder.unbox(entry, as: T.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) + } + + return value + } + + public func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer { + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = self.container[key.stringValue] else { + throw DecodingError.valueNotFound(KeyedDecodingContainer.self, + DecodingError.Context(codingPath: self.codingPath, + debugDescription: "Cannot get nested keyed container -- no value found for key \"\(key.stringValue)\"")) + } + + guard let dictionary = value as? [String : Any] else { + throw DecodingError._typeMismatch(at: self.codingPath, expectation: [String : Any].self, reality: value) + } + + let container = _FirebaseKeyedDecodingContainer(referencing: self.decoder, wrapping: dictionary) + return KeyedDecodingContainer(container) + } + + public func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + guard let value = self.container[key.stringValue] else { + throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self, + DecodingError.Context(codingPath: self.codingPath, + debugDescription: "Cannot get nested unkeyed container -- no value found for key \"\(key.stringValue)\"")) + } + + guard let array = value as? [Any] else { + let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Not an array") + throw DecodingError.typeMismatch([Any].self, context) + } + + return _FirebaseUnkeyedDecodingContainer(referencing: self.decoder, wrapping: array) + } + + private func _superDecoder(forKey key: CodingKey) throws -> Decoder { + self.decoder.codingPath.append(key) + defer { self.decoder.codingPath.removeLast() } + + let value: Any = container[key.stringValue] ?? NSNull() + return _FirebaseDecoder(referencing: value, at: self.decoder.codingPath) + } + + public func superDecoder() throws -> Decoder { + return try _superDecoder(forKey: _FirebaseKey.super) + } + + public func superDecoder(forKey key: Key) throws -> Decoder { + return try _superDecoder(forKey: key) + } +} + +fileprivate struct _FirebaseUnkeyedDecodingContainer : UnkeyedDecodingContainer { + // MARK: Properties + /// A reference to the decoder we're reading from. + private let decoder: _FirebaseDecoder + + /// A reference to the container we're reading from. + private let container: [Any] + + /// The path of coding keys taken to get to this point in decoding. + private(set) public var codingPath: [CodingKey] + + /// The index of the element we're about to decode. + private(set) public var currentIndex: Int + + // MARK: - Initialization + /// Initializes `self` by referencing the given decoder and container. + fileprivate init(referencing decoder: _FirebaseDecoder, wrapping container: [Any]) { + self.decoder = decoder + self.container = container + self.codingPath = decoder.codingPath + self.currentIndex = 0 + } + + // MARK: - UnkeyedDecodingContainer Methods + public var count: Int? { + return container.count + } + + public var isAtEnd: Bool { + return currentIndex >= count! + } + + public mutating func decodeNil() throws -> Bool { + guard !isAtEnd else { + throw DecodingError.valueNotFound(Any?.self, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) + } + + if container[currentIndex] is NSNull { + currentIndex += 1 + return true + } else { + return false + } + } + + public mutating func decode(_ type: Bool.Type) throws -> Bool { + guard !isAtEnd else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) + } + + decoder.codingPath.append(_FirebaseKey(index: currentIndex)) + defer { decoder.codingPath.removeLast() } + + guard let decoded = try decoder.unbox(container[currentIndex], as: Bool.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: Int.Type) throws -> Int { + guard !isAtEnd else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) + } + + decoder.codingPath.append(_FirebaseKey(index: currentIndex)) + defer { decoder.codingPath.removeLast() } + + guard let decoded = try decoder.unbox(container[currentIndex], as: Int.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: Int8.Type) throws -> Int8 { + guard !isAtEnd else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) + } + + decoder.codingPath.append(_FirebaseKey(index: currentIndex)) + defer { decoder.codingPath.removeLast() } + + guard let decoded = try decoder.unbox(container[currentIndex], as: Int8.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: Int16.Type) throws -> Int16 { + guard !isAtEnd else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) + } + + decoder.codingPath.append(_FirebaseKey(index: currentIndex)) + defer { decoder.codingPath.removeLast() } + + guard let decoded = try decoder.unbox(container[currentIndex], as: Int16.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: Int32.Type) throws -> Int32 { + guard !isAtEnd else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) + } + + decoder.codingPath.append(_FirebaseKey(index: currentIndex)) + defer { decoder.codingPath.removeLast() } + + guard let decoded = try decoder.unbox(container[currentIndex], as: Int32.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: Int64.Type) throws -> Int64 { + guard !isAtEnd else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) + } + + decoder.codingPath.append(_FirebaseKey(index: currentIndex)) + defer { decoder.codingPath.removeLast() } + + guard let decoded = try decoder.unbox(container[currentIndex], as: Int64.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: UInt.Type) throws -> UInt { + guard !isAtEnd else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) + } + + decoder.codingPath.append(_FirebaseKey(index: currentIndex)) + defer { decoder.codingPath.removeLast() } + + guard let decoded = try decoder.unbox(container[currentIndex], as: UInt.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: UInt8.Type) throws -> UInt8 { + guard !isAtEnd else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) + } + + decoder.codingPath.append(_FirebaseKey(index: currentIndex)) + defer { decoder.codingPath.removeLast() } + + guard let decoded = try decoder.unbox(container[currentIndex], as: UInt8.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: UInt16.Type) throws -> UInt16 { + guard !self.isAtEnd else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) + } + + self.decoder.codingPath.append(_FirebaseKey(index: currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard let decoded = try decoder.unbox(container[currentIndex], as: UInt16.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: UInt32.Type) throws -> UInt32 { + guard !self.isAtEnd else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) + } + + decoder.codingPath.append(_FirebaseKey(index: currentIndex)) + defer { decoder.codingPath.removeLast() } + + guard let decoded = try decoder.unbox(container[currentIndex], as: UInt32.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: UInt64.Type) throws -> UInt64 { + guard !isAtEnd else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) + } + + decoder.codingPath.append(_FirebaseKey(index: currentIndex)) + defer { decoder.codingPath.removeLast() } + + guard let decoded = try decoder.unbox(container[currentIndex], as: UInt64.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: Float.Type) throws -> Float { + guard !self.isAtEnd else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) + } + + self.decoder.codingPath.append(_FirebaseKey(index: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Float.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + self.currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: Double.Type) throws -> Double { + guard !self.isAtEnd else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) + } + + self.decoder.codingPath.append(_FirebaseKey(index: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Double.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + self.currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: String.Type) throws -> String { + guard !self.isAtEnd else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) + } + + self.decoder.codingPath.append(_FirebaseKey(index: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: String.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + self.currentIndex += 1 + return decoded + } + + public mutating func decode(_ type: T.Type) throws -> T { + guard !self.isAtEnd else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) + } + + self.decoder.codingPath.append(_FirebaseKey(index: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: T.self) else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) + } + + self.currentIndex += 1 + return decoded + } + + public mutating func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer { + self.decoder.codingPath.append(_FirebaseKey(index: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard !self.isAtEnd else { + throw DecodingError.valueNotFound(KeyedDecodingContainer.self, + DecodingError.Context(codingPath: self.codingPath, + debugDescription: "Cannot get nested keyed container -- unkeyed container is at end.")) + } + + let value = self.container[self.currentIndex] + guard !(value is NSNull) else { + throw DecodingError.valueNotFound(KeyedDecodingContainer.self, + DecodingError.Context(codingPath: self.codingPath, + debugDescription: "Cannot get keyed decoding container -- found null value instead.")) + } + + guard let dictionary = value as? [String : Any] else { + let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Not a dictionary") + throw DecodingError.typeMismatch([String : Any].self, context) + } + + self.currentIndex += 1 + let container = _FirebaseKeyedDecodingContainer(referencing: self.decoder, wrapping: dictionary) + return KeyedDecodingContainer(container) + } + + public mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { + self.decoder.codingPath.append(_FirebaseKey(index: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard !self.isAtEnd else { + throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self, + DecodingError.Context(codingPath: self.codingPath, + debugDescription: "Cannot get nested unkeyed container -- unkeyed container is at end.")) + } + + let value = self.container[self.currentIndex] + guard !(value is NSNull) else { + throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self, + DecodingError.Context(codingPath: self.codingPath, + debugDescription: "Cannot get keyed decoding container -- found null value instead.")) + } + + guard let array = value as? [Any] else { + let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Not an array") + throw DecodingError.typeMismatch([String : Any].self, context) + } + + self.currentIndex += 1 + return _FirebaseUnkeyedDecodingContainer(referencing: self.decoder, wrapping: array) + } + + public mutating func superDecoder() throws -> Decoder { + self.decoder.codingPath.append(_FirebaseKey(index: self.currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard !self.isAtEnd else { + throw DecodingError.valueNotFound(Decoder.self, DecodingError.Context(codingPath: self.codingPath, + debugDescription: "Cannot get superDecoder() -- unkeyed container is at end.")) + } + + let value = self.container[self.currentIndex] + self.currentIndex += 1 + return _FirebaseDecoder(referencing: value, at: self.decoder.codingPath) + } +} + +extension _FirebaseDecoder : SingleValueDecodingContainer { + // MARK: SingleValueDecodingContainer Methods + private func expectNonNull(_ type: T.Type) throws { + guard !decodeNil() else { + throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: codingPath, debugDescription: "Expected \(type) but found null value instead.")) + } + } + + public func decodeNil() -> Bool { + return storage.topContainer is NSNull + } + + public func decode(_ type: Bool.Type) throws -> Bool { + try expectNonNull(Bool.self) + return try self.unbox(self.storage.topContainer, as: Bool.self)! + } + + public func decode(_ type: Int.Type) throws -> Int { + try expectNonNull(Int.self) + return try self.unbox(self.storage.topContainer, as: Int.self)! + } + + public func decode(_ type: Int8.Type) throws -> Int8 { + try expectNonNull(Int8.self) + return try self.unbox(self.storage.topContainer, as: Int8.self)! + } + + public func decode(_ type: Int16.Type) throws -> Int16 { + try expectNonNull(Int16.self) + return try self.unbox(self.storage.topContainer, as: Int16.self)! + } + + public func decode(_ type: Int32.Type) throws -> Int32 { + try expectNonNull(Int32.self) + return try self.unbox(self.storage.topContainer, as: Int32.self)! + } + + public func decode(_ type: Int64.Type) throws -> Int64 { + try expectNonNull(Int64.self) + return try self.unbox(self.storage.topContainer, as: Int64.self)! + } + + public func decode(_ type: UInt.Type) throws -> UInt { + try expectNonNull(UInt.self) + return try self.unbox(self.storage.topContainer, as: UInt.self)! + } + + public func decode(_ type: UInt8.Type) throws -> UInt8 { + try expectNonNull(UInt8.self) + return try self.unbox(self.storage.topContainer, as: UInt8.self)! + } + + public func decode(_ type: UInt16.Type) throws -> UInt16 { + try expectNonNull(UInt16.self) + return try self.unbox(self.storage.topContainer, as: UInt16.self)! + } + + public func decode(_ type: UInt32.Type) throws -> UInt32 { + try expectNonNull(UInt32.self) + return try self.unbox(self.storage.topContainer, as: UInt32.self)! + } + + public func decode(_ type: UInt64.Type) throws -> UInt64 { + try expectNonNull(UInt64.self) + return try self.unbox(self.storage.topContainer, as: UInt64.self)! + } + + public func decode(_ type: Float.Type) throws -> Float { + try expectNonNull(Float.self) + return try self.unbox(self.storage.topContainer, as: Float.self)! + } + + public func decode(_ type: Double.Type) throws -> Double { + try expectNonNull(Double.self) + return try self.unbox(self.storage.topContainer, as: Double.self)! + } + + public func decode(_ type: String.Type) throws -> String { + try expectNonNull(String.self) + return try self.unbox(self.storage.topContainer, as: String.self)! + } + + public func decode(_ type: T.Type) throws -> T { + try expectNonNull(T.self) + return try self.unbox(self.storage.topContainer, as: T.self)! + } +} + +extension _FirebaseDecoder { + /// Returns the given value unboxed from a container. + func unbox(_ value: Any, as type: Bool.Type) throws -> Bool? { + guard !(value is NSNull) else { return nil } + + if let number = value as? NSNumber { + // TODO: Add a flag to coerce non-boolean numbers into Bools? + if number === kCFBooleanTrue as NSNumber { + return true + } else if number === kCFBooleanFalse as NSNumber { + return false + } + + /* FIXME: If swift-corelibs-foundation doesn't change to use NSNumber, this code path will need to be included and tested: + } else if let bool = value as? Bool { + return bool + */ + + } + + throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) + } + + func unbox(_ value: Any, as type: Int.Type) throws -> Int? { + guard !(value is NSNull) else { return nil } + + guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { + throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) + } + + let int = number.intValue + guard NSNumber(value: int) == number else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type).")) + } + + return int + } + + func unbox(_ value: Any, as type: Int8.Type) throws -> Int8? { + guard !(value is NSNull) else { return nil } + + guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { + throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) + } + + let int8 = number.int8Value + guard NSNumber(value: int8) == number else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type).")) + } + + return int8 + } + + func unbox(_ value: Any, as type: Int16.Type) throws -> Int16? { + guard !(value is NSNull) else { return nil } + + guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { + throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) + } + + let int16 = number.int16Value + guard NSNumber(value: int16) == number else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type).")) + } + + return int16 + } + + func unbox(_ value: Any, as type: Int32.Type) throws -> Int32? { + guard !(value is NSNull) else { return nil } + + guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { + throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) + } + + let int32 = number.int32Value + guard NSNumber(value: int32) == number else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type).")) + } + + return int32 + } + + func unbox(_ value: Any, as type: Int64.Type) throws -> Int64? { + guard !(value is NSNull) else { return nil } + + guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { + throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) + } + + let int64 = number.int64Value + guard NSNumber(value: int64) == number else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type).")) + } + + return int64 + } + + func unbox(_ value: Any, as type: UInt.Type) throws -> UInt? { + guard !(value is NSNull) else { return nil } + + guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { + throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) + } + + let uint = number.uintValue + guard NSNumber(value: uint) == number else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type).")) + } + + return uint + } + + func unbox(_ value: Any, as type: UInt8.Type) throws -> UInt8? { + guard !(value is NSNull) else { return nil } + + guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { + throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) + } + + let uint8 = number.uint8Value + guard NSNumber(value: uint8) == number else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type).")) + } + + return uint8 + } + + func unbox(_ value: Any, as type: UInt16.Type) throws -> UInt16? { + guard !(value is NSNull) else { return nil } + + guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { + throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) + } + + let uint16 = number.uint16Value + guard NSNumber(value: uint16) == number else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type).")) + } + + return uint16 + } + + func unbox(_ value: Any, as type: UInt32.Type) throws -> UInt32? { + guard !(value is NSNull) else { return nil } + + guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { + throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) + } + + let uint32 = number.uint32Value + guard NSNumber(value: uint32) == number else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type).")) + } + + return uint32 + } + + func unbox(_ value: Any, as type: UInt64.Type) throws -> UInt64? { + guard !(value is NSNull) else { return nil } + + guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { + throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) + } + + let uint64 = number.uint64Value + guard NSNumber(value: uint64) == number else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type).")) + } + + return uint64 + } + + func unbox(_ value: Any, as type: Float.Type) throws -> Float? { + guard !(value is NSNull) else { return nil } + + if let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse { + // We are willing to return a Float by losing precision: + // * If the original value was integral, + // * and the integral value was > Float.greatestFiniteMagnitude, we will fail + // * and the integral value was <= Float.greatestFiniteMagnitude, we are willing to lose precision past 2^24 + // * If it was a Float, you will get back the precise value + // * If it was a Double or Decimal, you will get back the nearest approximation if it will fit + let double = number.doubleValue + guard abs(double) <= Double(Float.greatestFiniteMagnitude) else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number \(number) does not fit in \(type).")) + } + + return Float(double) + + /* FIXME: If swift-corelibs-foundation doesn't change to use NSNumber, this code path will need to be included and tested: + } else if let double = value as? Double { + if abs(double) <= Double(Float.max) { + return Float(double) + } + overflow = true + } else if let int = value as? Int { + if let float = Float(exactly: int) { + return float + } + overflow = true + */ + + } + + throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) + } + + func unbox(_ value: Any, as type: Double.Type) throws -> Double? { + guard !(value is NSNull) else { return nil } + + if let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse { + // We are always willing to return the number as a Double: + // * If the original value was integral, it is guaranteed to fit in a Double; we are willing to lose precision past 2^53 if you encoded a UInt64 but requested a Double + // * If it was a Float or Double, you will get back the precise value + // * If it was Decimal, you will get back the nearest approximation + return number.doubleValue + + /* FIXME: If swift-corelibs-foundation doesn't change to use NSNumber, this code path will need to be included and tested: + } else if let double = value as? Double { + return double + } else if let int = value as? Int { + if let double = Double(exactly: int) { + return double + } + overflow = true + */ + + } + + throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) + } + + func unbox(_ value: Any, as type: String.Type) throws -> String? { + guard !(value is NSNull) else { return nil } + + guard let string = value as? String else { + throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) + } + + return string + } + + func unbox(_ value: Any, as type: Date.Type) throws -> Date? { + guard !(value is NSNull) else { return nil } + + guard let date = value as? Date else { + throw DecodingError._typeMismatch(at: codingPath, expectation: type, reality: value) + } + + return date + } + + func unbox(_ value: Any, as type: Data.Type) throws -> Data? { + guard !(value is NSNull) else { return nil } + + guard let data = value as? Data else { + throw DecodingError._typeMismatch(at: codingPath, expectation: type, reality: value) + } + + return data + } + + func unbox(_ value: Any, as type: Decimal.Type) throws -> Decimal? { + guard !(value is NSNull) else { return nil } + + // Attempt to bridge from NSDecimalNumber. + if let decimal = value as? Decimal { + return decimal + } else { + let doubleValue = try self.unbox(value, as: Double.self)! + return Decimal(doubleValue) + } + } + + func unbox(_ value: Any, as type: T.Type) throws -> T? { + let decoded: T + if T.self == Date.self || T.self == NSDate.self { + guard let date = try self.unbox(value, as: Date.self) else { return nil } + decoded = date as! T + } else if T.self == Data.self || T.self == NSData.self { + guard let data = try self.unbox(value, as: Data.self) else { return nil } + decoded = data as! T + } else if T.self == URL.self || T.self == NSURL.self { + guard let urlString = try self.unbox(value, as: String.self) else { + return nil + } + + guard let url = URL(string: urlString) else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + debugDescription: "Invalid URL string.")) + } + + decoded = (url as! T) + } else if T.self == Decimal.self || T.self == NSDecimalNumber.self { + guard let decimal = try self.unbox(value, as: Decimal.self) else { return nil } + decoded = decimal as! T + } else { + self.storage.push(container: value) + decoded = try T(from: self) + self.storage.popContainer() + } + + return decoded + } +} diff --git a/CodableFirebase/FirestoreDecoder.swift b/CodableFirebase/FirestoreDecoder.swift index cf60c59..750f953 100644 --- a/CodableFirebase/FirestoreDecoder.swift +++ b/CodableFirebase/FirestoreDecoder.swift @@ -20,1153 +20,3 @@ open class FirestoreDecoder { return value } } - -fileprivate class _FirebaseDecoder : Decoder { - // MARK: Properties - /// The decoder's storage. - fileprivate var storage: _FirebaseDecodingStorage - - /// The path to the current point in encoding. - fileprivate(set) public var codingPath: [CodingKey] - - /// Contextual user-provided information for use during encoding. - public var userInfo: [CodingUserInfoKey : Any] - - // MARK: - Initialization - /// Initializes `self` with the given top-level container and options. - fileprivate init(referencing container: Any, at codingPath: [CodingKey] = []) { - self.storage = _FirebaseDecodingStorage() - self.storage.push(container: container) - self.codingPath = codingPath - userInfo = [:] - } - - // MARK: - Decoder Methods - public func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer { - guard !(self.storage.topContainer is NSNull) else { - throw DecodingError.valueNotFound(KeyedDecodingContainer.self, - DecodingError.Context(codingPath: self.codingPath, - debugDescription: "Cannot get keyed decoding container -- found null value instead.")) - } - - guard let topContainer = self.storage.topContainer as? [String : Any] else { - let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Not a dictionary") - throw DecodingError.typeMismatch([String: Any].self, context) - } - - let container = _FirebaseKeyedDecodingContainer(referencing: self, wrapping: topContainer) - return KeyedDecodingContainer(container) - } - - public func unkeyedContainer() throws -> UnkeyedDecodingContainer { - guard !(self.storage.topContainer is NSNull) else { - throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self, - DecodingError.Context(codingPath: self.codingPath, - debugDescription: "Cannot get unkeyed decoding container -- found null value instead.")) - } - - guard let topContainer = self.storage.topContainer as? [Any] else { - let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Not an array") - throw DecodingError.typeMismatch([Any].self, context) - } - - return _FirebaseUnkeyedDecodingContainer(referencing: self, wrapping: topContainer) - } - - public func singleValueContainer() throws -> SingleValueDecodingContainer { - return self - } -} - -fileprivate struct _FirebaseDecodingStorage { - // MARK: Properties - /// The container stack. - /// Elements may be any one of the plist types (NSNumber, Date, String, Array, [String : Any]). - private(set) fileprivate var containers: [Any] = [] - - // MARK: - Initialization - /// Initializes `self` with no containers. - fileprivate init() {} - - // MARK: - Modifying the Stack - fileprivate var count: Int { - return containers.count - } - - fileprivate var topContainer: Any { - precondition(containers.count > 0, "Empty container stack.") - return containers.last! - } - - fileprivate mutating func push(container: Any) { - containers.append(container) - } - - fileprivate mutating func popContainer() { - precondition(containers.count > 0, "Empty container stack.") - containers.removeLast() - } -} - -fileprivate struct _FirebaseKeyedDecodingContainer : KeyedDecodingContainerProtocol { - typealias Key = K - - // MARK: Properties - /// A reference to the decoder we're reading from. - private let decoder: _FirebaseDecoder - - /// A reference to the container we're reading from. - private let container: [String : Any] - - /// The path of coding keys taken to get to this point in decoding. - private(set) public var codingPath: [CodingKey] - - // MARK: - Initialization - /// Initializes `self` by referencing the given decoder and container. - fileprivate init(referencing decoder: _FirebaseDecoder, wrapping container: [String : Any]) { - self.decoder = decoder - self.container = container - self.codingPath = decoder.codingPath - } - - // MARK: - KeyedDecodingContainerProtocol Methods - public var allKeys: [Key] { - return container.keys.flatMap { Key(stringValue: $0) } - } - - public func contains(_ key: Key) -> Bool { - return container[key.stringValue] != nil - } - - public func decodeNil(forKey key: Key) throws -> Bool { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - return entry is NSNull - } - - public func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: Bool.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: Int.Type, forKey key: Key) throws -> Int { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: Int.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: Int8.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: Int16.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: Int32.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: Int64.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: UInt.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { - guard let entry = container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: UInt8.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { - guard let entry = container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: UInt16.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: UInt32.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: UInt64.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: Float.Type, forKey key: Key) throws -> Float { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - guard let value = try self.decoder.unbox(entry, as: Float.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: Double.Type, forKey key: Key) throws -> Double { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: Double.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: String.Type, forKey key: Key) throws -> String { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: String.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: T.Type, forKey key: Key) throws -> T { - guard let entry = container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try decoder.unbox(entry, as: T.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer { - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = self.container[key.stringValue] else { - throw DecodingError.valueNotFound(KeyedDecodingContainer.self, - DecodingError.Context(codingPath: self.codingPath, - debugDescription: "Cannot get nested keyed container -- no value found for key \"\(key.stringValue)\"")) - } - - guard let dictionary = value as? [String : Any] else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: [String : Any].self, reality: value) - } - - let container = _FirebaseKeyedDecodingContainer(referencing: self.decoder, wrapping: dictionary) - return KeyedDecodingContainer(container) - } - - public func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = self.container[key.stringValue] else { - throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self, - DecodingError.Context(codingPath: self.codingPath, - debugDescription: "Cannot get nested unkeyed container -- no value found for key \"\(key.stringValue)\"")) - } - - guard let array = value as? [Any] else { - let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Not an array") - throw DecodingError.typeMismatch([Any].self, context) - } - - return _FirebaseUnkeyedDecodingContainer(referencing: self.decoder, wrapping: array) - } - - private func _superDecoder(forKey key: CodingKey) throws -> Decoder { - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - let value: Any = container[key.stringValue] ?? NSNull() - return _FirebaseDecoder(referencing: value, at: self.decoder.codingPath) - } - - public func superDecoder() throws -> Decoder { - return try _superDecoder(forKey: _FirebaseKey.super) - } - - public func superDecoder(forKey key: Key) throws -> Decoder { - return try _superDecoder(forKey: key) - } -} - -fileprivate struct _FirebaseUnkeyedDecodingContainer : UnkeyedDecodingContainer { - // MARK: Properties - /// A reference to the decoder we're reading from. - private let decoder: _FirebaseDecoder - - /// A reference to the container we're reading from. - private let container: [Any] - - /// The path of coding keys taken to get to this point in decoding. - private(set) public var codingPath: [CodingKey] - - /// The index of the element we're about to decode. - private(set) public var currentIndex: Int - - // MARK: - Initialization - /// Initializes `self` by referencing the given decoder and container. - fileprivate init(referencing decoder: _FirebaseDecoder, wrapping container: [Any]) { - self.decoder = decoder - self.container = container - self.codingPath = decoder.codingPath - self.currentIndex = 0 - } - - // MARK: - UnkeyedDecodingContainer Methods - public var count: Int? { - return container.count - } - - public var isAtEnd: Bool { - return currentIndex >= count! - } - - public mutating func decodeNil() throws -> Bool { - guard !isAtEnd else { - throw DecodingError.valueNotFound(Any?.self, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - if container[currentIndex] is NSNull { - currentIndex += 1 - return true - } else { - return false - } - } - - public mutating func decode(_ type: Bool.Type) throws -> Bool { - guard !isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - decoder.codingPath.append(_FirebaseKey(index: currentIndex)) - defer { decoder.codingPath.removeLast() } - - guard let decoded = try decoder.unbox(container[currentIndex], as: Bool.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: Int.Type) throws -> Int { - guard !isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - decoder.codingPath.append(_FirebaseKey(index: currentIndex)) - defer { decoder.codingPath.removeLast() } - - guard let decoded = try decoder.unbox(container[currentIndex], as: Int.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: Int8.Type) throws -> Int8 { - guard !isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - decoder.codingPath.append(_FirebaseKey(index: currentIndex)) - defer { decoder.codingPath.removeLast() } - - guard let decoded = try decoder.unbox(container[currentIndex], as: Int8.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: Int16.Type) throws -> Int16 { - guard !isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - decoder.codingPath.append(_FirebaseKey(index: currentIndex)) - defer { decoder.codingPath.removeLast() } - - guard let decoded = try decoder.unbox(container[currentIndex], as: Int16.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: Int32.Type) throws -> Int32 { - guard !isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - decoder.codingPath.append(_FirebaseKey(index: currentIndex)) - defer { decoder.codingPath.removeLast() } - - guard let decoded = try decoder.unbox(container[currentIndex], as: Int32.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: Int64.Type) throws -> Int64 { - guard !isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - decoder.codingPath.append(_FirebaseKey(index: currentIndex)) - defer { decoder.codingPath.removeLast() } - - guard let decoded = try decoder.unbox(container[currentIndex], as: Int64.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: UInt.Type) throws -> UInt { - guard !isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - decoder.codingPath.append(_FirebaseKey(index: currentIndex)) - defer { decoder.codingPath.removeLast() } - - guard let decoded = try decoder.unbox(container[currentIndex], as: UInt.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: UInt8.Type) throws -> UInt8 { - guard !isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - decoder.codingPath.append(_FirebaseKey(index: currentIndex)) - defer { decoder.codingPath.removeLast() } - - guard let decoded = try decoder.unbox(container[currentIndex], as: UInt8.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: UInt16.Type) throws -> UInt16 { - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - self.decoder.codingPath.append(_FirebaseKey(index: currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard let decoded = try decoder.unbox(container[currentIndex], as: UInt16.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: UInt32.Type) throws -> UInt32 { - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - decoder.codingPath.append(_FirebaseKey(index: currentIndex)) - defer { decoder.codingPath.removeLast() } - - guard let decoded = try decoder.unbox(container[currentIndex], as: UInt32.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: UInt64.Type) throws -> UInt64 { - guard !isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - decoder.codingPath.append(_FirebaseKey(index: currentIndex)) - defer { decoder.codingPath.removeLast() } - - guard let decoded = try decoder.unbox(container[currentIndex], as: UInt64.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath + [_FirebaseKey(index: currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: Float.Type) throws -> Float { - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - self.decoder.codingPath.append(_FirebaseKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Float.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - self.currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: Double.Type) throws -> Double { - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - self.decoder.codingPath.append(_FirebaseKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Double.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - self.currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: String.Type) throws -> String { - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - self.decoder.codingPath.append(_FirebaseKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: String.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - self.currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: T.Type) throws -> T { - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - self.decoder.codingPath.append(_FirebaseKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: T.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirebaseKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - self.currentIndex += 1 - return decoded - } - - public mutating func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer { - self.decoder.codingPath.append(_FirebaseKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(KeyedDecodingContainer.self, - DecodingError.Context(codingPath: self.codingPath, - debugDescription: "Cannot get nested keyed container -- unkeyed container is at end.")) - } - - let value = self.container[self.currentIndex] - guard !(value is NSNull) else { - throw DecodingError.valueNotFound(KeyedDecodingContainer.self, - DecodingError.Context(codingPath: self.codingPath, - debugDescription: "Cannot get keyed decoding container -- found null value instead.")) - } - - guard let dictionary = value as? [String : Any] else { - let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Not a dictionary") - throw DecodingError.typeMismatch([String : Any].self, context) - } - - self.currentIndex += 1 - let container = _FirebaseKeyedDecodingContainer(referencing: self.decoder, wrapping: dictionary) - return KeyedDecodingContainer(container) - } - - public mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { - self.decoder.codingPath.append(_FirebaseKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self, - DecodingError.Context(codingPath: self.codingPath, - debugDescription: "Cannot get nested unkeyed container -- unkeyed container is at end.")) - } - - let value = self.container[self.currentIndex] - guard !(value is NSNull) else { - throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self, - DecodingError.Context(codingPath: self.codingPath, - debugDescription: "Cannot get keyed decoding container -- found null value instead.")) - } - - guard let array = value as? [Any] else { - let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Not an array") - throw DecodingError.typeMismatch([String : Any].self, context) - } - - self.currentIndex += 1 - return _FirebaseUnkeyedDecodingContainer(referencing: self.decoder, wrapping: array) - } - - public mutating func superDecoder() throws -> Decoder { - self.decoder.codingPath.append(_FirebaseKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(Decoder.self, DecodingError.Context(codingPath: self.codingPath, - debugDescription: "Cannot get superDecoder() -- unkeyed container is at end.")) - } - - let value = self.container[self.currentIndex] - self.currentIndex += 1 - return _FirebaseDecoder(referencing: value, at: self.decoder.codingPath) - } -} - -extension _FirebaseDecoder : SingleValueDecodingContainer { - // MARK: SingleValueDecodingContainer Methods - private func expectNonNull(_ type: T.Type) throws { - guard !decodeNil() else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: codingPath, debugDescription: "Expected \(type) but found null value instead.")) - } - } - - public func decodeNil() -> Bool { - return storage.topContainer is NSNull - } - - public func decode(_ type: Bool.Type) throws -> Bool { - try expectNonNull(Bool.self) - return try self.unbox(self.storage.topContainer, as: Bool.self)! - } - - public func decode(_ type: Int.Type) throws -> Int { - try expectNonNull(Int.self) - return try self.unbox(self.storage.topContainer, as: Int.self)! - } - - public func decode(_ type: Int8.Type) throws -> Int8 { - try expectNonNull(Int8.self) - return try self.unbox(self.storage.topContainer, as: Int8.self)! - } - - public func decode(_ type: Int16.Type) throws -> Int16 { - try expectNonNull(Int16.self) - return try self.unbox(self.storage.topContainer, as: Int16.self)! - } - - public func decode(_ type: Int32.Type) throws -> Int32 { - try expectNonNull(Int32.self) - return try self.unbox(self.storage.topContainer, as: Int32.self)! - } - - public func decode(_ type: Int64.Type) throws -> Int64 { - try expectNonNull(Int64.self) - return try self.unbox(self.storage.topContainer, as: Int64.self)! - } - - public func decode(_ type: UInt.Type) throws -> UInt { - try expectNonNull(UInt.self) - return try self.unbox(self.storage.topContainer, as: UInt.self)! - } - - public func decode(_ type: UInt8.Type) throws -> UInt8 { - try expectNonNull(UInt8.self) - return try self.unbox(self.storage.topContainer, as: UInt8.self)! - } - - public func decode(_ type: UInt16.Type) throws -> UInt16 { - try expectNonNull(UInt16.self) - return try self.unbox(self.storage.topContainer, as: UInt16.self)! - } - - public func decode(_ type: UInt32.Type) throws -> UInt32 { - try expectNonNull(UInt32.self) - return try self.unbox(self.storage.topContainer, as: UInt32.self)! - } - - public func decode(_ type: UInt64.Type) throws -> UInt64 { - try expectNonNull(UInt64.self) - return try self.unbox(self.storage.topContainer, as: UInt64.self)! - } - - public func decode(_ type: Float.Type) throws -> Float { - try expectNonNull(Float.self) - return try self.unbox(self.storage.topContainer, as: Float.self)! - } - - public func decode(_ type: Double.Type) throws -> Double { - try expectNonNull(Double.self) - return try self.unbox(self.storage.topContainer, as: Double.self)! - } - - public func decode(_ type: String.Type) throws -> String { - try expectNonNull(String.self) - return try self.unbox(self.storage.topContainer, as: String.self)! - } - - public func decode(_ type: T.Type) throws -> T { - try expectNonNull(T.self) - return try self.unbox(self.storage.topContainer, as: T.self)! - } -} - -extension _FirebaseDecoder { - /// Returns the given value unboxed from a container. - fileprivate func unbox(_ value: Any, as type: Bool.Type) throws -> Bool? { - guard !(value is NSNull) else { return nil } - - if let number = value as? NSNumber { - // TODO: Add a flag to coerce non-boolean numbers into Bools? - if number === kCFBooleanTrue as NSNumber { - return true - } else if number === kCFBooleanFalse as NSNumber { - return false - } - - /* FIXME: If swift-corelibs-foundation doesn't change to use NSNumber, this code path will need to be included and tested: - } else if let bool = value as? Bool { - return bool - */ - - } - - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - fileprivate func unbox(_ value: Any, as type: Int.Type) throws -> Int? { - guard !(value is NSNull) else { return nil } - - guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - let int = number.intValue - guard NSNumber(value: int) == number else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type).")) - } - - return int - } - - fileprivate func unbox(_ value: Any, as type: Int8.Type) throws -> Int8? { - guard !(value is NSNull) else { return nil } - - guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - let int8 = number.int8Value - guard NSNumber(value: int8) == number else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type).")) - } - - return int8 - } - - fileprivate func unbox(_ value: Any, as type: Int16.Type) throws -> Int16? { - guard !(value is NSNull) else { return nil } - - guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - let int16 = number.int16Value - guard NSNumber(value: int16) == number else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type).")) - } - - return int16 - } - - fileprivate func unbox(_ value: Any, as type: Int32.Type) throws -> Int32? { - guard !(value is NSNull) else { return nil } - - guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - let int32 = number.int32Value - guard NSNumber(value: int32) == number else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type).")) - } - - return int32 - } - - fileprivate func unbox(_ value: Any, as type: Int64.Type) throws -> Int64? { - guard !(value is NSNull) else { return nil } - - guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - let int64 = number.int64Value - guard NSNumber(value: int64) == number else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type).")) - } - - return int64 - } - - fileprivate func unbox(_ value: Any, as type: UInt.Type) throws -> UInt? { - guard !(value is NSNull) else { return nil } - - guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - let uint = number.uintValue - guard NSNumber(value: uint) == number else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type).")) - } - - return uint - } - - fileprivate func unbox(_ value: Any, as type: UInt8.Type) throws -> UInt8? { - guard !(value is NSNull) else { return nil } - - guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - let uint8 = number.uint8Value - guard NSNumber(value: uint8) == number else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type).")) - } - - return uint8 - } - - fileprivate func unbox(_ value: Any, as type: UInt16.Type) throws -> UInt16? { - guard !(value is NSNull) else { return nil } - - guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - let uint16 = number.uint16Value - guard NSNumber(value: uint16) == number else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type).")) - } - - return uint16 - } - - fileprivate func unbox(_ value: Any, as type: UInt32.Type) throws -> UInt32? { - guard !(value is NSNull) else { return nil } - - guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - let uint32 = number.uint32Value - guard NSNumber(value: uint32) == number else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type).")) - } - - return uint32 - } - - fileprivate func unbox(_ value: Any, as type: UInt64.Type) throws -> UInt64? { - guard !(value is NSNull) else { return nil } - - guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - let uint64 = number.uint64Value - guard NSNumber(value: uint64) == number else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type).")) - } - - return uint64 - } - - fileprivate func unbox(_ value: Any, as type: Float.Type) throws -> Float? { - guard !(value is NSNull) else { return nil } - - if let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse { - // We are willing to return a Float by losing precision: - // * If the original value was integral, - // * and the integral value was > Float.greatestFiniteMagnitude, we will fail - // * and the integral value was <= Float.greatestFiniteMagnitude, we are willing to lose precision past 2^24 - // * If it was a Float, you will get back the precise value - // * If it was a Double or Decimal, you will get back the nearest approximation if it will fit - let double = number.doubleValue - guard abs(double) <= Double(Float.greatestFiniteMagnitude) else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number \(number) does not fit in \(type).")) - } - - return Float(double) - - /* FIXME: If swift-corelibs-foundation doesn't change to use NSNumber, this code path will need to be included and tested: - } else if let double = value as? Double { - if abs(double) <= Double(Float.max) { - return Float(double) - } - overflow = true - } else if let int = value as? Int { - if let float = Float(exactly: int) { - return float - } - overflow = true - */ - - } - - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - fileprivate func unbox(_ value: Any, as type: Double.Type) throws -> Double? { - guard !(value is NSNull) else { return nil } - - if let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse { - // We are always willing to return the number as a Double: - // * If the original value was integral, it is guaranteed to fit in a Double; we are willing to lose precision past 2^53 if you encoded a UInt64 but requested a Double - // * If it was a Float or Double, you will get back the precise value - // * If it was Decimal, you will get back the nearest approximation - return number.doubleValue - - /* FIXME: If swift-corelibs-foundation doesn't change to use NSNumber, this code path will need to be included and tested: - } else if let double = value as? Double { - return double - } else if let int = value as? Int { - if let double = Double(exactly: int) { - return double - } - overflow = true - */ - - } - - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - fileprivate func unbox(_ value: Any, as type: String.Type) throws -> String? { - guard !(value is NSNull) else { return nil } - - guard let string = value as? String else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - return string - } - - fileprivate func unbox(_ value: Any, as type: Date.Type) throws -> Date? { - guard !(value is NSNull) else { return nil } - - guard let date = value as? Date else { - throw DecodingError._typeMismatch(at: codingPath, expectation: type, reality: value) - } - - return date - } - - fileprivate func unbox(_ value: Any, as type: Data.Type) throws -> Data? { - guard !(value is NSNull) else { return nil } - - guard let data = value as? Data else { - throw DecodingError._typeMismatch(at: codingPath, expectation: type, reality: value) - } - - return data - } - - fileprivate func unbox(_ value: Any, as type: Decimal.Type) throws -> Decimal? { - guard !(value is NSNull) else { return nil } - - // Attempt to bridge from NSDecimalNumber. - if let decimal = value as? Decimal { - return decimal - } else { - let doubleValue = try self.unbox(value, as: Double.self)! - return Decimal(doubleValue) - } - } - - fileprivate func unbox(_ value: Any, as type: T.Type) throws -> T? { - let decoded: T - if T.self == Date.self || T.self == NSDate.self { - guard let date = try self.unbox(value, as: Date.self) else { return nil } - decoded = date as! T - } else if T.self == Data.self || T.self == NSData.self { - guard let data = try self.unbox(value, as: Data.self) else { return nil } - decoded = data as! T - } else if T.self == URL.self || T.self == NSURL.self { - guard let urlString = try self.unbox(value, as: String.self) else { - return nil - } - - guard let url = URL(string: urlString) else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, - debugDescription: "Invalid URL string.")) - } - - decoded = (url as! T) - } else if T.self == Decimal.self || T.self == NSDecimalNumber.self { - guard let decimal = try self.unbox(value, as: Decimal.self) else { return nil } - decoded = decimal as! T - } else { - self.storage.push(container: value) - decoded = try T(from: self) - self.storage.popContainer() - } - - return decoded - } -}