Merge pull request #11 from alickbass/firebase-database

Firebase database
This commit is contained in:
Oleksii Dykan
2017-12-27 20:55:49 +01:00
committed by GitHub
9 changed files with 1220 additions and 582 deletions

View File

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

View File

@@ -14,6 +14,10 @@
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 */
@@ -37,6 +41,10 @@
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 */
@@ -82,6 +90,8 @@
CE7DD36A1F9CFA81000225C5 /* CodableFirebase.h */,
CE7DD3821F9D04AE000225C5 /* FirestoreDecoder.swift */,
CE7DD3811F9D04AE000225C5 /* FirestoreEncoder.swift */,
CEFDBF891FF3E24200745EBE /* FirebaseDecoder.swift */,
CEFDBF8B1FF3E3CB00745EBE /* FirebaseEncoder.swift */,
CEFDBF851FF3B56200745EBE /* Decoder.swift */,
CEFDBF811FF3B35B00745EBE /* Encoder.swift */,
CE7DD36B1F9CFA81000225C5 /* Info.plist */,
@@ -92,7 +102,9 @@
CE7DD3741F9CFA81000225C5 /* CodableFirebaseTests */ = {
isa = PBXGroup;
children = (
CEFDBF8D1FF3E63700745EBE /* CodaleTests.swift */,
CE7DD3851F9DE4F7000225C5 /* TestCodableFirestore.swift */,
CEFDBF8F1FF3EB2900745EBE /* TestCodableFirebase.swift */,
CE7DD3771F9CFA81000225C5 /* Info.plist */,
);
path = CodableFirebaseTests;
@@ -210,9 +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;
};
@@ -221,6 +235,8 @@
buildActionMask = 2147483647;
files = (
CE7DD3861F9DE4F7000225C5 /* TestCodableFirestore.swift in Sources */,
CEFDBF8E1FF3E63700745EBE /* CodaleTests.swift in Sources */,
CEFDBF901FF3EB2900745EBE /* TestCodableFirebase.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -9,44 +9,10 @@
import Foundation
class _FirebaseDecoder : Decoder {
/// The strategy to use for decoding `Date` values.
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.
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)
}
/// Options set on the top-level encoder to pass down the decoding hierarchy.
struct _Options {
let dateDecodingStrategy: DateDecodingStrategy?
let dataDecodingStrategy: DataDecodingStrategy?
let dateDecodingStrategy: FirebaseDecoder.DateDecodingStrategy?
let dataDecodingStrategy: FirebaseDecoder.DataDecodingStrategy?
let userInfo: [CodingUserInfoKey : Any]
}

View File

@@ -9,49 +9,10 @@
import Foundation
class _FirebaseEncoder : Encoder {
/// 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)
}
/// Options set on the top-level encoder to pass down the encoding hierarchy.
struct _Options {
let dateEncodingStrategy: DateEncodingStrategy?
let dataEncodingStrategy: DataEncodingStrategy?
let dateEncodingStrategy: FirebaseEncoder.DateEncodingStrategy?
let dataEncodingStrategy: FirebaseEncoder.DataEncodingStrategy?
let userInfo: [CodingUserInfoKey : Any]
}

View File

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

View File

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

View File

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

View File

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

View File

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