28 Commits
0.0.1 ... 0.0.3

Author SHA1 Message Date
Oleksii Dykan
4dcdd049f0 Add cocoapods support 2017-12-28 11:58:49 +01:00
Oleksii Dykan
db7bd47c06 Bump version number 2017-12-28 11:30:44 +01:00
Oleksii Dykan
969cd78a6b Update README.md 2017-12-28 10:59:01 +01:00
Oleksii Dykan
3da07216a3 Merge pull request #11 from alickbass/firebase-database
Firebase database
2017-12-27 20:55:49 +01:00
Oleksii Dykan
cbf348f01d Fix apple tv simulator 2017-12-27 20:05:43 +01:00
Oleksii Dykan
40a14eb088 Fix xcode image 2017-12-27 19:56:09 +01:00
Oleksii Dykan
95b168e16e Fix name in the round trip 2017-12-27 19:54:40 +01:00
Oleksii Dykan
1d7c5e88d9 remove unused structs 2017-12-27 19:20:31 +01:00
Oleksii Dykan
04ff2eba1d Remove duplicate tests 2017-12-27 19:15:45 +01:00
Oleksii Dykan
ab2f387640 Add codable tests 2017-12-27 19:15:30 +01:00
Oleksii Dykan
2e49c677f1 Add codable tests 2017-12-27 15:51:45 +01:00
Oleksii Dykan
bd11b2b5bf Add Firebase encoder 2017-12-27 15:26:41 +01:00
Oleksii Dykan
0195c92b82 Use real options to decoder options 2017-12-27 15:19:58 +01:00
Oleksii Dykan
27d6397244 Add firebase decoder 2017-12-27 15:18:23 +01:00
Oleksii Dykan
e9685432c0 Merge pull request #10 from alickbass/date-data-strategies
Date data strategies
2017-12-27 15:09:53 +01:00
Oleksii Dykan
fe226d61e2 Add encoding URL as string 2017-12-27 14:57:31 +01:00
Oleksii Dykan
6981a38487 Add encoding strategies 2017-12-27 14:50:48 +01:00
Oleksii Dykan
af2921a5a9 Add data decoding stategy implementation 2017-12-27 14:31:37 +01:00
Oleksii Dykan
fb110d0a6c Add date decoding 2017-12-27 14:28:09 +01:00
Oleksii Dykan
24a2172269 Make date and data decoding strategies optionals 2017-12-27 14:27:59 +01:00
Oleksii Dykan
a336aec29c Add options to decoder 2017-12-27 14:16:32 +01:00
Oleksii Dykan
b3f02bf7e1 Merge pull request #9 from alickbass/separate-file-encoder
Separate file encoder
2017-12-27 13:23:26 +01:00
Oleksii Dykan
31f708f215 Move decoder to separate file 2017-12-27 12:06:07 +01:00
Oleksii Dykan
3029b53316 Rename firestore to firebase 2017-12-27 12:02:41 +01:00
Oleksii Dykan
f339062e3e Move firebase encoder to separate file 2017-12-27 12:00:48 +01:00
Oleksii Dykan
37b70fddb4 Rename to firebase decider and encode 2017-12-27 11:53:49 +01:00
Oleksii Dykan
a9480c9f6d Remove firebase encoder 2017-12-27 11:50:39 +01:00
Oleksii Dykan
d255a07fb7 Add FirebaseEncoder class 2017-12-20 17:01:24 +01:00
14 changed files with 3102 additions and 2144 deletions

View File

@@ -1,11 +1,11 @@
language: objective-c
osx_image: xcode9
osx_image: xcode9.2
env:
- ACTION=test PLATFORM=Mac DESTINATION='platform=OS X'
- ACTION=test PLATFORM=iOS DESTINATION='platform=iOS Simulator,name=iPhone 8'
- ACTION=build PLATFORM=watchOS DESTINATION='platform=watchOS Simulator,name=Apple Watch - 38mm'
- ACTION=test PLATFORM=tvOS DESTINATION='platform=tvOS Simulator,name=Apple TV 1080p'
- ACTION=test PLATFORM=tvOS DESTINATION='platform=tvOS Simulator,name=Apple TV'
script:
- xcodebuild clean $ACTION -project CodableFirebase.xcodeproj -scheme CodableFirebase -destination "$DESTINATION"

20
CodableFirebase.podspec Normal file
View File

@@ -0,0 +1,20 @@
Pod::Spec.new do |s|
s.name = "CodableFirebase"
s.version = "0.0.3"
s.summary = "Use Codable with Firebase"
s.description = "This library helps you use your custom models that conform to Codable protocol with Firebase Realtime Database and Firestore"
s.homepage = "https://github.com/alickbass/CodableFirebase"
s.license = { :type => "MIT", :file => "LICENSE" }
s.author = { "Oleksii Dykan" => "alick_bass@mail.ru" }
s.ios.deployment_target = "9.0"
s.osx.deployment_target = "10.11"
s.watchos.deployment_target = "2.0"
s.tvos.deployment_target = "9.0"
s.requires_arc = true
s.source = { :git => "https://github.com/alickbass/CodableFirebase.git", :tag => s.version, :branch => 'master'}
s.source_files = "CodableFirebase/*.swift"
s.pod_target_xcconfig = { 'SWIFT_VERSION' => '4' }
end

View File

