72 Commits

Author SHA1 Message Date
Oleksii Dykan
505f140e29 0.0.11 release 2018-05-13 21:40:47 +01:00
Oleksii Dykan
7ac3efc8b8 0.0.10 release 2018-05-13 21:22:43 +01:00
Oleksii Dykan
55b8bd92c1 0.0.9 release 2018-05-13 21:12:05 +01:00
Oleksii Dykan
9885bf2381 Merge pull request #39 from alickbass/fastlane_releases
Fastlane releases
2018-04-28 14:26:08 +01:00
Marcos Griselli
5e9f86496e Removed Appfile 2018-04-26 19:13:44 -03:00
Marcos Griselli
9b65f4e473 revert podspec version 2018-04-25 10:52:01 -03:00
Marcos Griselli
cef13572db fastlane version number 2018-04-25 10:37:18 -03:00
Marcos Griselli
03459ec82a 0.0.9 release 2018-04-25 10:24:41 -03:00
Marcos Griselli
8ff6771f42 Initial fastlane release configuration 2018-04-25 10:23:20 -03:00
Oleksii Dykan
3767b9a771 Merge pull request #38 from marcosgriselli/master
Prepare 0.0.8 release for Swift 4.1
2018-04-25 08:21:49 +01:00
Marcos Griselli
c722389e5c Updated info plist, podspec and xcodeproj to new release 0.0.8 and Swift 4.1 2018-04-23 06:32:36 -03:00
Oleksii Dykan
6b0b78e597 Merge pull request #33 from marcosgriselli/master
Changes for Swift 4.1 and Xcode9.3
2018-04-21 09:19:10 +01:00
Marcos Griselli
1fbf4295b3 updated travis to Xcode9.3 2018-04-20 20:29:25 -03:00
Marcos Griselli
8ed17c7aec updates flatMap to compactMap 2018-04-03 09:52:36 -03:00
Oleksii Dykan
3cae6e90d9 Bump version number 2018-02-20 23:13:02 +01:00
Oleksii Dykan
222b080693 Merge pull request #24 from alickbass/update-encoder
Update Encoder
2018-02-20 23:11:31 +01:00
Oleksii Dykan
5b8627dbbe If the value pushed a container before throwing, pop it back off to restore state 2018-02-20 23:00:29 +01:00
Oleksii Dykan
1384814716 Bump version number 2018-02-11 11:44:17 +01:00
Oleksii Dykan
b0e86866a0 Merge pull request #21 from alickbass/fix-decimal-encoding
Fix handling decimal value
2018-02-11 11:41:33 +01:00
Oleksii Dykan
0c60f70d91 Fix handling decimal value 2018-02-11 11:29:42 +01:00
Oleksii Dykan
df9dfe1dda Fix cocoapods link 2018-01-30 10:56:48 +01:00
Oleksii Dykan
2afa8c101c Bump version number 2018-01-29 11:59:22 +01:00
Oleksii Dykan
ec22c1649e Merge pull request #17 from alickbass/field-type-support
Field type support
2018-01-29 11:52:38 +01:00
Oleksii Dykan
8279c70901 Update README 2018-01-29 11:32:46 +01:00
Oleksii Dykan
c4ed0ec8eb Refactor to FirestoreDecodable and FirestoreEncodable protocols 2018-01-29 11:25:25 +01:00
Oleksii Dykan
fcfb7b8b1e Fix setting option for skipping values 2018-01-29 11:19:33 +01:00
Oleksii Dykan
747d540d77 Rename bool option to skip firestore types 2018-01-29 11:18:27 +01:00
Oleksii Dykan
e6947deb6b Bump version number 2018-01-26 12:53:08 +01:00
Oleksii Dykan
eaf809498a Merge pull request #14 from alickbass/geopoint-and-reference-encoding
Geopoint and reference encoding
2018-01-26 12:39:06 +01:00
Oleksii Dykan
3a08c9e650 Update README 2018-01-26 11:42:35 +01:00
Oleksii Dykan
4cfd13702b Cover firestore types with tests 2018-01-26 11:12:33 +01:00
Oleksii Dykan
3c61d54654 Fix checking for protocol conformance 2018-01-26 10:55:02 +01:00
Oleksii Dykan
2528453f9b Cover firebase database geopoint and reference with tests 2018-01-26 10:20:19 +01:00
Oleksii Dykan
587db265cd Make protocols public 2018-01-26 10:20:03 +01:00
Oleksii Dykan
04facd2f6d Fix skipping encoding 2018-01-26 10:19:44 +01:00
Oleksii Dykan
f3eed29a7d Add encoding the geo point and document reference 2018-01-25 17:18:29 +01:00
Oleksii Dykan
7aff81a267 Add geopointtype and documentreference protocols 2018-01-25 17:14:11 +01:00
Oleksii Dykan
9fa47dd610 Add skip geo and reference flag 2018-01-25 16:33:32 +01:00
Oleksii Dykan
31ecb57f14 Add enum example to README 2018-01-18 14:23:43 +01:00
Oleksii Dykan
6a6fcc1d07 Update README.md 2018-01-16 16:41:55 +01:00
Oleksii Dykan
02cd074714 Update README.md 2018-01-10 17:20:02 +01:00
Oleksii Dykan
69d0a835be Update README.md 2017-12-29 14:55:57 +01:00
Oleksii Dykan
e0b61e57e5 Update README.md 2017-12-28 12:16:28 +01:00
Oleksii Dykan
e32bd8f4d6 Update README.md 2017-12-28 12:12:36 +01:00
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
21 changed files with 3577 additions and 2140 deletions

