add deferred to timestamp type, other types WIP

This commit is contained in:
Zitao Xiong
2018-11-03 17:01:52 +08:00
parent 81007b2998
commit 2f2d50a065
7 changed files with 95 additions and 14 deletions

View File

@@ -13,6 +13,8 @@ public enum DateDecodingStrategy {
/// Defer to `Date` for decoding. This is the default strategy.
case deferredToDate
case deferredToTimestamp
/// Decode the `Date` as a UNIX timestamp from a JSON number.
case secondsSince1970
@@ -43,13 +45,17 @@ public enum DataDecodingStrategy {
}
public enum FirestoreTypeDecodingStrategy {
case byProtocol
case deferredToPtotocol
case custom((_ value: Any) throws -> Any)
}
extension CodingUserInfoKey {
public static let dateDecodingStrategy: CodingUserInfoKey = CodingUserInfoKey(rawValue: "dateDecodingStrategy")!
public static let dataDecodingStrategy: CodingUserInfoKey = CodingUserInfoKey(rawValue: "dataDecodingStrategy")!
public static let firestoreTypeDecodingStrategy: CodingUserInfoKey = CodingUserInfoKey(rawValue: "firestoreTypeDecodingStrategy")!
}
extension Dictionary where Key == CodingUserInfoKey, Value == Any {
@@ -60,4 +66,12 @@ extension Dictionary where Key == CodingUserInfoKey, Value == Any {
var dataDecodingStrategy: DataDecodingStrategy? {
return self[.dataDecodingStrategy] as? DataDecodingStrategy
}
var firestoreTypeDecodingStrategy: FirestoreTypeDecodingStrategy {
if let strategy = self[.firestoreTypeDecodingStrategy] as? FirestoreTypeDecodingStrategy {
return strategy
}
return FirestoreTypeDecodingStrategy.deferredToPtotocol
}
}

View File

@@ -1104,6 +1104,9 @@ extension _FirebaseDecoder {
}
switch options {
case .deferredToTimestamp:
let timestamp = value as! TimestampType
return timestamp.dateValue()
case .deferredToDate:
self.storage.push(container: value)
let date = try Date(from: self)
@@ -1219,7 +1222,13 @@ extension _FirebaseDecoder {
decoded = decimal as! T
}
else if userInfo.skipFirestoreTypes && (T.self is FirestoreDecodable.Type) {
decoded = value as! T
let strategy = userInfo.firestoreTypeDecodingStrategy
switch strategy {
case .deferredToPtotocol:
decoded = value as! T
case .custom(let decodeFunc):
decoded = try decodeFunc(value) as! T
}
}
else {
self.storage.push(container: value)

View File

@@ -13,6 +13,8 @@ public enum DateEncodingStrategy {
/// Defer to `Date` for choosing an encoding. This is the default strategy.
case deferredToDate
case deferredToTimestamp((Date) -> TimestampType)
/// Encode the `Date` as a UNIX timestamp (as a JSON number).
case secondsSince1970
@@ -46,11 +48,19 @@ public enum DataEncodingStrategy {
case custom((Data, Encoder) throws -> Void)
}
public enum FirestoreTypeEncodingStrategy {
case deferredToPtotocol
case custom((_ value: Any) throws -> Any)
}
extension CodingUserInfoKey {
public static let dateEncodingStrategy: CodingUserInfoKey = CodingUserInfoKey(rawValue: "dateEncodingStrategy")!
public static let dataEncodingStrategy: CodingUserInfoKey = CodingUserInfoKey(rawValue: "dataEncodingStrategy")!
public static let skipFirestoreTypes: CodingUserInfoKey = CodingUserInfoKey(rawValue: "skipFirestoreTypes")!
public static let firestoreTypeEncodingStrategy: CodingUserInfoKey = CodingUserInfoKey(rawValue: "firestoreTypeEncodingStrategy")!
}
extension Dictionary where Key == CodingUserInfoKey, Value == Any {
@@ -68,4 +78,12 @@ extension Dictionary where Key == CodingUserInfoKey, Value == Any {
}
return false
}
var firestoreTypeEncodingStrategy: FirestoreTypeEncodingStrategy {
if let strategy = self[.firestoreTypeEncodingStrategy] as? FirestoreTypeEncodingStrategy {
return strategy
}
return FirestoreTypeEncodingStrategy.deferredToPtotocol
}
}

View File

@@ -306,6 +306,8 @@ extension _FirebaseEncoder {
guard let options = userInfo.dateEncodingStrategy else { return date as NSDate }
switch options {
case .deferredToTimestamp(let converter):
return converter(date) as! NSObject
case .deferredToDate:
// Must be called with a surrounding with(pushedKey:) call.
try date.encode(to: self)
@@ -377,10 +379,18 @@ extension _FirebaseEncoder {
} else if T.self == Decimal.self || T.self == NSDecimalNumber.self {
return (value as! NSDecimalNumber)
} else if userInfo.skipFirestoreTypes && (value is FirestoreEncodable) {
guard let value = value as? NSObject else {
let target: Any
switch userInfo.firestoreTypeDecodingStrategy {
case .deferredToPtotocol:
target = value
case .custom(let encodeFunc):
target = try encodeFunc(value)
}
guard let result = target as? NSObject else {
throw DocumentReferenceError.typeIsNotNSObject
}
return value
return result
}
// The value should request a container from the _FirebaseEncoder.

View File

@@ -26,9 +26,11 @@ public protocol TimestampType: FirestoreDecodable, FirestoreEncodable {
}
open class FirestoreDecoder {
public init() {}
public init(userInfo: [CodingUserInfoKey: Any] = [.skipFirestoreTypes: true]) {
self.userInfo = userInfo
}
open var userInfo: [CodingUserInfoKey : Any] = [.skipFirestoreTypes: true]
public let userInfo: [CodingUserInfoKey: Any]
open func decode<T : Decodable>(_ type: T.Type, from container: [String: Any]) throws -> T {
let decoder = _FirebaseDecoder(referencing: container,

View File

@@ -9,10 +9,12 @@
import Foundation
open class FirestoreEncoder {
public init() {}
open var userInfo: [CodingUserInfoKey : Any] = [.skipFirestoreTypes: true]
public init(userInfo: [CodingUserInfoKey: Any] = [.skipFirestoreTypes: true]) {
self.userInfo = userInfo
}
public let userInfo: [CodingUserInfoKey: Any]
open func encode<Value : Encodable>(_ value: Value) throws -> [String: Any] {
let topLevel = try encodeToTopLevelContainer(value)
switch topLevel {

View File

@@ -128,14 +128,36 @@ class TestCodableFirestore: XCTestCase {
XCTAssertEqual((try? FirestoreEncoder().encode(val)) as NSDictionary?, ["value": val.value])
XCTAssertEqual(try? FirestoreDecoder().decode(TopLevelWrapper<DocumentReference>.self, from: ["value": val.value]), val)
}
func testEncodingTimestamp() {
let timestamp = Timestamp(date: Date())
let wrapper = TopLevelWrapper(timestamp)
XCTAssertEqual((try? FirestoreEncoder().encode(wrapper)) as NSDictionary?, ["value": timestamp])
XCTAssertEqual(try? FirestoreDecoder().decode(TopLevelWrapper<Timestamp>.self, from: ["value": timestamp]), wrapper)
}
func testCustomEncodingTimestamp() {
let date = Date()
let timestamp = Timestamp(date: date)
// encode date to Timestamp
let encodeWrapper = TopLevelWrapper(date)
let encodeResult = (try? FirestoreEncoder(userInfo: [CodingUserInfoKey.dateEncodingStrategy: DateEncodingStrategy.deferredToTimestamp({ date in
return Timestamp(date: date)
})])
.encode(encodeWrapper)) as NSDictionary?
XCTAssertEqual(encodeResult, ["value": timestamp])
// decode timestamp to date
let decodeWrapper = TopLevelWrapper(timestamp)
let decoder = FirestoreDecoder(userInfo: [
CodingUserInfoKey.dateDecodingStrategy: DateDecodingStrategy.deferredToTimestamp
])
let decodeResult = (try? decoder.decode(TopLevelWrapper<Timestamp>.self, from: ["value": timestamp]))
XCTAssertEqual(decodeResult, decodeWrapper)
}
private func _testEncodeFailure<T : Encodable>(of value: T) {
do {
let _ = try FirestoreEncoder().encode(value)
@@ -214,17 +236,21 @@ fileprivate class DocumentReference: NSObject, DocumentReferenceType {}
// MARK: - Timestamp
fileprivate class Timestamp: NSObject, TimestampType {
let date: Date
required init(date: Date) {
self.date = date
}
func dateValue() -> Date {
return date
}
override func isEqual(_ object: Any?) -> Bool {
guard let other = object.flatMap({ $0 as? Timestamp }) else { return false }
return date == other.date
}
}
extension Date: FirestoreEncodable {
}