@@ -12,6 +12,12 @@
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 */; };
CEFDBF861FF3B56200745EBE /* Decoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFDBF851FF3B56200745EBE /* Decoder.swift */; };
CEFDBF8A1FF3E24200745EBE /* FirebaseDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFDBF891FF3E24200745EBE /* FirebaseDecoder.swift */; };
CEFDBF8C1FF3E3CB00745EBE /* FirebaseEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFDBF8B1FF3E3CB00745EBE /* FirebaseEncoder.swift */; };
CEFDBF8E1FF3E63700745EBE /* CodaleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFDBF8D1FF3E63700745EBE /* CodaleTests.swift */; };
CEFDBF901FF3EB2900745EBE /* TestCodableFirebase.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFDBF8F1FF3EB2900745EBE /* TestCodableFirebase.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -33,6 +39,12 @@
CE7DD3811F9D04AE000225C5 /* FirestoreEncoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirestoreEncoder.swift; sourceTree = "<group>"; };
CE7DD3821F9D04AE000225C5 /* FirestoreDecoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirestoreDecoder.swift; sourceTree = "<group>"; };
CE7DD3851F9DE4F7000225C5 /* TestCodableFirestore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestCodableFirestore.swift; sourceTree = "<group>"; };
CEFDBF811FF3B35B00745EBE /* Encoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encoder.swift; sourceTree = "<group>"; };
CEFDBF851FF3B56200745EBE /* Decoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Decoder.swift; sourceTree = "<group>"; };
CEFDBF891FF3E24200745EBE /* FirebaseDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseDecoder.swift; sourceTree = "<group>"; };
CEFDBF8B1FF3E3CB00745EBE /* FirebaseEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseEncoder.swift; sourceTree = "<group>"; };
CEFDBF8D1FF3E63700745EBE /* CodaleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodaleTests.swift; sourceTree = "<group>"; };
CEFDBF8F1FF3EB2900745EBE /* TestCodableFirebase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCodableFirebase.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -78,6 +90,10 @@
CE7DD36A1F9CFA81000225C5 /* CodableFirebase.h */,
CE7DD3821F9D04AE000225C5 /* FirestoreDecoder.swift */,
CE7DD3811F9D04AE000225C5 /* FirestoreEncoder.swift */,
CEFDBF891FF3E24200745EBE /* FirebaseDecoder.swift */,
CEFDBF8B1FF3E3CB00745EBE /* FirebaseEncoder.swift */,
CEFDBF851FF3B56200745EBE /* Decoder.swift */,
CEFDBF811FF3B35B00745EBE /* Encoder.swift */,
CE7DD36B1F9CFA81000225C5 /* Info.plist */,
);
path = CodableFirebase;
@@ -86,7 +102,9 @@
CE7DD3741F9CFA81000225C5 /* CodableFirebaseTests */ = {
isa = PBXGroup;
children = (
CEFDBF8D1FF3E63700745EBE /* CodaleTests.swift */,
CE7DD3851F9DE4F7000225C5 /* TestCodableFirestore.swift */,
CEFDBF8F1FF3EB2900745EBE /* TestCodableFirebase.swift */,
CE7DD3771F9CFA81000225C5 /* Info.plist */,
);
path = CodableFirebaseTests;
@@ -204,7 +222,11 @@
buildActionMask = 2147483647;
files = (
CE7DD3831F9D04AE000225C5 /* FirestoreEncoder.swift in Sources */,
CEFDBF8C1FF3E3CB00745EBE /* FirebaseEncoder.swift in Sources */,
CEFDBF821FF3B35B00745EBE /* Encoder.swift in Sources */,
CEFDBF861FF3B56200745EBE /* Decoder.swift in Sources */,
CE7DD3841F9D04AE000225C5 /* FirestoreDecoder.swift in Sources */,
CEFDBF8A1FF3E24200745EBE /* FirebaseDecoder.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -213,6 +235,8 @@
buildActionMask = 2147483647;
files = (
CE7DD3861F9DE4F7000225C5 /* TestCodableFirestore.swift in Sources */,
CEFDBF8E1FF3E63700745EBE /* CodaleTests.swift in Sources */,
CEFDBF901FF3EB2900745EBE /* TestCodableFirebase.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,572 @@
//
// Encoder.swift
// CodableFirebase
//
// Created by Oleksii on 27/12/2017.
// Copyright © 2017 ViolentOctopus. All rights reserved.
//
import Foundation
class _FirebaseEncoder : Encoder {
/// Options set on the top-level encoder to pass down the encoding hierarchy.
struct _Options {
let dateEncodingStrategy: FirebaseEncoder.DateEncodingStrategy?
let dataEncodingStrategy: FirebaseEncoder.DataEncodingStrategy?
let userInfo: [CodingUserInfoKey : Any]
}
fileprivate var storage: _FirebaseEncodingStorage
fileprivate let options: _Options
fileprivate(set) public var codingPath: [CodingKey]
public var userInfo: [CodingUserInfoKey : Any] {
return options.userInfo
}
init(options: _Options, codingPath: [CodingKey] = []) {
self.storage = _FirebaseEncodingStorage()
self.codingPath = codingPath
self.options = options
}
/// 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<Key>(keyedBy: Key.Type) -> KeyedEncodingContainer<Key> {
// 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<Key>(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<K : CodingKey> : 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<T : Encodable>(_ 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<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> {
let dictionary = NSMutableDictionary()
self.container[key.stringValue] = dictionary
codingPath.append(key)
defer { codingPath.removeLast() }
let container = _FirebaseKeyedEncodingContainer<NestedKey>(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<T : Encodable>(_ value: T) throws {
encoder.codingPath.append(_FirebaseKey(index: count))
defer { encoder.codingPath.removeLast() }
container.add(try encoder.box(value))
}
public mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> {
self.codingPath.append(_FirebaseKey(index: self.count))
defer { self.codingPath.removeLast() }
let dictionary = NSMutableDictionary()
self.container.add(dictionary)
let container = _FirebaseKeyedEncodingContainer<NestedKey>(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<T : Encodable>(_ value: T) throws -> NSObject {
return try self.box_(value) ?? NSDictionary()
}
fileprivate func box(_ date: Date) throws -> NSObject {
guard let options = options.dateEncodingStrategy else { return date as NSDate }
switch options {
case .deferredToDate:
// Must be called with a surrounding with(pushedKey:) call.
try date.encode(to: self)
return self.storage.popContainer()
case .secondsSince1970:
return NSNumber(value: date.timeIntervalSince1970)
case .millisecondsSince1970:
return NSNumber(value: 1000.0 * date.timeIntervalSince1970)
case .iso8601:
if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
return NSString(string: _iso8601Formatter.string(from: date))
} else {
fatalError("ISO8601DateFormatter is unavailable on this platform.")
}
case .formatted(let formatter):
return NSString(string: formatter.string(from: date))
case .custom(let closure):
let depth = self.storage.count
try closure(date, self)
guard self.storage.count > depth else {
// The closure didn't encode anything. Return the default keyed container.
return NSDictionary()
}
// We can pop because the closure encoded something.
return self.storage.popContainer()
}
}
fileprivate func box(_ data: Data) throws -> NSObject {
guard let options = options.dataEncodingStrategy else { return data as NSData }
switch options {
case .deferredToData:
// Must be called with a surrounding with(pushedKey:) call.
try data.encode(to: self)
return self.storage.popContainer()
case .base64:
return NSString(string: data.base64EncodedString())
case .custom(let closure):
let depth = self.storage.count
try closure(data, self)
guard self.storage.count > depth else {
// The closure didn't encode anything. Return the default keyed container.
return NSDictionary()
}
// We can pop because the closure encoded something.
return self.storage.popContainer()
}
}
func box_<T : Encodable>(_ value: T) throws -> NSObject? {
if T.self == Date.self || T.self == NSDate.self {
return try self.box((value as! Date))
} else if T.self == Data.self || T.self == NSData.self {
return try self.box((value as! Data))
} else if T.self == URL.self || T.self == NSURL.self {
return self.box((value as! URL).absoluteString)
}
// 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<T : Encodable>(_ 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(options: encoder.options, 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(options: encoder.options, 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))"
}
}
}

View File

@@ -0,0 +1,65 @@
//
// FirebaseDecoder.swift
// CodableFirebase
//
// Created by Oleksii on 27/12/2017.
// Copyright © 2017 ViolentOctopus. All rights reserved.
//
import Foundation
open class FirebaseDecoder {
/// The strategy to use for decoding `Date` values.
public enum DateDecodingStrategy {
/// Defer to `Date` for decoding. This is the default strategy.
case deferredToDate
/// Decode the `Date` as a UNIX timestamp from a JSON number.
case secondsSince1970
/// Decode the `Date` as UNIX millisecond timestamp from a JSON number.
case millisecondsSince1970
/// Decode the `Date` as an ISO-8601-formatted string (in RFC 3339 format).
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
case iso8601
/// Decode the `Date` as a string parsed by the given formatter.
case formatted(DateFormatter)
/// Decode the `Date` as a custom value decoded by the given closure.
case custom((_ decoder: Decoder) throws -> Date)
}
/// The strategy to use for decoding `Data` values.
public enum DataDecodingStrategy {
/// Defer to `Data` for decoding.
case deferredToData
/// Decode the `Data` from a Base64-encoded string. This is the default strategy.
case base64
/// Decode the `Data` as a custom value decoded by the given closure.
case custom((_ decoder: Decoder) throws -> Data)
}
public init() {}
open var userInfo: [CodingUserInfoKey : Any] = [:]
open var dateDecodingStrategy: DateDecodingStrategy = .deferredToDate
open var dataDecodingStrategy: DataDecodingStrategy = .deferredToData
open func decode<T : Decodable>(_ type: T.Type, from container: Any) throws -> T {
let options = _FirebaseDecoder._Options(
dateDecodingStrategy: dateDecodingStrategy,
dataDecodingStrategy: dataDecodingStrategy,
userInfo: userInfo
)
let decoder = _FirebaseDecoder(referencing: container, options: options)
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
}
}

View File

@@ -0,0 +1,71 @@
//
// FirebaseEncoder.swift
// CodableFirebase
//
// Created by Oleksii on 27/12/2017.
// Copyright © 2017 ViolentOctopus. All rights reserved.
//
import Foundation
open class FirebaseEncoder {
/// The strategy to use for encoding `Date` values.
public enum DateEncodingStrategy {
/// Defer to `Date` for choosing an encoding. This is the default strategy.
case deferredToDate
/// Encode the `Date` as a UNIX timestamp (as a JSON number).
case secondsSince1970
/// Encode the `Date` as UNIX millisecond timestamp (as a JSON number).
case millisecondsSince1970
/// Encode the `Date` as an ISO-8601-formatted string (in RFC 3339 format).
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
case iso8601
/// Encode the `Date` as a string formatted by the given formatter.
case formatted(DateFormatter)
/// Encode the `Date` as a custom value encoded by the given closure.
///
/// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place.
case custom((Date, Encoder) throws -> Void)
}
/// The strategy to use for encoding `Data` values.
public enum DataEncodingStrategy {
/// Defer to `Data` for choosing an encoding.
case deferredToData
/// Encoded the `Data` as a Base64-encoded string. This is the default strategy.
case base64
/// Encode the `Data` as a custom value encoded by the given closure.
///
/// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place.
case custom((Data, Encoder) throws -> Void)
}
public init() {}
open var userInfo: [CodingUserInfoKey : Any] = [:]
open var dateEncodingStrategy: DateEncodingStrategy = .deferredToDate
open var dataEncodingStrategy: DataEncodingStrategy = .deferredToData
open func encode<Value : Encodable>(_ value: Value) throws -> Any {
let options = _FirebaseEncoder._Options(
dateEncodingStrategy: dateEncodingStrategy,
dataEncodingStrategy: dataEncodingStrategy,
userInfo: userInfo
)
let encoder = _FirebaseEncoder(options: options)
guard let topLevel = try encoder.box_(value) else {
throw EncodingError.invalidValue(value,
EncodingError.Context(codingPath: [],
debugDescription: "Top-level \(Value.self) did not encode any values."))
}
return topLevel
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,8 @@ import Foundation
open class FirestoreEncoder {
public init() {}
open var userInfo: [CodingUserInfoKey : Any] = [:]
open func encode<Value : Encodable>(_ value: Value) throws -> [String: Any] {
let topLevel = try encodeToTopLevelContainer(value)
switch topLevel {
@@ -24,7 +26,8 @@ open class FirestoreEncoder {
}
internal func encodeToTopLevelContainer<Value : Encodable>(_ value: Value) throws -> Any {
let encoder = _FirestoreEncoder()
let options = _FirebaseEncoder._Options(dateEncodingStrategy: nil, dataEncodingStrategy: nil, userInfo: userInfo)
let encoder = _FirebaseEncoder(options: options)
guard let topLevel = try encoder.box_(value) else {
throw EncodingError.invalidValue(value,
EncodingError.Context(codingPath: [],
@@ -34,489 +37,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<Key>(keyedBy: Key.Type) -> KeyedEncodingContainer<Key> {
// 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<Key>(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<K : CodingKey> : 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<T : Encodable>(_ 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<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> {
let dictionary = NSMutableDictionary()
self.container[key.stringValue] = dictionary
codingPath.append(key)
defer { codingPath.removeLast() }
let container = _FirestoreKeyedEncodingContainer<NestedKey>(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)
}
}
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<T : Encodable>(_ value: T) throws {
encoder.codingPath.append(_FirestoreKey(index: count))
defer { encoder.codingPath.removeLast() }
container.add(try encoder.box(value))
}
public mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> {
self.codingPath.append(_FirestoreKey(index: self.count))
defer { self.codingPath.removeLast() }
let dictionary = NSMutableDictionary()
self.container.add(dictionary)
let container = _FirestoreKeyedEncodingContainer<NestedKey>(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)
}
}
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<T : Encodable>(_ value: T) throws -> NSObject {
return try self.box_(value) ?? NSDictionary()
}
fileprivate func box_<T : Encodable>(_ 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<T : Encodable>(_ 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(_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
}
}
}
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))"
}
}
}

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.0.1</string>
<string>0.0.3</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>

View File

@@ -0,0 +1,202 @@
//
// CodaleTests.swift
// CodableFirebaseTests
//
// Created by Oleksii on 27/12/2017.
// Copyright © 2017 ViolentOctopus. All rights reserved.
//
import XCTest
import CodableFirebase
func debugDescription<T>(_ value: T) -> String {
if let debugDescribable = value as? CustomDebugStringConvertible {
return debugDescribable.debugDescription
} else if let describable = value as? CustomStringConvertible {
return describable.description
} else {
return "\(value)"
}
}
func expectRoundTripEquality<T : Codable>(of value: T, encode: (T) throws -> Any, decode: (Any) throws -> T, lineNumber: Int) where T : Equatable {
let data: Any
do {
data = try encode(value)
} catch {
fatalError("\(#file):\(lineNumber): Unable to encode \(T.self) <\(debugDescription(value))>: \(error)")
}
let decoded: T
do {
decoded = try decode(data as! NSObject)
} catch {
fatalError("\(#file):\(lineNumber): Unable to decode \(T.self) <\(debugDescription(value))>: \(error)")
}
XCTAssertEqual(value, decoded, "\(#file):\(lineNumber): Decoded \(T.self) <\(debugDescription(decoded))> not equal to original <\(debugDescription(value))>")
}
func expectRoundTripEqualityThroughFirebaseDatabase<T : Codable>(for value: T, lineNumber: Int) where T : Equatable {
let encode = { (_ value: T) throws -> Any in
return try FirebaseEncoder().encode(value) as! NSObject
}
let decode = { (_ data: Any) throws -> T in
return try FirebaseDecoder().decode(T.self, from: data)
}
expectRoundTripEquality(of: value, encode: encode, decode: decode, lineNumber: lineNumber)
}
// MARK: - Helper Types
// A wrapper around a UUID that will allow it to be encoded at the top level of an encoder.
struct UUIDCodingWrapper : Codable, Equatable {
let value: UUID
init(_ value: UUID) {
self.value = value
}
static func ==(_ lhs: UUIDCodingWrapper, _ rhs: UUIDCodingWrapper) -> Bool {
return lhs.value == rhs.value
}
}
class CodaleTests: XCTestCase {
// MARK: - Calendar
lazy var calendarValues: [Int : Calendar] = [
#line : Calendar(identifier: .gregorian),
#line : Calendar(identifier: .buddhist),
#line : Calendar(identifier: .chinese),
#line : Calendar(identifier: .coptic),
#line : Calendar(identifier: .ethiopicAmeteMihret),
#line : Calendar(identifier: .ethiopicAmeteAlem),
#line : Calendar(identifier: .hebrew),
#line : Calendar(identifier: .iso8601),
#line : Calendar(identifier: .indian),
#line : Calendar(identifier: .islamic),
#line : Calendar(identifier: .islamicCivil),
#line : Calendar(identifier: .japanese),
#line : Calendar(identifier: .persian),
#line : Calendar(identifier: .republicOfChina),
]
func test_Calendar_Database() {
for (testLine, calendar) in calendarValues {
expectRoundTripEqualityThroughFirebaseDatabase(for: calendar, lineNumber: testLine)
}
}
// MARK: - CharacterSet
lazy var characterSetValues: [Int : CharacterSet] = [
#line : CharacterSet.controlCharacters,
#line : CharacterSet.whitespaces,
#line : CharacterSet.whitespacesAndNewlines,
#line : CharacterSet.decimalDigits,
#line : CharacterSet.letters,
#line : CharacterSet.lowercaseLetters,
#line : CharacterSet.uppercaseLetters,
#line : CharacterSet.nonBaseCharacters,
#line : CharacterSet.alphanumerics,
#line : CharacterSet.decomposables,
#line : CharacterSet.illegalCharacters,
#line : CharacterSet.punctuationCharacters,
#line : CharacterSet.capitalizedLetters,
#line : CharacterSet.symbols,
#line : CharacterSet.newlines
]
func test_CharacterSet_Database() {
for (testLine, characterSet) in characterSetValues {
expectRoundTripEqualityThroughFirebaseDatabase(for: characterSet, lineNumber: testLine)
}
}
// MARK: - CGAffineTransform
lazy var cg_affineTransformValues: [Int : CGAffineTransform] = {
var values = [
#line : CGAffineTransform.identity,
#line : CGAffineTransform(),
#line : CGAffineTransform(translationX: 2.0, y: 2.0),
#line : CGAffineTransform(scaleX: 2.0, y: 2.0),
#line : CGAffineTransform(a: 1.0, b: 2.5, c: 66.2, d: 40.2, tx: -5.5, ty: 3.7),
#line : CGAffineTransform(a: -55.66, b: 22.7, c: 1.5, d: 0.0, tx: -22, ty: -33),
#line : CGAffineTransform(a: 4.5, b: 1.1, c: 0.025, d: 0.077, tx: -0.55, ty: 33.2),
#line : CGAffineTransform(a: 7.0, b: -2.3, c: 6.7, d: 0.25, tx: 0.556, ty: 0.99),
#line : CGAffineTransform(a: 0.498, b: -0.284, c: -0.742, d: 0.3248, tx: 12, ty: 44)
]
if #available(OSX 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) {
values[#line] = CGAffineTransform(rotationAngle: .pi / 2)
}
return values
}()
func test_CGAffineTransform_Database() {
for (testLine, transform) in cg_affineTransformValues {
expectRoundTripEqualityThroughFirebaseDatabase(for: transform, lineNumber: testLine)
}
}
// MARK: - DateComponents
lazy var dateComponents: Set<Calendar.Component> = [
.era, .year, .month, .day, .hour, .minute, .second, .nanosecond,
.weekday, .weekdayOrdinal, .quarter, .weekOfMonth, .weekOfYear,
.yearForWeekOfYear, .timeZone, .calendar
]
func test_DateComponents_Database() {
let calendar = Calendar(identifier: .gregorian)
let components = calendar.dateComponents(dateComponents, from: Date())
expectRoundTripEqualityThroughFirebaseDatabase(for: components, lineNumber: #line - 1)
}
// MARK: - URL
lazy var urlValues: [Int : URL] = {
var values: [Int : URL] = [
#line : URL(fileURLWithPath: NSTemporaryDirectory()),
#line : URL(fileURLWithPath: "/"),
#line : URL(string: "http://swift.org")!,
#line : URL(string: "documentation", relativeTo: URL(string: "http://swift.org")!)!
]
if #available(OSX 10.11, iOS 9.0, *) {
values[#line] = URL(fileURLWithPath: "bin/sh", relativeTo: URL(fileURLWithPath: "/"))
}
return values
}()
func test_URL_Database() {
for (testLine, url) in urlValues {
// URLs encode as single strings in JSON. They lose their baseURL this way.
// For relative URLs, we don't expect them to be equal to the original.
if url.baseURL == nil {
// This is an absolute URL; we can expect equality.
expectRoundTripEqualityThroughFirebaseDatabase(for: TopLevelWrapper(url), lineNumber: testLine)
} else {
// This is a relative URL. Make it absolute first.
let absoluteURL = URL(string: url.absoluteString)!
expectRoundTripEqualityThroughFirebaseDatabase(for: TopLevelWrapper(absoluteURL), lineNumber: testLine)
}
}
}
// MARK: - UUID
lazy var uuidValues: [Int : UUID] = [
#line : UUID(),
#line : UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!,
#line : UUID(uuidString: "e621e1f8-c36c-495a-93fc-0c247a3e6e5f")!,
#line : UUID(uuid: uuid_t(0xe6,0x21,0xe1,0xf8,0xc3,0x6c,0x49,0x5a,0x93,0xfc,0x0c,0x24,0x7a,0x3e,0x6e,0x5f))
]
func test_UUID_Database() {
for (testLine, uuid) in uuidValues {
// We have to wrap the UUID since we cannot have a top-level string.
expectRoundTripEqualityThroughFirebaseDatabase(for: UUIDCodingWrapper(uuid), lineNumber: testLine)
}
}
}

View File

@@ -0,0 +1,859 @@
//
// TestCodableFirebase.swift
// CodableFirebaseTests
//
// Created by Oleksii on 27/12/2017.
// Copyright © 2017 ViolentOctopus. All rights reserved.
//
import XCTest
import CodableFirebase
class TestCodableFirebase: XCTestCase {
// MARK: - Encoding Top-Level Empty Types
func testEncodingTopLevelEmptyStruct() {
_testRoundTrip(of: EmptyStruct(), expectedValue: _emptyDictionary)
}
func testEncodingTopLevelEmptyClass() {
_testRoundTrip(of: EmptyClass(), expectedValue: _emptyDictionary)
}
// MARK: - Encoding Top-Level Single-Value Types
func testEncodingTopLevelSingleValueEnum() {
_testRoundTrip(of: Switch.off)
_testRoundTrip(of: Switch.on)
_testRoundTrip(of: TopLevelWrapper(Switch.off))
_testRoundTrip(of: TopLevelWrapper(Switch.on))
}
func testEncodingTopLevelSingleValueStruct() {
_testRoundTrip(of: Timestamp(3141592653))
_testRoundTrip(of: TopLevelWrapper(Timestamp(3141592653)))
}
func testEncodingTopLevelSingleValueClass() {
_testRoundTrip(of: Counter())
_testRoundTrip(of: TopLevelWrapper(Counter()))
}
// MARK: - Encoding Top-Level Structured Types
func testEncodingTopLevelStructuredStruct() {
_testRoundTrip(of: Address.testValue)
}
func testEncodingTopLevelStructuredClass() {
_testRoundTrip(of: Person.testValue, expectedValue: ["name": "Johnny Appleseed","email":"appleseed@apple.com"])
}
func testEncodingTopLevelStructuredSingleStruct() {
_testRoundTrip(of: Numbers.testValue)
}
func testEncodingTopLevelStructuredSingleClass() {
_testRoundTrip(of: Mapping.testValue)
}
func testEncodingTopLevelDeepStructuredType() {
_testRoundTrip(of: Company.testValue)
}
func testEncodingClassWhichSharesEncoderWithSuper() {
_testRoundTrip(of: Employee.testValue)
}
func testEncodingTopLevelNullableType() {
// EnhancedBool is a type which encodes either as a Bool or as nil.
_testRoundTrip(of: EnhancedBool.true)
_testRoundTrip(of: EnhancedBool.false)
_testRoundTrip(of: EnhancedBool.fileNotFound)
_testRoundTrip(of: TopLevelWrapper(EnhancedBool.true), expectedValue: ["value": true])
_testRoundTrip(of: TopLevelWrapper(EnhancedBool.false), expectedValue: ["value": false])
_testRoundTrip(of: TopLevelWrapper(EnhancedBool.fileNotFound), expectedValue: ["value": NSNull()])
}
// MARK: - Date Strategy Tests
func testEncodingDate() {
_testRoundTrip(of: Date())
_testRoundTrip(of: TopLevelWrapper(Date()))
_testRoundTrip(of: OptionalTopLevelWrapper(Date()))
}
func testEncodingDateSecondsSince1970() {
// Cannot encode an arbitrary number of seconds since we've lost precision since 1970.
let seconds = 1000.0
let expected = ["value":1000]
_testRoundTrip(of: Date(timeIntervalSince1970: seconds),
expectedValue: 1000,
dateEncodingStrategy: .secondsSince1970,
dateDecodingStrategy: .secondsSince1970)
_testRoundTrip(of: TopLevelWrapper(Date(timeIntervalSince1970: seconds)),
expectedValue: expected,
dateEncodingStrategy: .secondsSince1970,
dateDecodingStrategy: .secondsSince1970)
_testRoundTrip(of: OptionalTopLevelWrapper(Date(timeIntervalSince1970: seconds)),
expectedValue: expected,
dateEncodingStrategy: .secondsSince1970,
dateDecodingStrategy: .secondsSince1970)
}
func testEncodingDateMillisecondsSince1970() {
// Cannot encode an arbitrary number of seconds since we've lost precision since 1970.
let seconds = 1000.0
let expectedValue = ["value": 1000000]
_testRoundTrip(of: Date(timeIntervalSince1970: seconds),
expectedValue: 1000000,
dateEncodingStrategy: .millisecondsSince1970,
dateDecodingStrategy: .millisecondsSince1970)
_testRoundTrip(of: TopLevelWrapper(Date(timeIntervalSince1970: seconds)),
expectedValue: expectedValue,
dateEncodingStrategy: .millisecondsSince1970,
dateDecodingStrategy: .millisecondsSince1970)
_testRoundTrip(of: OptionalTopLevelWrapper(Date(timeIntervalSince1970: seconds)),
expectedValue: expectedValue,
dateEncodingStrategy: .millisecondsSince1970,
dateDecodingStrategy: .millisecondsSince1970)
}
func testEncodingDateISO8601() {
if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = .withInternetDateTime
let timestamp = Date(timeIntervalSince1970: 1000)
let expectedValue = ["value": formatter.string(from: timestamp)]
_testRoundTrip(of: timestamp,
expectedValue: formatter.string(from: timestamp),
dateEncodingStrategy: .iso8601,
dateDecodingStrategy: .iso8601)
_testRoundTrip(of: TopLevelWrapper(timestamp),
expectedValue: expectedValue,
dateEncodingStrategy: .iso8601,
dateDecodingStrategy: .iso8601)
_testRoundTrip(of: OptionalTopLevelWrapper(timestamp),
expectedValue: expectedValue,
dateEncodingStrategy: .iso8601,
dateDecodingStrategy: .iso8601)
}
}
func testEncodingDateFormatted() {
let formatter = DateFormatter()
formatter.dateStyle = .full
formatter.timeStyle = .full
let timestamp = Date(timeIntervalSince1970: 1000)
let expectedValue = ["value": formatter.string(from: timestamp)]
_testRoundTrip(of: timestamp,
expectedValue: formatter.string(from: timestamp),
dateEncodingStrategy: .formatted(formatter),
dateDecodingStrategy: .formatted(formatter))
_testRoundTrip(of: TopLevelWrapper(timestamp),
expectedValue: expectedValue,
dateEncodingStrategy: .formatted(formatter),
dateDecodingStrategy: .formatted(formatter))
_testRoundTrip(of: OptionalTopLevelWrapper(timestamp),
expectedValue: expectedValue,
dateEncodingStrategy: .formatted(formatter),
dateDecodingStrategy: .formatted(formatter))
}
func testEncodingDateCustom() {
let timestamp = Date()
// We'll encode a number instead of a date.
let encode = { (_ data: Date, _ encoder: Encoder) throws -> Void in
var container = encoder.singleValueContainer()
try container.encode(42)
}
let decode = { (_: Decoder) throws -> Date in return timestamp }
let expectedValue = ["value": 42]
_testRoundTrip(of: timestamp,
expectedValue: 42,
dateEncodingStrategy: .custom(encode),
dateDecodingStrategy: .custom(decode))
_testRoundTrip(of: TopLevelWrapper(timestamp),
expectedValue: expectedValue,
dateEncodingStrategy: .custom(encode),
dateDecodingStrategy: .custom(decode))
_testRoundTrip(of: OptionalTopLevelWrapper(timestamp),
expectedValue: expectedValue,
dateEncodingStrategy: .custom(encode),
dateDecodingStrategy: .custom(decode))
}
func testEncodingDateCustomEmpty() {
let timestamp = Date()
// Encoding nothing should encode an empty keyed container ({}).
let encode = { (_: Date, _: Encoder) throws -> Void in }
let decode = { (_: Decoder) throws -> Date in return timestamp }
let expectedValue = ["value": [:]]
_testRoundTrip(of: TopLevelWrapper(timestamp),
expectedValue: expectedValue,
dateEncodingStrategy: .custom(encode),
dateDecodingStrategy: .custom(decode))
// Optional dates should encode the same way.
_testRoundTrip(of: OptionalTopLevelWrapper(timestamp),
expectedValue: expectedValue,
dateEncodingStrategy: .custom(encode),
dateDecodingStrategy: .custom(decode))
}
// MARK: - Data Strategy Tests
func testEncodingData() {
let data = Data(bytes: [0xDE, 0xAD, 0xBE, 0xEF])
let expectedValue = ["value":[222,173,190,239]]
_testRoundTrip(of: data,
expectedValue: [222,173,190,239],
dataEncodingStrategy: .deferredToData,
dataDecodingStrategy: .deferredToData)
_testRoundTrip(of: TopLevelWrapper(data),
expectedValue: expectedValue,
dataEncodingStrategy: .deferredToData,
dataDecodingStrategy: .deferredToData)
// Optional data should encode the same way.
_testRoundTrip(of: OptionalTopLevelWrapper(data),
expectedValue: expectedValue,
dataEncodingStrategy: .deferredToData,
dataDecodingStrategy: .deferredToData)
}
func testEncodingDataBase64() {
let data = Data(bytes: [0xDE, 0xAD, 0xBE, 0xEF])
let expectedValue = ["value":"3q2+7w=="]
_testRoundTrip(of: data, expectedValue: "3q2+7w==")
_testRoundTrip(of: TopLevelWrapper(data), expectedValue: expectedValue)
_testRoundTrip(of: OptionalTopLevelWrapper(data), expectedValue: expectedValue)
}
func testEncodingDataCustom() {
// We'll encode a number instead of data.
let encode = { (_ data: Data, _ encoder: Encoder) throws -> Void in
var container = encoder.singleValueContainer()
try container.encode(42)
}
let decode = { (_: Decoder) throws -> Data in return Data() }
let expectedValue = ["value": 42]
_testRoundTrip(of: Data(),
expectedValue: 42,
dataEncodingStrategy: .custom(encode),
dataDecodingStrategy: .custom(decode))
_testRoundTrip(of: TopLevelWrapper(Data()),
expectedValue: expectedValue,
dataEncodingStrategy: .custom(encode),
dataDecodingStrategy: .custom(decode))
// Optional data should encode the same way.
_testRoundTrip(of: OptionalTopLevelWrapper(Data()),
expectedValue: expectedValue,
dataEncodingStrategy: .custom(encode),
dataDecodingStrategy: .custom(decode))
}
func testEncodingDataCustomEmpty() {
// Encoding nothing should encode an empty keyed container ({}).
let encode = { (_: Data, _: Encoder) throws -> Void in }
let decode = { (_: Decoder) throws -> Data in return Data() }
let expectedValue = ["value": [:]]
_testRoundTrip(of: Data(),
expectedValue: [:],
dataEncodingStrategy: .custom(encode),
dataDecodingStrategy: .custom(decode))
_testRoundTrip(of: TopLevelWrapper(Data()),
expectedValue: expectedValue,
dataEncodingStrategy: .custom(encode),
dataDecodingStrategy: .custom(decode))
// Optional Data should encode the same way.
_testRoundTrip(of: OptionalTopLevelWrapper(Data()),
expectedValue: expectedValue,
dataEncodingStrategy: .custom(encode),
dataDecodingStrategy: .custom(decode))
}
// MARK: - Encoder Features
func testNestedContainerCodingPaths() {
let encoder = FirebaseEncoder()
do {
let _ = try encoder.encode(NestedContainersTestType())
} catch let error as NSError {
XCTFail("Caught error during encoding nested container types: \(error)")
}
}
func testSuperEncoderCodingPaths() {
let encoder = FirebaseEncoder()
do {
let _ = try encoder.encode(NestedContainersTestType(testSuperEncoder: true))
} catch let error as NSError {
XCTFail("Caught error during encoding nested container types: \(error)")
}
}
func testInterceptURL() {
// Want to make sure JSONEncoder writes out single-value URLs, not the keyed encoding.
let expectedValue = ["value": "http://swift.org"]
let url = URL(string: "http://swift.org")!
_testRoundTrip(of: url, expectedValue: "http://swift.org")
_testRoundTrip(of: TopLevelWrapper(url), expectedValue: expectedValue)
_testRoundTrip(of: OptionalTopLevelWrapper(url), expectedValue: expectedValue)
}
func testTypeCoercion() {
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int].self)
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int8].self)
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int16].self)
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int32].self)
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int64].self)
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt].self)
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt8].self)
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt16].self)
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt32].self)
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt64].self)
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Float].self)
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Double].self)
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int], as: [Bool].self)
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int8], as: [Bool].self)
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int16], as: [Bool].self)
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int32], as: [Bool].self)
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int64], as: [Bool].self)
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt], as: [Bool].self)
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt8], as: [Bool].self)
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt16], as: [Bool].self)
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt32], as: [Bool].self)
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt64], as: [Bool].self)
_testRoundTripTypeCoercionFailure(of: [0.0, 1.0] as [Float], as: [Bool].self)
_testRoundTripTypeCoercionFailure(of: [0.0, 1.0] as [Double], as: [Bool].self)
}
func testEncodingTopLevelNumericTypes() {
_testRoundTrip(of: 3 as Int)
_testRoundTrip(of: 3 as Int8)
_testRoundTrip(of: 3 as Int16)
_testRoundTrip(of: 3 as Int32)
_testRoundTrip(of: 3 as Int64)
_testRoundTrip(of: 3 as UInt)
_testRoundTrip(of: 3 as UInt8)
_testRoundTrip(of: 3 as UInt16)
_testRoundTrip(of: 3 as UInt32)
_testRoundTrip(of: 3 as UInt64)
_testRoundTrip(of: 3 as Float)
_testRoundTrip(of: 3 as Double)
}
// MARK: - Helper Functions
private var _emptyDictionary: [String: Any] = [:]
private func _testRoundTrip<T>(of value: T,
expectedValue json: Any? = nil,
dateEncodingStrategy: FirebaseEncoder.DateEncodingStrategy = .deferredToDate,
dateDecodingStrategy: FirebaseDecoder.DateDecodingStrategy = .deferredToDate,
dataEncodingStrategy: FirebaseEncoder.DataEncodingStrategy = .base64,
dataDecodingStrategy: FirebaseDecoder.DataDecodingStrategy = .base64) where T : Codable, T : Equatable {
var payload: Any! = nil
do {
let encoder = FirebaseEncoder()
encoder.dateEncodingStrategy = dateEncodingStrategy
encoder.dataEncodingStrategy = dataEncodingStrategy
payload = try encoder.encode(value)
} catch {
XCTFail("Failed to encode \(T.self) to val: \(error)")
}
if let expectedJSON = json.flatMap({ $0 as? NSObject }), let payload = payload as? NSObject {
XCTAssertEqual(expectedJSON, payload, "Produced value not identical to expected value.")
}
do {
let decoder = FirebaseDecoder()
decoder.dateDecodingStrategy = dateDecodingStrategy
decoder.dataDecodingStrategy = dataDecodingStrategy
let decoded = try decoder.decode(T.self, from: payload)
XCTAssertEqual(decoded, value, "\(T.self) did not round-trip to an equal value.")
} catch {
XCTFail("Failed to decode \(T.self) from val: \(error)")
}
}
private func _testRoundTripTypeCoercionFailure<T,U>(of value: T, as type: U.Type) where T : Codable, U : Codable {
do {
let data = try FirebaseEncoder().encode(value)
let _ = try FirebaseDecoder().decode(U.self, from: data)
XCTFail("Coercion from \(T.self) to \(U.self) was expected to fail.")
} catch {}
}
}
// MARK: - Test Types
/* FIXME: Import from %S/Inputs/Coding/SharedTypes.swift somehow. */
// MARK: - Empty Types
fileprivate struct EmptyStruct : Codable, Equatable {
static func ==(_ lhs: EmptyStruct, _ rhs: EmptyStruct) -> Bool {
return true
}
}
fileprivate class EmptyClass : Codable, Equatable {
static func ==(_ lhs: EmptyClass, _ rhs: EmptyClass) -> Bool {
return true
}
}
// MARK: - Single-Value Types
/// A simple on-off switch type that encodes as a single Bool value.
fileprivate enum Switch : Codable {
case off
case on
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
switch try container.decode(Bool.self) {
case false: self = .off
case true: self = .on
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .off: try container.encode(false)
case .on: try container.encode(true)
}
}
}
/// A simple timestamp type that encodes as a single Double value.
fileprivate struct Timestamp : Codable, Equatable {
let value: Double
init(_ value: Double) {
self.value = value
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
value = try container.decode(Double.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.value)
}
static func ==(_ lhs: Timestamp, _ rhs: Timestamp) -> Bool {
return lhs.value == rhs.value
}
}
/// A simple referential counter type that encodes as a single Int value.
fileprivate final class Counter : Codable, Equatable {
var count: Int = 0
init() {}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
count = try container.decode(Int.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.count)
}
static func ==(_ lhs: Counter, _ rhs: Counter) -> Bool {
return lhs === rhs || lhs.count == rhs.count
}
}
// MARK: - Structured Types
/// A simple address type that encodes as a dictionary of values.
fileprivate struct Address : Codable, Equatable {
let street: String
let city: String
let state: String
let zipCode: Int
let country: String
init(street: String, city: String, state: String, zipCode: Int, country: String) {
self.street = street
self.city = city
self.state = state
self.zipCode = zipCode
self.country = country
}
static func ==(_ lhs: Address, _ rhs: Address) -> Bool {
return lhs.street == rhs.street &&
lhs.city == rhs.city &&
lhs.state == rhs.state &&
lhs.zipCode == rhs.zipCode &&
lhs.country == rhs.country
}
static var testValue: Address {
return Address(street: "1 Infinite Loop",
city: "Cupertino",
state: "CA",
zipCode: 95014,
country: "United States")
}
}
/// A simple person class that encodes as a dictionary of values.
fileprivate class Person : Codable, Equatable {
let name: String
let email: String
let website: URL?
init(name: String, email: String, website: URL? = nil) {
self.name = name
self.email = email
self.website = website
}
private enum CodingKeys : String, CodingKey {
case name
case email
case website
}
// FIXME: Remove when subclasses (Employee) are able to override synthesized conformance.
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
email = try container.decode(String.self, forKey: .email)
website = try container.decodeIfPresent(URL.self, forKey: .website)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(email, forKey: .email)
try container.encodeIfPresent(website, forKey: .website)
}
func isEqual(_ other: Person) -> Bool {
return self.name == other.name &&
self.email == other.email &&
self.website == other.website
}
static func ==(_ lhs: Person, _ rhs: Person) -> Bool {
return lhs.isEqual(rhs)
}
class var testValue: Person {
return Person(name: "Johnny Appleseed", email: "appleseed@apple.com")
}
}
/// A class which shares its encoder and decoder with its superclass.
fileprivate class Employee : Person {
let id: Int
init(name: String, email: String, website: URL? = nil, id: Int) {
self.id = id
super.init(name: name, email: email, website: website)
}
enum CodingKeys : String, CodingKey {
case id
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try super.encode(to: encoder)
}
override func isEqual(_ other: Person) -> Bool {
if let employee = other as? Employee {
guard self.id == employee.id else { return false }
}
return super.isEqual(other)
}
override class var testValue: Employee {
return Employee(name: "Johnny Appleseed", email: "appleseed@apple.com", id: 42)
}
}
/// A simple company struct which encodes as a dictionary of nested values.
fileprivate struct Company : Codable, Equatable {
let address: Address
var employees: [Employee]
init(address: Address, employees: [Employee]) {
self.address = address
self.employees = employees
}
static func ==(_ lhs: Company, _ rhs: Company) -> Bool {
return lhs.address == rhs.address && lhs.employees == rhs.employees
}
static var testValue: Company {
return Company(address: Address.testValue, employees: [Employee.testValue])
}
}
/// An enum type which decodes from Bool?.
fileprivate enum EnhancedBool : Codable {
case `true`
case `false`
case fileNotFound
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if container.decodeNil() {
self = .fileNotFound
} else {
let value = try container.decode(Bool.self)
self = value ? .true : .false
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .true: try container.encode(true)
case .false: try container.encode(false)
case .fileNotFound: try container.encodeNil()
}
}
}
/// A type which encodes as an array directly through a single value container.
struct Numbers : Codable, Equatable {
let values = [4, 8, 15, 16, 23, 42]
init() {}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let decodedValues = try container.decode([Int].self)
guard decodedValues == values else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "The Numbers are wrong!"))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(values)
}
static func ==(_ lhs: Numbers, _ rhs: Numbers) -> Bool {
return lhs.values == rhs.values
}
static var testValue: Numbers {
return Numbers()
}
}
/// A type which encodes as a dictionary directly through a single value container.
fileprivate final class Mapping : Codable, Equatable {
let values: [String : URL]
init(values: [String : URL]) {
self.values = values
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
values = try container.decode([String : URL].self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(values)
}
static func ==(_ lhs: Mapping, _ rhs: Mapping) -> Bool {
return lhs === rhs || lhs.values == rhs.values
}
static var testValue: Mapping {
return Mapping(values: ["Apple": URL(string: "http://apple.com")!,
"localhost": URL(string: "http://127.0.0.1")!])
}
}
struct NestedContainersTestType : Encodable {
let testSuperEncoder: Bool
init(testSuperEncoder: Bool = false) {
self.testSuperEncoder = testSuperEncoder
}
enum TopLevelCodingKeys : Int, CodingKey {
case a
case b
case c
}
enum IntermediateCodingKeys : Int, CodingKey {
case one
case two
}
func encode(to encoder: Encoder) throws {
if self.testSuperEncoder {
var topLevelContainer = encoder.container(keyedBy: TopLevelCodingKeys.self)
expectEqualPaths(encoder.codingPath, [], "Top-level Encoder's codingPath changed.")
expectEqualPaths(topLevelContainer.codingPath, [], "New first-level keyed container has non-empty codingPath.")
let superEncoder = topLevelContainer.superEncoder(forKey: .a)
expectEqualPaths(encoder.codingPath, [], "Top-level Encoder's codingPath changed.")
expectEqualPaths(topLevelContainer.codingPath, [], "First-level keyed container's codingPath changed.")
expectEqualPaths(superEncoder.codingPath, [TopLevelCodingKeys.a], "New superEncoder had unexpected codingPath.")
_testNestedContainers(in: superEncoder, baseCodingPath: [TopLevelCodingKeys.a])
} else {
_testNestedContainers(in: encoder, baseCodingPath: [])
}
}
func _testNestedContainers(in encoder: Encoder, baseCodingPath: [CodingKey]) {
expectEqualPaths(encoder.codingPath, baseCodingPath, "New encoder has non-empty codingPath.")
// codingPath should not change upon fetching a non-nested container.
var firstLevelContainer = encoder.container(keyedBy: TopLevelCodingKeys.self)
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.")
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "New first-level keyed container has non-empty codingPath.")
// Nested Keyed Container
do {
// Nested container for key should have a new key pushed on.
var secondLevelContainer = firstLevelContainer.nestedContainer(keyedBy: IntermediateCodingKeys.self, forKey: .a)
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.")
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.")
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "New second-level keyed container had unexpected codingPath.")
// Inserting a keyed container should not change existing coding paths.
let thirdLevelContainerKeyed = secondLevelContainer.nestedContainer(keyedBy: IntermediateCodingKeys.self, forKey: .one)
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.")
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.")
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "Second-level keyed container's codingPath changed.")
expectEqualPaths(thirdLevelContainerKeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.a, IntermediateCodingKeys.one], "New third-level keyed container had unexpected codingPath.")
// Inserting an unkeyed container should not change existing coding paths.
let thirdLevelContainerUnkeyed = secondLevelContainer.nestedUnkeyedContainer(forKey: .two)
expectEqualPaths(encoder.codingPath, baseCodingPath + [], "Top-level Encoder's codingPath changed.")
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath + [], "First-level keyed container's codingPath changed.")
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "Second-level keyed container's codingPath changed.")
expectEqualPaths(thirdLevelContainerUnkeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.a, IntermediateCodingKeys.two], "New third-level unkeyed container had unexpected codingPath.")
}
// Nested Unkeyed Container
do {
// Nested container for key should have a new key pushed on.
var secondLevelContainer = firstLevelContainer.nestedUnkeyedContainer(forKey: .b)
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.")
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.")
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.b], "New second-level keyed container had unexpected codingPath.")
// Appending a keyed container should not change existing coding paths.
let thirdLevelContainerKeyed = secondLevelContainer.nestedContainer(keyedBy: IntermediateCodingKeys.self)
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.")
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.")
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.b], "Second-level unkeyed container's codingPath changed.")
expectEqualPaths(thirdLevelContainerKeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.b, _TestKey(index: 0)], "New third-level keyed container had unexpected codingPath.")
// Appending an unkeyed container should not change existing coding paths.
let thirdLevelContainerUnkeyed = secondLevelContainer.nestedUnkeyedContainer()
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.")
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.")
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.b], "Second-level unkeyed container's codingPath changed.")
expectEqualPaths(thirdLevelContainerUnkeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.b, _TestKey(index: 1)], "New third-level unkeyed container had unexpected codingPath.")
}
}
}
// MARK: - Helper Types
/// A key type which can take on any string or integer value.
/// This needs to mirror _JSONKey.
fileprivate struct _TestKey : CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
init?(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
init(index: Int) {
self.stringValue = "Index \(index)"
self.intValue = index
}
}
/// Wraps a type T (as T?) so that it can be encoded at the top level of a payload.
fileprivate struct OptionalTopLevelWrapper<T> : Codable, Equatable where T : Codable, T : Equatable {
let value: T?
init(_ value: T) {
self.value = value
}
// Provide an implementation of Codable to encode(forKey:) instead of encodeIfPresent(forKey:).
private enum CodingKeys : String, CodingKey {
case value
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
value = try container.decode(T?.self, forKey: .value)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(value, forKey: .value)
}
static func ==(_ lhs: OptionalTopLevelWrapper<T>, _ rhs: OptionalTopLevelWrapper<T>) -> Bool {
return lhs.value == rhs.value
}
}