View File

@@ -1,11 +1,11 @@
language: objective-c
osx_image: xcode9
osx_image: xcode9.3
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"

19
CodableFirebase.podspec Normal file
View File

@@ -0,0 +1,19 @@
Pod::Spec.new do |s|
s.name = "CodableFirebase"
s.version = "0.0.11"
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.swift_version = '4.1'
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;
@@ -149,7 +167,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0900;
LastUpgradeCheck = 0900;
LastUpgradeCheck = 0930;
ORGANIZATIONNAME = ViolentOctopus;
TargetAttributes = {
CE7DD3661F9CFA81000225C5 = {
@@ -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;
};
@@ -241,6 +265,7 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
@@ -248,6 +273,7 @@
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
@@ -300,6 +326,7 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
@@ -307,6 +334,7 @@
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0900"
LastUpgradeVersion = "0930"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -26,7 +26,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
@@ -56,7 +55,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,589 @@
//
// 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 skipFirestoreTypes: Bool
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)
} else if T.self == Decimal.self || T.self == NSDecimalNumber.self {
return (value as! NSDecimalNumber)
} else if options.skipFirestoreTypes && (value is FirestoreEncodable) {
guard let value = value as? NSObject else {
throw DocumentReferenceError.typeIsNotNSObject
}
return value
}
// The value should request a container from the _FirebaseEncoder.
let depth = self.storage.count
do {
try value.encode(to: self)
} catch {
// If the value pushed a container before throwing, pop it back off to restore state.
if self.storage.count > depth {
let _ = self.storage.popContainer()
}
throw error
}
// The top container should be a new container.
guard self.storage.count > depth else {
return nil
}
return storage.popContainer()
}
}
extension _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,66 @@
//
// 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,
skipFirestoreTypes: false,
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,72 @@
//
// 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,
skipFirestoreTypes: false,
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,13 @@ open class FirestoreEncoder {
}
internal func encodeToTopLevelContainer<Value : Encodable>(_ value: Value) throws -> Any {
let encoder = _FirestoreEncoder()
let options = _FirebaseEncoder._Options(
dateEncodingStrategy: nil,
dataEncodingStrategy: nil,
skipFirestoreTypes: true,
userInfo: userInfo
)
let encoder = _FirebaseEncoder(options: options)
guard let topLevel = try encoder.box_(value) else {
throw EncodingError.invalidValue(value,
EncodingError.Context(codingPath: [],
@@ -34,489 +42,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.11</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

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>0.0.11</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>

View File

@@ -0,0 +1,885 @@
//
// 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: - GeoPoint
func testEncodingGeoPoint() {
let point = Point(latitude: 2, longitude: 2)
XCTAssertEqual((try? FirebaseEncoder().encode(point)) as? NSDictionary, ["latitude": 2, "longitude": 2])
XCTAssertEqual(try? FirebaseDecoder().decode(Point.self, from: ["latitude": 2, "longitude": 2]), point)
}
// MARK: - Document Reference
func testEncodingDocumentReference() {
XCTAssertThrowsError(try FirebaseEncoder().encode(DocumentReference()))
XCTAssertThrowsError(try FirebaseDecoder().decode(DocumentReference.self, from: []))
}
// 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: - GeoPoint
struct Point: GeoPointType, Equatable {
let latitude: Double
let longitude: Double
static func == (lhs: Point, rhs: Point) -> Bool {
return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude
}
}
// MARK: - ReferenceType
fileprivate struct DocumentReference: DocumentReferenceType {}
// 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 {
@@ -603,6 +109,26 @@ class TestCodableFirestore: XCTestCase {
_testRoundTrip(of: TopLevelWrapper(date), expected: ["value": date])
}
func testDecimalValue() {
let value = Decimal(2)
_testRoundTrip(of: TopLevelWrapper(value), expected: ["value": value])
}
// MARK: - GeoPoint & Document Reference
func testEncodingGeoPoint() {
let point = GeoPoint(latitude: 2, longitude: 2)
let wrapper = TopLevelWrapper(point)
XCTAssertEqual((try? FirestoreEncoder().encode(wrapper)) as NSDictionary?, ["value": point])
XCTAssertEqual(try? FirestoreDecoder().decode(TopLevelWrapper<GeoPoint>.self, from: ["value": point]), wrapper)
XCTAssertThrowsError(try FirestoreEncoder().encode(TopLevelWrapper(Point(latitude: 2, longitude: 2))))
}
func testEncodingDocumentReference() {
let val = TopLevelWrapper(DocumentReference())
XCTAssertEqual((try? FirestoreEncoder().encode(val)) as NSDictionary?, ["value": val.value])
XCTAssertEqual(try? FirestoreDecoder().decode(TopLevelWrapper<DocumentReference>.self, from: ["value": val.value]), val)
}
private func _testEncodeFailure<T : Encodable>(of value: T) {
do {
let _ = try FirestoreEncoder().encode(value)
@@ -610,14 +136,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 {
@@ -666,3 +184,21 @@ func expectEqualPaths(_ lhs: [CodingKey], _ rhs: [CodingKey], _ prefix: String)
XCTAssertEqual(key1.stringValue, key2.stringValue, "\(prefix) CodingKey.stringValue mismatch: \(type(of: key1))('\(key1.stringValue)') != \(type(of: key2))('\(key2.stringValue)')")
}
}
// MARK: - GeioPoint
fileprivate class GeoPoint: NSObject, GeoPointType {
let latitude: Double
let longitude: Double
required init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
}
static func == (lhs: Point, rhs: Point) -> Bool {
return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude
}
}
// MARK: - ReferenceType
fileprivate class DocumentReference: NSObject, DocumentReferenceType {}

4
Gemfile Normal file
View File

@@ -0,0 +1,4 @@
source 'https://rubygems.org'
gem 'fastlane'
gem 'cocoapods', '1.5.0'

205
Gemfile.lock Normal file
View File

@@ -0,0 +1,205 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.0)
activesupport (4.2.10)
i18n (~> 0.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
addressable (2.5.2)
public_suffix (>= 2.0.2, < 4.0)
atomos (0.1.2)
babosa (1.0.2)
claide (1.0.2)
cocoapods (1.5.0)
activesupport (>= 4.0.2, < 5)
claide (>= 1.0.2, < 2.0)
cocoapods-core (= 1.5.0)
cocoapods-deintegrate (>= 1.0.2, < 2.0)
cocoapods-downloader (>= 1.2.0, < 2.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
cocoapods-search (>= 1.0.0, < 2.0)
cocoapods-stats (>= 1.0.0, < 2.0)
cocoapods-trunk (>= 1.3.0, < 2.0)
cocoapods-try (>= 1.1.0, < 2.0)
colored2 (~> 3.1)
escape (~> 0.0.4)
fourflusher (~> 2.0.1)
gh_inspector (~> 1.0)
molinillo (~> 0.6.5)
nap (~> 1.0)
ruby-macho (~> 1.1)
xcodeproj (>= 1.5.7, < 2.0)
cocoapods-core (1.5.0)
activesupport (>= 4.0.2, < 6)
fuzzy_match (~> 2.0.4)
nap (~> 1.0)
cocoapods-deintegrate (1.0.2)
cocoapods-downloader (1.2.0)
cocoapods-plugins (1.0.0)
nap
cocoapods-search (1.0.0)
cocoapods-stats (1.0.0)
cocoapods-trunk (1.3.0)
nap (>= 0.8, < 2.0)
netrc (~> 0.11)
cocoapods-try (1.1.0)
colored (1.2)
colored2 (3.1.2)
commander-fastlane (4.4.6)
highline (~> 1.7.2)
concurrent-ruby (1.0.5)
declarative (0.0.10)
declarative-option (0.1.0)
domain_name (0.5.20180417)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.4.0)
emoji_regex (0.1.1)
escape (0.0.4)
excon (0.62.0)
faraday (0.15.0)
multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6)
faraday (>= 0.7.4)
http-cookie (~> 1.0.0)
faraday_middleware (0.12.2)
faraday (>= 0.7.4, < 1.0)
fastimage (2.1.1)
fastlane (2.93.1)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
babosa (>= 1.0.2, < 2.0.0)
bundler (>= 1.12.0, < 2.0.0)
colored
commander-fastlane (>= 4.4.6, < 5.0.0)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (~> 0.1)
excon (>= 0.45.0, < 1.0.0)
faraday (~> 0.9)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 0.9)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-api-client (>= 0.13.1, < 0.14.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
mini_magick (~> 4.5.1)
multi_json
multi_xml (~> 0.5)
multipart-post (~> 2.0.0)
plist (>= 3.1.0, < 4.0.0)
public_suffix (~> 2.0.0)
rubyzip (>= 1.1.0, < 2.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
slack-notifier (>= 2.0.0, < 3.0.0)
terminal-notifier (>= 1.6.2, < 2.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.5.7, < 2.0.0)
xcpretty (>= 0.2.4, < 1.0.0)
xcpretty-travis-formatter (>= 0.0.3)
fourflusher (2.0.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.3)
google-api-client (0.13.6)
addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.5)
httpclient (>= 2.8.1, < 3.0)
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
googleauth (0.6.2)
faraday (~> 0.12)
jwt (>= 1.4, < 3.0)
logging (~> 2.0)
memoist (~> 0.12)
multi_json (~> 1.11)
os (~> 0.9)
signet (~> 0.7)
highline (1.7.10)
http-cookie (1.0.3)
domain_name (~> 0.5)
httpclient (2.8.3)
i18n (0.9.5)
concurrent-ruby (~> 1.0)
json (2.1.0)
jwt (2.1.0)
little-plugger (1.1.4)
logging (2.2.2)
little-plugger (~> 1.1)
multi_json (~> 1.10)
memoist (0.16.0)
mime-types (3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
mini_magick (4.5.1)
minitest (5.11.3)
molinillo (0.6.5)
multi_json (1.13.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
nanaimo (0.2.5)
nap (1.1.0)
naturally (2.1.0)
netrc (0.11.0)
os (0.9.6)
plist (3.4.0)
public_suffix (2.0.5)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
uber (< 0.2.0)
retriable (3.1.1)
rouge (2.0.7)
ruby-macho (1.1.0)
rubyzip (1.2.1)
security (0.1.3)
signet (0.8.1)
addressable (~> 2.3)
faraday (~> 0.9)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.4)
CFPropertyList
naturally
slack-notifier (2.3.2)
terminal-notifier (1.8.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
thread_safe (0.3.6)
tty-cursor (0.5.0)
tty-screen (0.6.4)
tty-spinner (0.8.0)
tty-cursor (>= 0.5.0)
tzinfo (1.2.5)
thread_safe (~> 0.1)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.5)
unicode-display_width (1.3.2)
word_wrap (1.0.0)
xcodeproj (1.5.7)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.2)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.2.4)
xcpretty (0.2.8)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.0)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
ruby
DEPENDENCIES
cocoapods (= 1.5.0)
fastlane
BUNDLED WITH
1.16.1

View File

@@ -1,16 +1,20 @@
# CodableFirebase
Use [Codable](https://developer.apple.com/documentation/swift/codable) with [Firebase](https://firebase.google.com)
[![CocoaPods](https://img.shields.io/cocoapods/p/CodableFirebase.svg)](https://github.com/alickbass/CodableFirebase)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![Build Status](https://travis-ci.org/alickbass/CodableFirebase.svg?branch=master)](https://travis-ci.org/alickbass/CodableFirebase)
[![codecov](https://codecov.io/gh/alickbass/CodableFirebase/branch/master/graph/badge.svg)](https://codecov.io/gh/alickbass/CodableFirebase)
## Overview
This library helps you to use your custom type that conform to `Codable` protocol with Firebase. Here's an example of model:
This library helps you to use your custom types that conform to `Codable` protocol with Firebase. Here's an example of a custom model:
```swift
struct Model: Codable {
enum MyEnum: Int, Codable {
case one, two, three
}
let stringExample: String
let booleanExample: Bool
let numberExample: Double
@@ -18,9 +22,40 @@ struct Model: Codable {
let arrayExample: [String]
let nullExample: Int?
let objectExample: [String: String]
let myEnum: MyEnum
}
```
### 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
@@ -50,3 +85,42 @@ Firestore.firestore().collection("data").document("one").getDocument { (document
}
}
```
### How to use `GeoPoint`, `DocumentRefence`, `FieldValue` in Firestore
In order to use these 2 types with `Firestore`, you need to add the following code somewhere in your app:
```swift
extension DocumentReference: DocumentReferenceType {}
extension GeoPoint: GeoPointType {}
extension FieldValue: FieldValueType {}
```
and now they become `Codable` and can be used properly with `FirestoreEncoder` and `FirestoreDecoder`.
***PLEASE NOTE*** that as `FieldValue` is only used to [`setData()` and `updateData()`](https://firebase.google.com/docs/reference/swift/firebasefirestore/api/reference/Classes/FieldValue), it only adopts the `Encodable` protocol.
## Integration
### CocoaPods (iOS 9+)
You can use CocoaPods to install CodableFirebase by adding it to your Podfile:
```swift
platform :ios, '9.0'
use_frameworks!
target 'MyApp' do
pod 'CodableFirebase'
end
```
Note that this requires CocoaPods version 36, and your iOS deployment target to be at least 9.0:
### Carthage (iOS 9+)
You can use Carthage to install CodableFirebase by adding it to your Cartfile:
```swift
github "alickbass/CodableFirebase"
```

34
fastlane/Fastfile Normal file
View File

@@ -0,0 +1,34 @@
default_platform(:ios)
platform :ios do
desc "Release a new version with a patch bump_type"
lane :patch do
release("patch")
end
desc "Release a new version with a minor bump_type"
lane :minor do
release("minor")
end
desc "Release a new version with a major bump_type"
lane :major do
release("major")
end
def release(type)
pod_lib_lint
podspec_name = "CodableFirebase.podspec"
version = version_bump_podspec(path: podspec_name,
bump_type: type)
increment_version_number(version_number: version)
#cocoapods
git_add
git_commit(path: ".",
message: "#{version} release")
add_git_tag(tag: "#{version}")
push_to_git_remote
pod_push
end
end

39
fastlane/README.md Normal file
View File

@@ -0,0 +1,39 @@
fastlane documentation
================
# Installation
Make sure you have the latest version of the Xcode command line tools installed:
```
xcode-select --install
```
Install _fastlane_ using
```
[sudo] gem install fastlane -NV
```
or alternatively using `brew cask install fastlane`
# Available Actions
## iOS
### ios patch
```
fastlane ios patch
```
Release a new version with a patch bump_type
### ios minor
```
fastlane ios minor
```
Release a new version with a minor bump_type
### ios major
```
fastlane ios major
```
Release a new version with a major bump_type
----
This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run.
More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).