diff --git a/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore-Example.xcscheme b/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore-Example.xcscheme
index a8538f52299..078b5b9e2c5 100644
--- a/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore-Example.xcscheme
+++ b/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore-Example.xcscheme
@@ -49,6 +49,16 @@
ReferencedContainer = "container:Firestore.xcodeproj">
+
+
+
+
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+
+
+
+
+
+
+
+
@@ -51,6 +73,15 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
+
+
+
+
@@ -60,6 +91,15 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
+
+
+
+
diff --git a/Firestore/Swift/Source/Codable/DocumentReference+Codable.swift b/Firestore/Swift/Source/Codable/DocumentReference+Codable.swift
new file mode 100644
index 00000000000..479336a9a35
--- /dev/null
+++ b/Firestore/Swift/Source/Codable/DocumentReference+Codable.swift
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import FirebaseFirestore
+
+/**
+ * A protocol describing the encodable properties of a DocumentReference.
+ *
+ * Note: this protocol exists as a workaround for the Swift compiler: if the DocumentReference class was
+ * extended directly to conform to Codable, the methods implementing the protcol would be need to be
+ * marked required but that can't be done in an extension. Declaring the extension on the protocol
+ * sidesteps this issue.
+ */
+fileprivate protocol CodableDocumentReference: Codable {}
+
+extension CodableDocumentReference {
+ public init(from decoder: Decoder) throws {
+ throw FirestoreDecodingError.decodingIsNotSupported
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ throw FirestoreEncodingError.encodingIsNotSupported
+ }
+}
+
+extension DocumentReference: CodableDocumentReference {}
diff --git a/Firestore/Swift/Source/Codable/Errors.swift b/Firestore/Swift/Source/Codable/Errors.swift
new file mode 100644
index 00000000000..2ed0bc5e32a
--- /dev/null
+++ b/Firestore/Swift/Source/Codable/Errors.swift
@@ -0,0 +1,16 @@
+//
+// Errors.swift
+// FirebaseFirestoreSwift
+//
+// Created by Oleksii on 27/02/2018.
+//
+
+import Foundation
+
+enum FirestoreDecodingError: Error {
+ case decodingIsNotSupported
+}
+
+enum FirestoreEncodingError: Error {
+ case encodingIsNotSupported
+}
diff --git a/Firestore/Swift/Source/Codable/FieldValue+Codable.swift b/Firestore/Swift/Source/Codable/FieldValue+Codable.swift
new file mode 100644
index 00000000000..a8823aa098a
--- /dev/null
+++ b/Firestore/Swift/Source/Codable/FieldValue+Codable.swift
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import FirebaseFirestore
+
+/**
+ * A protocol describing the encodable properties of a FirebaseFirestore.
+ *
+ * Note: this protocol exists as a workaround for the Swift compiler: if the FieldValue class was
+ * extended directly to conform to Codable, the methods implementing the protcol would be need to be
+ * marked required but that can't be done in an extension. Declaring the extension on the protocol
+ * sidesteps this issue.
+ */
+fileprivate protocol CodableFieldValue: Encodable {}
+
+extension CodableFieldValue {
+ public func encode(to encoder: Encoder) throws {
+ throw FirestoreEncodingError.encodingIsNotSupported
+ }
+}
+
+extension FieldValue: CodableFieldValue {}
diff --git a/Firestore/Swift/Source/Codable/FirestoreDecoder.swift b/Firestore/Swift/Source/Codable/FirestoreDecoder.swift
new file mode 100644
index 00000000000..814129354d8
--- /dev/null
+++ b/Firestore/Swift/Source/Codable/FirestoreDecoder.swift
@@ -0,0 +1,1055 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Foundation
+import FirebaseFirestore
+
+@available(swift 4.0.0)
+extension DocumentSnapshot {
+ func data(as type: T.Type) throws -> T {
+ guard let dict = data() else {
+ throw DecodingError.valueNotFound(T.self, DecodingError.Context(codingPath: [], debugDescription: "Data was empty"))
+ }
+
+ return try Firestore.Decoder().decode(T.self, from: dict)
+ }
+}
+
+extension Firestore {
+ @available(swift 4.0.0)
+ struct Decoder {
+ func decode(_ type: T.Type, from container: [String: Any]) throws -> T {
+ let decoder = _FirestoreDecoder(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"))
+ }
+
+ return value
+ }
+ }
+}
+
+@available(swift 4.0.0)
+fileprivate class _FirestoreDecoder : Decoder {
+ /// Options set on the top-level encoder to pass down the decoding hierarchy.
+
+ // MARK: Properties
+ /// The decoder's storage.
+ fileprivate var storage: _FirestoreDecodingStorage
+
+ /// 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 = _FirestoreDecodingStorage()
+ self.storage.push(container: container)
+ self.codingPath = codingPath
+ }
+
+ // 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 = _FirestoreKeyedDecodingContainer(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 _FirestoreUnkeyedDecodingContainer(referencing: self, wrapping: topContainer)
+ }
+
+ public func singleValueContainer() throws -> SingleValueDecodingContainer {
+ return self
+ }
+}
+
+@available(swift 4.0.0)
+fileprivate struct _FirestoreDecodingStorage {
+ // 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()
+ }
+}
+
+@available(swift 4.0.0)
+fileprivate struct _FirestoreKeyedDecodingContainer : KeyedDecodingContainerProtocol {
+ typealias Key = K
+
+ // MARK: Properties
+ /// A reference to the decoder we're reading from.
+ private let decoder: _FirestoreDecoder
+
+ /// 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: _FirestoreDecoder, 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 {
+ let entry = try require(key: key)
+ return entry is NSNull
+ }
+
+ public func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool {
+ let entry = try require(key: key)
+
+ self.decoder.codingPath.append(key)
+ defer { self.decoder.codingPath.removeLast() }
+
+ let value = try self.decoder.unbox(entry, as: Bool.self)
+ return try require(value: value)
+ }
+
+ public func decode(_ type: Int.Type, forKey key: Key) throws -> Int {
+ let entry = try require(key: key)
+
+ self.decoder.codingPath.append(key)
+ defer { self.decoder.codingPath.removeLast() }
+
+ let value = try self.decoder.unbox(entry, as: Int.self)
+ return try require(value: value)
+ }
+
+ public func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 {
+ let entry = try require(key: key)
+
+ self.decoder.codingPath.append(key)
+ defer { self.decoder.codingPath.removeLast() }
+
+ let value = try self.decoder.unbox(entry, as: Int8.self)
+ return try require(value: value)
+ }
+
+ public func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 {
+ let entry = try require(key: key)
+
+ self.decoder.codingPath.append(key)
+ defer { self.decoder.codingPath.removeLast() }
+
+ let value = try self.decoder.unbox(entry, as: Int16.self)
+ return try require(value: value)
+ }
+
+ public func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 {
+ let entry = try require(key: key)
+
+ self.decoder.codingPath.append(key)
+ defer { self.decoder.codingPath.removeLast() }
+
+ let value = try self.decoder.unbox(entry, as: Int32.self)
+ return try require(value: value)
+ }
+
+ public func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 {
+ let entry = try require(key: key)
+
+ self.decoder.codingPath.append(key)
+ defer { self.decoder.codingPath.removeLast() }
+
+ let value = try self.decoder.unbox(entry, as: Int64.self)
+ return try require(value: value)
+ }
+
+ public func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt {
+ let entry = try require(key: key)
+
+ self.decoder.codingPath.append(key)
+ defer { self.decoder.codingPath.removeLast() }
+
+ let value = try self.decoder.unbox(entry, as: UInt.self)
+ return try require(value: value)
+ }
+
+ public func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 {
+ let entry = try require(key: key)
+
+ self.decoder.codingPath.append(key)
+ defer { self.decoder.codingPath.removeLast() }
+
+ let value = try self.decoder.unbox(entry, as: UInt8.self)
+ return try require(value: value)
+ }
+
+ public func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 {
+ let entry = try require(key: key)
+
+ self.decoder.codingPath.append(key)
+ defer { self.decoder.codingPath.removeLast() }
+
+ let value = try self.decoder.unbox(entry, as: UInt16.self)
+ return try require(value: value)
+ }
+
+ public func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 {
+ let entry = try require(key: key)
+
+ self.decoder.codingPath.append(key)
+ defer { self.decoder.codingPath.removeLast() }
+
+ let value = try self.decoder.unbox(entry, as: UInt32.self)
+ return try require(value: value)
+ }
+
+ public func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 {
+ let entry = try require(key: key)
+
+ self.decoder.codingPath.append(key)
+ defer { self.decoder.codingPath.removeLast() }
+
+ let value = try self.decoder.unbox(entry, as: UInt64.self)
+ return try require(value: value)
+ }
+
+ public func decode(_ type: Float.Type, forKey key: Key) throws -> Float {
+ let entry = try require(key: key)
+
+ self.decoder.codingPath.append(key)
+ defer { self.decoder.codingPath.removeLast() }
+
+ let value = try self.decoder.unbox(entry, as: Float.self)
+ return try require(value: value)
+ }
+
+ public func decode(_ type: Double.Type, forKey key: Key) throws -> Double {
+ let entry = try require(key: key)
+
+ self.decoder.codingPath.append(key)
+ defer { self.decoder.codingPath.removeLast() }
+
+ let value = try self.decoder.unbox(entry, as: Double.self)
+ return try require(value: value)
+ }
+
+ public func decode(_ type: String.Type, forKey key: Key) throws -> String {
+ let entry = try require(key: key)
+
+ self.decoder.codingPath.append(key)
+ defer { self.decoder.codingPath.removeLast() }
+
+ let value = try self.decoder.unbox(entry, as: String.self)
+ return try require(value: value)
+ }
+
+ public func decode(_ type: T.Type, forKey key: Key) throws -> T {
+ let entry = try require(key: key)
+
+ self.decoder.codingPath.append(key)
+ defer { self.decoder.codingPath.removeLast() }
+
+ let value = try self.decoder.unbox(entry, as: T.self)
+ return try require(value: value)
+ }
+
+ private func require(key: Key) throws -> Any {
+ if let entry = self.container[key.stringValue] {
+ return entry
+ }
+
+ let description = "No value associated with key \(key) (\"\(key.stringValue)\")."
+ let context = DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: description)
+ throw DecodingError.keyNotFound(key, context)
+ }
+
+ private func require(value: T?) throws -> T {
+ if let value = value {
+ return value
+ }
+
+ let message = "Expected \(T.self) value but found null instead."
+ let context = DecodingError.Context(codingPath: decoder.codingPath, debugDescription: message)
+ throw DecodingError.valueNotFound(T.self, context)
+ }
+
+ 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 = _FirestoreKeyedDecodingContainer(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 _FirestoreUnkeyedDecodingContainer(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 _FirestoreDecoder(referencing: value, at: self.decoder.codingPath)
+ }
+
+ public func superDecoder() throws -> Decoder {
+ return try _superDecoder(forKey: _FirestoreKey.super)
+ }
+
+ public func superDecoder(forKey key: Key) throws -> Decoder {
+ return try _superDecoder(forKey: key)
+ }
+}
+
+@available(swift 4.0.0)
+fileprivate struct _FirestoreUnkeyedDecodingContainer : UnkeyedDecodingContainer {
+ // MARK: Properties
+ /// A reference to the decoder we're reading from.
+ private let decoder: _FirestoreDecoder
+
+ /// 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: _FirestoreDecoder, 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 {
+ try expectNotAtEnd()
+
+ if container[currentIndex] is NSNull {
+ currentIndex += 1
+ return true
+ } else {
+ return false
+ }
+ }
+
+ public mutating func decode(_ type: Bool.Type) throws -> Bool {
+ try expectNotAtEnd()
+
+ decoder.codingPath.append(_FirestoreKey(index: currentIndex))
+ defer { decoder.codingPath.removeLast() }
+
+ let decoded = try decoder.unbox(container[currentIndex], as: Bool.self)
+ return try require(value: decoded)
+ }
+
+ public mutating func decode(_ type: Int.Type) throws -> Int {
+ try expectNotAtEnd()
+
+ decoder.codingPath.append(_FirestoreKey(index: currentIndex))
+ defer { decoder.codingPath.removeLast() }
+
+ let decoded = try decoder.unbox(container[currentIndex], as: Int.self)
+ return try require(value: decoded)
+ }
+
+ public mutating func decode(_ type: Int8.Type) throws -> Int8 {
+ try expectNotAtEnd()
+
+ decoder.codingPath.append(_FirestoreKey(index: currentIndex))
+ defer { decoder.codingPath.removeLast() }
+
+ let decoded = try decoder.unbox(container[currentIndex], as: Int8.self)
+ return try require(value: decoded)
+ }
+
+ public mutating func decode(_ type: Int16.Type) throws -> Int16 {
+ try expectNotAtEnd()
+
+ decoder.codingPath.append(_FirestoreKey(index: currentIndex))
+ defer { decoder.codingPath.removeLast() }
+
+ let decoded = try decoder.unbox(container[currentIndex], as: Int16.self)
+ return try require(value: decoded)
+ }
+
+ public mutating func decode(_ type: Int32.Type) throws -> Int32 {
+ try expectNotAtEnd()
+
+ decoder.codingPath.append(_FirestoreKey(index: currentIndex))
+ defer { decoder.codingPath.removeLast() }
+
+ let decoded = try decoder.unbox(container[currentIndex], as: Int32.self)
+ return try require(value: decoded)
+ }
+
+ public mutating func decode(_ type: Int64.Type) throws -> Int64 {
+ try expectNotAtEnd()
+
+ decoder.codingPath.append(_FirestoreKey(index: currentIndex))
+ defer { decoder.codingPath.removeLast() }
+
+ let decoded = try decoder.unbox(container[currentIndex], as: Int64.self)
+ return try require(value: decoded)
+ }
+
+ public mutating func decode(_ type: UInt.Type) throws -> UInt {
+ try expectNotAtEnd()
+
+ decoder.codingPath.append(_FirestoreKey(index: currentIndex))
+ defer { decoder.codingPath.removeLast() }
+
+ let decoded = try decoder.unbox(container[currentIndex], as: UInt.self)
+ return try require(value: decoded)
+ }
+
+ public mutating func decode(_ type: UInt8.Type) throws -> UInt8 {
+ try expectNotAtEnd()
+
+ decoder.codingPath.append(_FirestoreKey(index: currentIndex))
+ defer { decoder.codingPath.removeLast() }
+
+ let decoded = try decoder.unbox(container[currentIndex], as: UInt8.self)
+ return try require(value: decoded)
+ }
+
+ public mutating func decode(_ type: UInt16.Type) throws -> UInt16 {
+ try expectNotAtEnd()
+
+ self.decoder.codingPath.append(_FirestoreKey(index: currentIndex))
+ defer { self.decoder.codingPath.removeLast() }
+
+ let decoded = try decoder.unbox(container[currentIndex], as: UInt16.self)
+ return try require(value: decoded)
+ }
+
+ public mutating func decode(_ type: UInt32.Type) throws -> UInt32 {
+ try expectNotAtEnd()
+
+ decoder.codingPath.append(_FirestoreKey(index: currentIndex))
+ defer { decoder.codingPath.removeLast() }
+
+ let decoded = try decoder.unbox(container[currentIndex], as: UInt32.self)
+ return try require(value: decoded)
+ }
+
+ public mutating func decode(_ type: UInt64.Type) throws -> UInt64 {
+ try expectNotAtEnd()
+
+ decoder.codingPath.append(_FirestoreKey(index: currentIndex))
+ defer { decoder.codingPath.removeLast() }
+
+ let decoded = try decoder.unbox(container[currentIndex], as: UInt64.self)
+ return try require(value: decoded)
+ }
+
+ public mutating func decode(_ type: Float.Type) throws -> Float {
+ try expectNotAtEnd()
+
+ self.decoder.codingPath.append(_FirestoreKey(index: self.currentIndex))
+ defer { self.decoder.codingPath.removeLast() }
+
+ let decoded = try decoder.unbox(container[currentIndex], as: Float.self)
+ return try require(value: decoded)
+ }
+
+ public mutating func decode(_ type: Double.Type) throws -> Double {
+ try expectNotAtEnd()
+
+ self.decoder.codingPath.append(_FirestoreKey(index: self.currentIndex))
+ defer { self.decoder.codingPath.removeLast() }
+
+ let decoded = try decoder.unbox(container[currentIndex], as: Double.self)
+ return try require(value: decoded)
+ }
+
+ public mutating func decode(_ type: String.Type) throws -> String {
+ try expectNotAtEnd()
+
+ self.decoder.codingPath.append(_FirestoreKey(index: self.currentIndex))
+ defer { self.decoder.codingPath.removeLast() }
+
+ let decoded = try decoder.unbox(container[currentIndex], as: String.self)
+ return try require(value: decoded)
+ }
+
+ public mutating func decode(_ type: T.Type) throws -> T {
+ try expectNotAtEnd()
+
+ self.decoder.codingPath.append(_FirestoreKey(index: self.currentIndex))
+ defer { self.decoder.codingPath.removeLast() }
+
+ let decoded = try decoder.unbox(container[currentIndex], as: T.self)
+ return try require(value: decoded)
+ }
+
+ public mutating func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer {
+ self.decoder.codingPath.append(_FirestoreKey(index: self.currentIndex))
+ defer { self.decoder.codingPath.removeLast() }
+
+ try expectNotAtEnd()
+
+ let value = self.container[self.currentIndex]
+ try requireNotNSNull(value)
+
+ guard let dictionary = value as? [String : Any] else {
+ throw DecodingError._typeMismatch(at: self.codingPath, expectation: [String : Any].self, reality: value)
+ }
+
+ self.currentIndex += 1
+ let container = _FirestoreKeyedDecodingContainer(referencing: self.decoder, wrapping: dictionary)
+ return KeyedDecodingContainer(container)
+ }
+
+ public mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
+ self.decoder.codingPath.append(_FirestoreKey(index: self.currentIndex))
+ defer { self.decoder.codingPath.removeLast() }
+
+ try expectNotAtEnd()
+
+ let value = self.container[self.currentIndex]
+ try requireNotNSNull(value)
+
+ guard let array = value as? [Any] else {
+ throw DecodingError._typeMismatch(at: self.codingPath, expectation: [Any].self, reality: value)
+ }
+
+ self.currentIndex += 1
+ return _FirestoreUnkeyedDecodingContainer(referencing: self.decoder, wrapping: array)
+ }
+
+ public mutating func superDecoder() throws -> Decoder {
+ self.decoder.codingPath.append(_FirestoreKey(index: self.currentIndex))
+ defer { self.decoder.codingPath.removeLast() }
+
+ try expectNotAtEnd()
+
+ let value = self.container[self.currentIndex]
+ self.currentIndex += 1
+ return _FirestoreDecoder(referencing: value, at: decoder.codingPath)
+ }
+
+ private func expectNotAtEnd() throws {
+ guard !isAtEnd else {
+ throw DecodingError.valueNotFound(Any?.self, DecodingError.Context(codingPath: self.decoder.codingPath + [_FirestoreKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end."))
+ }
+ }
+
+ private func requireNotNSNull(_ value: Any) throws {
+ if !(value is NSNull) {
+ return
+ }
+
+ let description = "Cannot get keyed decoding container -- found null value instead."
+ let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: description)
+ throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self, context)
+ }
+
+ private mutating func require(value: T?) throws -> T {
+ guard let value = value else {
+ let message = "Expected \(T.self) value but found null instead."
+ let context = DecodingError.Context(codingPath: decoder.codingPath + [_FirestoreKey(index: currentIndex)], debugDescription: message)
+ throw DecodingError.valueNotFound(T.self, context)
+ }
+
+ currentIndex += 1
+ return value
+ }
+}
+
+extension _FirestoreDecoder : 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 _FirestoreDecoder {
+ /// 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 if T.self == GeoPoint.self || T.self == DocumentReference.self {
+ // All the native types that should not be encoded
+ decoded = value as! T
+ } else {
+ self.storage.push(container: value)
+ decoded = try T(from: self)
+ self.storage.popContainer()
+ }
+
+ return decoded
+ }
+}
diff --git a/Firestore/Swift/Source/Codable/FirestoreEncoder.swift b/Firestore/Swift/Source/Codable/FirestoreEncoder.swift
new file mode 100644
index 00000000000..06a70ace20b
--- /dev/null
+++ b/Firestore/Swift/Source/Codable/FirestoreEncoder.swift
@@ -0,0 +1,558 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Foundation
+import FirebaseFirestore
+
+@available(swift 4.0.0)
+extension DocumentReference {
+ func set(_ value: T, _ completion: ((Error?) -> Void)?) {
+ do {
+ setData(try Firestore.Encoder().encode(value), completion: completion)
+ } catch let error {
+ completion?(error)
+ }
+ }
+}
+
+extension Firestore {
+ @available(swift 4.0.0)
+ struct Encoder {
+ func encode(_ value: T) throws -> [String: Any] {
+ guard let topLevel = try _FirestoreEncoder().box_(value) else {
+ throw EncodingError.invalidValue(value,
+ EncodingError.Context(codingPath: [],
+ debugDescription: "Top-level \(T.self) did not encode any values."))
+ }
+
+ // This is O(n) check. We might get rid of it once we refactor to internal
+ guard let dict = topLevel as? [String: Any] else {
+ throw EncodingError.invalidValue(value,
+ EncodingError.Context(codingPath: [],
+ debugDescription: "Top-level \(T.self) encoded not as dictionary."))
+ }
+
+ return dict
+ }
+ }
+}
+
+@available(swift 4.0.0)
+fileprivate 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
+ }
+
+ /// 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
+ }
+}
+
+@available(swift 4.0.0)
+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()!
+ }
+}
+
+@available(swift 4.0.0)
+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: _FirestoreKey.super, wrapping: container)
+ }
+
+ public mutating func superEncoder(forKey key: Key) -> Encoder {
+ return _FirestoreReferencingEncoder(referencing: encoder, at: key, wrapping: container)
+ }
+}
+
+@available(swift 4.0.0)
+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(_FirestoreKey(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))
+ 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(_FirestoreKey(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)
+ }
+}
+
+@available(swift 4.0.0)
+struct _FirestoreKey : 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` = _FirestoreKey(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)
+ } else if T.self == URL.self || T.self == NSURL.self {
+ return self.box((value as! URL).absoluteString)
+ } else if T.self == Decimal.self || T.self == NSDecimalNumber.self {
+ return (value as! NSDecimalNumber)
+ } else if T.self == GeoPoint.self || T.self == DocumentReference.self || T.self == FieldValue.self {
+ // These are all native _Firestore types that we don't need to Encode
+ return (value as! NSObject)
+ }
+
+ // The value should request a container from the _FirestoreEncoder.
+ let depth = self.storage.count
+ do {
+ try value.encode(to: self)
+ } catch {
+ // If the value pushed a container before throwing, pop it back off to restore state.
+ if self.storage.count > depth {
+ let _ = self.storage.popContainer()
+ }
+
+ throw error
+ }
+
+ // The top container should be a new container.
+ guard self.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))
+ }
+}
+
+@available(swift 4.0.0)
+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(_FirestoreKey(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
+ }
+ }
+}
+
+@available(swift 4.0.0)
+extension DecodingError {
+ 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/Firestore/Swift/Source/Codable/CodableGeoPoint.swift b/Firestore/Swift/Source/Codable/GeoPoint+Codable.swift
similarity index 100%
rename from Firestore/Swift/Source/Codable/CodableGeoPoint.swift
rename to Firestore/Swift/Source/Codable/GeoPoint+Codable.swift