View File

@@ -29,280 +29,8 @@ fileprivate struct Document: Codable, Equatable {
}
}
fileprivate struct EmptyStruct : Codable, Equatable {
static func ==(_ lhs: EmptyStruct, _ rhs: EmptyStruct) -> Bool {
return true
}
}
fileprivate class EmptyClass : Codable, Equatable {
static func ==(_ lhs: EmptyClass, _ rhs: EmptyClass) -> Bool {
return true
}
}
// MARK: - Single-Value Types
/// A simple on-off switch type that encodes as a single Bool value.
fileprivate enum Switch : Codable {
case off
case on
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
switch try container.decode(Bool.self) {
case false: self = .off
case true: self = .on
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .off: try container.encode(false)
case .on: try container.encode(true)
}
}
}
/// A simple timestamp type that encodes as a single Double value.
fileprivate struct Timestamp : Codable, Equatable {
let value: Double
init(_ value: Double) {
self.value = value
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
value = try container.decode(Double.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.value)
}
static func ==(_ lhs: Timestamp, _ rhs: Timestamp) -> Bool {
return lhs.value == rhs.value
}
}
/// A simple referential counter type that encodes as a single Int value.
fileprivate final class Counter : Codable, Equatable {
var count: Int = 0
init() {}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
count = try container.decode(Int.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.count)
}
static func ==(_ lhs: Counter, _ rhs: Counter) -> Bool {
return lhs === rhs || lhs.count == rhs.count
}
}
// MARK: - Structured Types
/// A simple address type that encodes as a dictionary of values.
fileprivate struct Address : Codable, Equatable {
let street: String
let city: String
let state: String
let zipCode: Int
let country: String
init(street: String, city: String, state: String, zipCode: Int, country: String) {
self.street = street
self.city = city
self.state = state
self.zipCode = zipCode
self.country = country
}
static func ==(_ lhs: Address, _ rhs: Address) -> Bool {
return lhs.street == rhs.street &&
lhs.city == rhs.city &&
lhs.state == rhs.state &&
lhs.zipCode == rhs.zipCode &&
lhs.country == rhs.country
}
static var testValue: Address {
return Address(street: "1 Infinite Loop",
city: "Cupertino",
state: "CA",
zipCode: 95014,
country: "United States")
}
}
/// A simple person class that encodes as a dictionary of values.
fileprivate class Person : Codable, Equatable {
let name: String
let email: String
let website: URL?
init(name: String, email: String, website: URL? = nil) {
self.name = name
self.email = email
self.website = website
}
private enum CodingKeys : String, CodingKey {
case name
case email
case website
}
// FIXME: Remove when subclasses (Employee) are able to override synthesized conformance.
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
email = try container.decode(String.self, forKey: .email)
website = try container.decodeIfPresent(URL.self, forKey: .website)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(email, forKey: .email)
try container.encodeIfPresent(website, forKey: .website)
}
func isEqual(_ other: Person) -> Bool {
return self.name == other.name &&
self.email == other.email &&
self.website == other.website
}
static func ==(_ lhs: Person, _ rhs: Person) -> Bool {
return lhs.isEqual(rhs)
}
class var testValue: Person {
return Person(name: "Johnny Appleseed", email: "appleseed@apple.com")
}
}
/// A class which shares its encoder and decoder with its superclass.
fileprivate class Employee : Person {
let id: Int
init(name: String, email: String, website: URL? = nil, id: Int) {
self.id = id
super.init(name: name, email: email, website: website)
}
enum CodingKeys : String, CodingKey {
case id
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try super.encode(to: encoder)
}
override func isEqual(_ other: Person) -> Bool {
if let employee = other as? Employee {
guard self.id == employee.id else { return false }
}
return super.isEqual(other)
}
override class var testValue: Employee {
return Employee(name: "Johnny Appleseed", email: "appleseed@apple.com", id: 42)
}
}
/// A simple company struct which encodes as a dictionary of nested values.
fileprivate struct Company : Codable, Equatable {
let address: Address
var employees: [Employee]
init(address: Address, employees: [Employee]) {
self.address = address
self.employees = employees
}
static func ==(_ lhs: Company, _ rhs: Company) -> Bool {
return lhs.address == rhs.address && lhs.employees == rhs.employees
}
static var testValue: Company {
return Company(address: Address.testValue, employees: [Employee.testValue])
}
}
/// An enum type which decodes from Bool?.
fileprivate enum EnhancedBool : Codable {
case `true`
case `false`
case fileNotFound
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if container.decodeNil() {
self = .fileNotFound
} else {
let value = try container.decode(Bool.self)
self = value ? .true : .false
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .true: try container.encode(true)
case .false: try container.encode(false)
case .fileNotFound: try container.encodeNil()
}
}
}
/// A type which encodes as a dictionary directly through a single value container.
fileprivate final class Mapping : Codable, Equatable {
let values: [String : URL]
init(values: [String : URL]) {
self.values = values
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
values = try container.decode([String : URL].self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(values)
}
static func ==(_ lhs: Mapping, _ rhs: Mapping) -> Bool {
return lhs === rhs || lhs.values == rhs.values
}
static var testValue: Mapping {
return Mapping(values: ["Apple": URL(string: "http://apple.com")!,
"localhost": URL(string: "http://127.0.0.1")!])
}
}
/// Wraps a type T so that it can be encoded at the top level of a payload.
fileprivate struct TopLevelWrapper<T> : Codable, Equatable where T : Codable, T : Equatable {
struct TopLevelWrapper<T> : Codable, Equatable where T : Codable, T : Equatable {
enum CodingKeys : String, CodingKey {
case value
}
@@ -328,118 +56,6 @@ fileprivate struct TopLevelWrapper<T> : Codable, Equatable where T : Codable, T
}
}
struct NestedContainersTestType : Encodable {
let testSuperEncoder: Bool
init(testSuperEncoder: Bool = false) {
self.testSuperEncoder = testSuperEncoder
}
enum TopLevelCodingKeys : Int, CodingKey {
case a
case b
case c
}
enum IntermediateCodingKeys : Int, CodingKey {
case one
case two
}
func encode(to encoder: Encoder) throws {
if self.testSuperEncoder {
var topLevelContainer = encoder.container(keyedBy: TopLevelCodingKeys.self)
expectEqualPaths(encoder.codingPath, [], "Top-level Encoder's codingPath changed.")
expectEqualPaths(topLevelContainer.codingPath, [], "New first-level keyed container has non-empty codingPath.")
let superEncoder = topLevelContainer.superEncoder(forKey: .a)
expectEqualPaths(encoder.codingPath, [], "Top-level Encoder's codingPath changed.")
expectEqualPaths(topLevelContainer.codingPath, [], "First-level keyed container's codingPath changed.")
expectEqualPaths(superEncoder.codingPath, [TopLevelCodingKeys.a], "New superEncoder had unexpected codingPath.")
_testNestedContainers(in: superEncoder, baseCodingPath: [TopLevelCodingKeys.a])
} else {
_testNestedContainers(in: encoder, baseCodingPath: [])
}
}
func _testNestedContainers(in encoder: Encoder, baseCodingPath: [CodingKey]) {
expectEqualPaths(encoder.codingPath, baseCodingPath, "New encoder has non-empty codingPath.")
// codingPath should not change upon fetching a non-nested container.
var firstLevelContainer = encoder.container(keyedBy: TopLevelCodingKeys.self)
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.")
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "New first-level keyed container has non-empty codingPath.")
// Nested Keyed Container
do {
// Nested container for key should have a new key pushed on.
var secondLevelContainer = firstLevelContainer.nestedContainer(keyedBy: IntermediateCodingKeys.self, forKey: .a)
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.")
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.")
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "New second-level keyed container had unexpected codingPath.")
// Inserting a keyed container should not change existing coding paths.
let thirdLevelContainerKeyed = secondLevelContainer.nestedContainer(keyedBy: IntermediateCodingKeys.self, forKey: .one)
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.")
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.")
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "Second-level keyed container's codingPath changed.")
expectEqualPaths(thirdLevelContainerKeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.a, IntermediateCodingKeys.one], "New third-level keyed container had unexpected codingPath.")
// Inserting an unkeyed container should not change existing coding paths.
let thirdLevelContainerUnkeyed = secondLevelContainer.nestedUnkeyedContainer(forKey: .two)
expectEqualPaths(encoder.codingPath, baseCodingPath + [], "Top-level Encoder's codingPath changed.")
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath + [], "First-level keyed container's codingPath changed.")
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "Second-level keyed container's codingPath changed.")
expectEqualPaths(thirdLevelContainerUnkeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.a, IntermediateCodingKeys.two], "New third-level unkeyed container had unexpected codingPath.")
}
// Nested Unkeyed Container
do {
// Nested container for key should have a new key pushed on.
var secondLevelContainer = firstLevelContainer.nestedUnkeyedContainer(forKey: .b)
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.")
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.")
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.b], "New second-level keyed container had unexpected codingPath.")
// Appending a keyed container should not change existing coding paths.
let thirdLevelContainerKeyed = secondLevelContainer.nestedContainer(keyedBy: IntermediateCodingKeys.self)
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.")
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.")
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.b], "Second-level unkeyed container's codingPath changed.")
expectEqualPaths(thirdLevelContainerKeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.b, _TestKey(index: 0)], "New third-level keyed container had unexpected codingPath.")
// Appending an unkeyed container should not change existing coding paths.
let thirdLevelContainerUnkeyed = secondLevelContainer.nestedUnkeyedContainer()
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.")
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.")
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.b], "Second-level unkeyed container's codingPath changed.")
expectEqualPaths(thirdLevelContainerUnkeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.b, _TestKey(index: 1)], "New third-level unkeyed container had unexpected codingPath.")
}
}
}
/// A key type which can take on any string or integer value.
/// This needs to mirror _PlistKey.
fileprivate struct _TestKey : CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
init?(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
init(index: Int) {
self.stringValue = "Index \(index)"
self.intValue = index
}
}
class TestCodableFirestore: XCTestCase {
func testFirebaseEncoder() {
@@ -466,116 +82,6 @@ class TestCodableFirestore: XCTestCase {
XCTAssertEqual(try? FirestoreDecoder().decode(Document.self, from: dict) , model)
}
func testEncodingTopLevelEmptyStruct() {
_testRoundTrip(of: EmptyStruct(), expected: [:])
}
func testEncodingTopLevelEmptyClass() {
_testRoundTrip(of: EmptyClass(), expected: [:])
}
// MARK: - Encoding Top-Level Single-Value Types
func testEncodingTopLevelSingleValueEnum() {
let s1 = Switch.off
_testEncodeFailure(of: s1)
_testRoundTrip(of: TopLevelWrapper(s1))
let s2 = Switch.on
_testEncodeFailure(of: s2)
_testRoundTrip(of: TopLevelWrapper(s2))
}
func testEncodingTopLevelSingleValueStruct() {
let t = Timestamp(3141592653)
_testEncodeFailure(of: t)
_testRoundTrip(of: TopLevelWrapper(t))
}
func testEncodingTopLevelSingleValueClass() {
let c = Counter()
_testEncodeFailure(of: c)
_testRoundTrip(of: TopLevelWrapper(c))
}
// MARK: - Encoding Top-Level Structured Types
func testEncodingTopLevelStructuredStruct() {
// Address is a struct type with multiple fields.
_testRoundTrip(of: Address.testValue)
}
func testEncodingTopLevelStructuredClass() {
// Person is a class with multiple fields.
_testRoundTrip(of: Person.testValue)
}
func testEncodingTopLevelStructuredSingleClass() {
// Mapping is a class which encodes as a dictionary through a single value container.
_testRoundTrip(of: Mapping.testValue)
}
func testEncodingTopLevelDeepStructuredType() {
// Company is a type with fields which are Codable themselves.
_testRoundTrip(of: Company.testValue)
}
func testEncodingClassWhichSharesEncoderWithSuper() {
// Employee is a type which shares its encoder & decoder with its superclass, Person.
_testRoundTrip(of: Employee.testValue)
}
func testEncodingTopLevelNullableType() {
// EnhancedBool is a type which encodes either as a Bool or as nil.
_testEncodeFailure(of: EnhancedBool.true)
_testEncodeFailure(of: EnhancedBool.false)
_testEncodeFailure(of: EnhancedBool.fileNotFound)
_testRoundTrip(of: TopLevelWrapper(EnhancedBool.true))
_testRoundTrip(of: TopLevelWrapper(EnhancedBool.false))
_testRoundTrip(of: TopLevelWrapper(EnhancedBool.fileNotFound))
}
func testEncodingTopLevelNumericTypes() {
_testRoundTrip(of: TopLevelWrapper(3 as Int))
_testRoundTrip(of: TopLevelWrapper(3 as Int8))
_testRoundTrip(of: TopLevelWrapper(3 as Int16))
_testRoundTrip(of: TopLevelWrapper(3 as Int32))
_testRoundTrip(of: TopLevelWrapper(3 as Int64))
_testRoundTrip(of: TopLevelWrapper(3 as UInt))
_testRoundTrip(of: TopLevelWrapper(3 as UInt8))
_testRoundTrip(of: TopLevelWrapper(3 as UInt16))
_testRoundTrip(of: TopLevelWrapper(3 as UInt32))
_testRoundTrip(of: TopLevelWrapper(3 as UInt64))
_testRoundTrip(of: TopLevelWrapper(3 as Float))
_testRoundTrip(of: TopLevelWrapper(3 as Double))
}
func testTypeCoercion() {
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int].self)
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int8].self)
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int16].self)
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int32].self)
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int64].self)
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt].self)
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt8].self)
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt16].self)
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt32].self)
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt64].self)
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Float].self)
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Double].self)
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int], as: [Bool].self)
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int8], as: [Bool].self)
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int16], as: [Bool].self)
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int32], as: [Bool].self)
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int64], as: [Bool].self)
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt], as: [Bool].self)
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt8], as: [Bool].self)
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt16], as: [Bool].self)
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt32], as: [Bool].self)
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt64], as: [Bool].self)
_testRoundTripTypeCoercionFailure(of: [0.0, 1.0] as [Float], as: [Bool].self)
_testRoundTripTypeCoercionFailure(of: [0.0, 1.0] as [Double], as: [Bool].self)
}
// MARK: - Encoder Features
func testNestedContainerCodingPaths() {
do {
@@ -610,14 +116,6 @@ class TestCodableFirestore: XCTestCase {
} catch {}
}
private func _testRoundTripTypeCoercionFailure<T,U>(of value: T, as type: U.Type) where T : Codable, U : Codable {
do {
let data = try FirestoreEncoder().encode(value)
let _ = try FirestoreDecoder().decode(U.self, from: data)
XCTFail("Coercion from \(T.self) to \(U.self) was expected to fail.")
} catch {}
}
private func _testRoundTrip<T>(of value: T, expected dict: [String: Any]? = nil) where T : Codable, T : Equatable {
var payload: [String: Any]! = nil
do {

View File

@@ -21,6 +21,36 @@ struct Model: Codable {
}
```
### Firebase Database usage
This is how you would use the library with [Firebase Realtime Database](https://firebase.google.com/products/realtime-database/):
```swift
import Firebase
import CodableFirebase
let model: Model // here you will create an instance of Model
let data = try! FirebaseEncoder().encode(model)
Database.database().reference().child("model").setValue(data)
```
And here is how you would read the same value from [Firebase Realtime Database](https://firebase.google.com/products/realtime-database/):
```swift
Database.database().reference().child("model").observeSingleEvent(of: .value, with: { (snapshot) in
guard let value = snapshot.value else { return }
do {
let model = try FirebaseDecoder().decode(Model.self, from: value)
print(model)
} catch let error {
print(error)
}
})
```
### Firestore usage
And this is how you would encode it with [Firebase Firestore](https://firebase.google.com/products/firestore/):
```swift