Skip to content

Commit b45bd79

Browse files
wu-huiwilhuff
authored andcommitted
Add @documentid equivalent to Swift Codable (#3762)
* Commit with failed optional test case. * Integration Test and cleanup * swiftformat * better doc formatting * addressing comments * rename to SelfDocumentID * Addressing Comments * fix comment
1 parent d709b9f commit b45bd79

File tree

4 files changed

+86
-6
lines changed

4 files changed

+86
-6
lines changed

Firestore/Swift/Source/Codable/third_party/FirestoreDecoder.swift

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import Foundation
1818

1919
extension Firestore {
2020
public struct Decoder {
21+
fileprivate static let documentRefUserInfoKey = CodingUserInfoKey(rawValue: "DocumentRefUserInfoKey")
22+
2123
public init() {}
2224
/// Returns an instance of specified type from a Firestore document.
2325
///
@@ -31,9 +33,17 @@ extension Firestore {
3133
/// - Parameters:
3234
/// - A type to decode a document to.
3335
/// - container: A Map keyed of String representing a Firestore document.
36+
/// - document: A reference to the Firestore Document that is being
37+
/// decoded.
3438
/// - Returns: An instance of specified type by the first parameter.
35-
public func decode<T: Decodable>(_: T.Type, from container: [String: Any]) throws -> T {
39+
public func decode<T: Decodable>(_: T.Type,
40+
from container: [String: Any],
41+
in document: DocumentReference? = nil) throws -> T {
3642
let decoder = _FirestoreDecoder(referencing: container)
43+
if let doc = document {
44+
decoder.userInfo[Firestore.Decoder.documentRefUserInfoKey!] = doc
45+
}
46+
3747
guard let value = try decoder.unbox(container, as: T.self) else {
3848
throw DecodingError.valueNotFound(
3949
T.self,
@@ -323,6 +333,24 @@ private struct _FirestoreKeyedDecodingContainer<K: CodingKey>: KeyedDecodingCont
323333
}
324334

325335
public func decode<T: Decodable>(_: T.Type, forKey key: Key) throws -> T {
336+
if T.self == SelfDocumentID.self {
337+
let docRef = decoder.userInfo[
338+
Firestore.Decoder.documentRefUserInfoKey!
339+
] as! DocumentReference?
340+
341+
if contains(key) {
342+
let docPath = (docRef != nil) ? docRef!.path : "nil"
343+
var codingPathCopy = codingPath.map { key in key.stringValue }
344+
codingPathCopy.append(key.stringValue)
345+
346+
throw FirestoreDecodingError.fieldNameConfict("Field name " +
347+
"\(codingPathCopy) was found from document \"\(docPath)\", " +
348+
"cannot assign the document reference to this field.")
349+
}
350+
351+
return SelfDocumentID(from: docRef) as! T
352+
}
353+
326354
let entry = try require(key: key)
327355

328356
decoder.codingPath.append(key)
@@ -978,6 +1006,10 @@ extension _FirestoreDecoder {
9781006

9791007
func unbox(_ value: Any, as type: Date.Type) throws -> Date? {
9801008
guard !(value is NSNull) else { return nil }
1009+
// Firestore returns all dates as Timestamp, converting it to Date so it can be used in custom objects.
1010+
if let timestamp = value as? Timestamp {
1011+
return timestamp.dateValue()
1012+
}
9811013
guard let date = value as? Date else {
9821014
throw DecodingError._typeMismatch(at: codingPath, expectation: type, reality: value)
9831015
}

Firestore/Swift/Source/Codable/third_party/FirestoreEncoder.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,11 @@ extension Firestore {
3636
/// - Returns: A Map keyed by String representing a document Firestore
3737
/// API can work with.
3838
public func encode<T: Encodable>(_ value: T) throws -> [String: Any] {
39-
// DocumentReference and FieldValue cannot be encoded directly.
40-
guard T.self != DocumentReference.self, T.self != FieldValue.self else {
39+
// SelfDocumentID, DocumentReference and FieldValue cannot be
40+
// encoded directly.
41+
guard T.self != SelfDocumentID.self,
42+
T.self != DocumentReference.self,
43+
T.self != FieldValue.self else {
4144
throw FirestoreEncodingError.encodingIsNotSupported
4245
}
4346
guard let topLevel = try _FirestoreEncoder().box_(value) else {
@@ -212,6 +215,11 @@ private struct _FirestoreKeyedEncodingContainer<K: CodingKey>: KeyedEncodingCont
212215
public mutating func encode(_ value: Double, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) }
213216

214217
public mutating func encode<T: Encodable>(_ value: T, forKey key: Key) throws {
218+
// `SelfDocumentID` is ignored during encoding.
219+
if T.self == SelfDocumentID.self {
220+
return
221+
}
222+
215223
encoder.codingPath.append(key)
216224
defer {
217225
encoder.codingPath.removeLast()

Firestore/third_party/FirestoreEncoder/FirestoreDecoder.swift

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import Foundation
1818

1919
extension Firestore {
2020
public struct Decoder {
21+
fileprivate static let documentRefUserInfoKey = CodingUserInfoKey(rawValue: "DocumentRefUserInfoKey")
22+
2123
public init() {}
2224
/// Returns an instance of specified type from a Firestore document.
2325
///
@@ -31,9 +33,17 @@ extension Firestore {
3133
/// - Parameters:
3234
/// - A type to decode a document to.
3335
/// - container: A Map keyed of String representing a Firestore document.
36+
/// - document: A reference to the Firestore Document that is being
37+
/// decoded.
3438
/// - Returns: An instance of specified type by the first parameter.
35-
public func decode<T: Decodable>(_: T.Type, from container: [String: Any]) throws -> T {
39+
public func decode<T: Decodable>(_: T.Type,
40+
from container: [String: Any],
41+
in document: DocumentReference? = nil) throws -> T {
3642
let decoder = _FirestoreDecoder(referencing: container)
43+
if let doc = document {
44+
decoder.userInfo[Firestore.Decoder.documentRefUserInfoKey!] = doc
45+
}
46+
3747
guard let value = try decoder.unbox(container, as: T.self) else {
3848
throw DecodingError.valueNotFound(
3949
T.self,
@@ -323,6 +333,24 @@ private struct _FirestoreKeyedDecodingContainer<K: CodingKey>: KeyedDecodingCont
323333
}
324334

325335
public func decode<T: Decodable>(_: T.Type, forKey key: Key) throws -> T {
336+
if T.self == SelfDocumentID.self {
337+
let docRef = decoder.userInfo[
338+
Firestore.Decoder.documentRefUserInfoKey!
339+
] as! DocumentReference?
340+
341+
if contains(key) {
342+
let docPath = (docRef != nil) ? docRef!.path : "nil"
343+
var codingPathCopy = codingPath.map { key in key.stringValue }
344+
codingPathCopy.append(key.stringValue)
345+
346+
throw FirestoreDecodingError.fieldNameConfict("Field name " +
347+
"\(codingPathCopy) was found from document \"\(docPath)\", " +
348+
"cannot assign the document reference to this field.")
349+
}
350+
351+
return SelfDocumentID(from: docRef) as! T
352+
}
353+
326354
let entry = try require(key: key)
327355

328356
decoder.codingPath.append(key)
@@ -978,6 +1006,10 @@ extension _FirestoreDecoder {
9781006

9791007
func unbox(_ value: Any, as type: Date.Type) throws -> Date? {
9801008
guard !(value is NSNull) else { return nil }
1009+
// Firestore returns all dates as Timestamp, converting it to Date so it can be used in custom objects.
1010+
if let timestamp = value as? Timestamp {
1011+
return timestamp.dateValue()
1012+
}
9811013
guard let date = value as? Date else {
9821014
throw DecodingError._typeMismatch(at: codingPath, expectation: type, reality: value)
9831015
}

Firestore/third_party/FirestoreEncoder/FirestoreEncoder.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,11 @@ extension Firestore {
3636
/// - Returns: A Map keyed by String representing a document Firestore
3737
/// API can work with.
3838
public func encode<T: Encodable>(_ value: T) throws -> [String: Any] {
39-
// DocumentReference and FieldValue cannot be encoded directly.
40-
guard T.self != DocumentReference.self, T.self != FieldValue.self else {
39+
// SelfDocumentID, DocumentReference and FieldValue cannot be
40+
// encoded directly.
41+
guard T.self != SelfDocumentID.self,
42+
T.self != DocumentReference.self,
43+
T.self != FieldValue.self else {
4144
throw FirestoreEncodingError.encodingIsNotSupported
4245
}
4346
guard let topLevel = try _FirestoreEncoder().box_(value) else {
@@ -212,6 +215,11 @@ private struct _FirestoreKeyedEncodingContainer<K: CodingKey>: KeyedEncodingCont
212215
public mutating func encode(_ value: Double, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) }
213216

214217
public mutating func encode<T: Encodable>(_ value: T, forKey key: Key) throws {
218+
// `SelfDocumentID` is ignored during encoding.
219+
if T.self == SelfDocumentID.self {
220+
return
221+
}
222+
215223
encoder.codingPath.append(key)
216224
defer {
217225
encoder.codingPath.removeLast()

0 commit comments

Comments
 (0)