Skip to content

Commit 65f6cfa

Browse files
Make Firestore use FirebaseDataEncoder and FirebaseDataDecoder (re-implementation of #8858) (#9465)
1 parent b695d99 commit 65f6cfa

20 files changed

+271
-2100
lines changed

CoreOnly/Tests/FirebasePodTest/Podfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ target 'FirebasePodTest' do
3737
pod 'FirebaseCoreDiagnostics', :path => '../../../'
3838
pod 'FirebaseCoreInternal', :path => '../../../'
3939
pod 'FirebaseCoreExtension', :path => '../../../'
40+
pod 'FirebaseSharedSwift', :path => '../../../'
4041

4142
pod 'FirebaseAnalytics' # Analytics is not open source
4243
end

FirebaseFirestoreSwift.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling,
3232
s.requires_arc = true
3333
s.source_files = [
3434
'Firestore/Swift/Source/**/*.swift',
35-
'Firestore/third_party/FirestoreEncoder/*.swift',
3635
]
3736

3837
s.dependency 'FirebaseFirestore', '~> 9.0'
38+
s.dependency 'FirebaseSharedSwift', '~> 9.0'
3939
end

FirebaseSharedSwift/Sources/third_party/FirebaseDataEncoder/FirebaseDataEncoder.swift

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@
1414

1515
import Foundation
1616

17+
public protocol StructureCodingPassthroughTypeResolver {
18+
static func isPassthroughType<T>(_ t: T) -> Bool
19+
}
20+
21+
private struct NoPassthroughTypes: StructureCodingPassthroughTypeResolver {
22+
static func isPassthroughType<T>(_ t: T) -> Bool {
23+
return false
24+
}
25+
}
26+
27+
public protocol StructureCodingUncodedUnkeyed {}
28+
1729
extension DecodingError {
1830
/// Returns a `.typeMismatch` error describing the expected type.
1931
///
@@ -233,6 +245,9 @@ public class FirebaseDataEncoder {
233245
/// The strategy to use for encoding keys. Defaults to `.useDefaultKeys`.
234246
open var keyEncodingStrategy: KeyEncodingStrategy = .useDefaultKeys
235247

248+
/// A type that can resolve which types to 'pass through' - or leave alone while encoding. Defaults to not passing any types through.
249+
open var passthroughTypeResolver: StructureCodingPassthroughTypeResolver.Type = NoPassthroughTypes.self
250+
236251
/// Contextual user-provided information for use during encoding.
237252
open var userInfo: [CodingUserInfoKey : Any] = [:]
238253

@@ -242,6 +257,7 @@ public class FirebaseDataEncoder {
242257
let dataEncodingStrategy: DataEncodingStrategy
243258
let nonConformingFloatEncodingStrategy: NonConformingFloatEncodingStrategy
244259
let keyEncodingStrategy: KeyEncodingStrategy
260+
let passthroughTypeResolver: StructureCodingPassthroughTypeResolver.Type
245261
let userInfo: [CodingUserInfoKey : Any]
246262
}
247263

@@ -251,6 +267,7 @@ public class FirebaseDataEncoder {
251267
dataEncodingStrategy: dataEncodingStrategy,
252268
nonConformingFloatEncodingStrategy: nonConformingFloatEncodingStrategy,
253269
keyEncodingStrategy: keyEncodingStrategy,
270+
passthroughTypeResolver: passthroughTypeResolver,
254271
userInfo: userInfo)
255272
}
256273

@@ -501,6 +518,7 @@ fileprivate struct _JSONKeyedEncodingContainer<K : CodingKey> : KeyedEncodingCon
501518
}
502519

503520
public mutating func encode<T : Encodable>(_ value: T, forKey key: Key) throws {
521+
if T.self is StructureCodingUncodedUnkeyed.Type { return }
504522
self.encoder.codingPath.append(key)
505523
defer { self.encoder.codingPath.removeLast() }
506524
self.container[_converted(key).stringValue] = try self.encoder.box(value)
@@ -928,6 +946,8 @@ extension __JSONEncoder {
928946
return (value as! NSDecimalNumber)
929947
} else if value is _JSONStringDictionaryEncodableMarker {
930948
return try self.box(value as! [String : Encodable])
949+
} else if let object = value as? NSObject, self.options.passthroughTypeResolver.isPassthroughType(value) {
950+
return object
931951
}
932952

933953
// The value should request a container from the __JSONEncoder.
@@ -1166,6 +1186,9 @@ public class FirebaseDataDecoder {
11661186
/// The strategy to use for decoding keys. Defaults to `.useDefaultKeys`.
11671187
open var keyDecodingStrategy: KeyDecodingStrategy = .useDefaultKeys
11681188

1189+
/// A type that can resolve which types to 'pass through' - or leave alone while decoding. Defaults to not passing any types through.
1190+
open var passthroughTypeResolver: StructureCodingPassthroughTypeResolver.Type = NoPassthroughTypes.self
1191+
11691192
/// Contextual user-provided information for use during decoding.
11701193
open var userInfo: [CodingUserInfoKey : Any] = [:]
11711194

@@ -1175,6 +1198,7 @@ public class FirebaseDataDecoder {
11751198
let dataDecodingStrategy: DataDecodingStrategy
11761199
let nonConformingFloatDecodingStrategy: NonConformingFloatDecodingStrategy
11771200
let keyDecodingStrategy: KeyDecodingStrategy
1201+
let passthroughTypeResolver: StructureCodingPassthroughTypeResolver.Type
11781202
let userInfo: [CodingUserInfoKey : Any]
11791203
}
11801204

@@ -1184,6 +1208,7 @@ public class FirebaseDataDecoder {
11841208
dataDecodingStrategy: dataDecodingStrategy,
11851209
nonConformingFloatDecodingStrategy: nonConformingFloatDecodingStrategy,
11861210
keyDecodingStrategy: keyDecodingStrategy,
1211+
passthroughTypeResolver: passthroughTypeResolver,
11871212
userInfo: userInfo)
11881213
}
11891214

@@ -1617,6 +1642,11 @@ fileprivate struct _JSONKeyedDecodingContainer<K : CodingKey> : KeyedDecodingCon
16171642
}
16181643

16191644
public func decode<T : Decodable>(_ type: T.Type, forKey key: Key) throws -> T {
1645+
if type is StructureCodingUncodedUnkeyed.Type {
1646+
// Note: not pushing and popping key to codingPath since the key is
1647+
// not part of the decoded structure.
1648+
return try T.init(from: self.decoder)
1649+
}
16201650
guard let entry = self.container[key.stringValue] else {
16211651
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
16221652
}
@@ -2505,12 +2535,12 @@ extension __JSONDecoder {
25052535
return try unbox_(value, as: type) as? T
25062536
}
25072537

2508-
fileprivate func unbox_(_ value: Any, as type: Decodable.Type) throws -> Any? {
2509-
if type == Date.self || type == NSDate.self {
2538+
fileprivate func unbox_(_ value: Any, as _type: Decodable.Type) throws -> Any? {
2539+
if _type == Date.self || _type == NSDate.self {
25102540
return try self.unbox(value, as: Date.self)
2511-
} else if type == Data.self || type == NSData.self {
2541+
} else if _type == Data.self || _type == NSData.self {
25122542
return try self.unbox(value, as: Data.self)
2513-
} else if type == URL.self || type == NSURL.self {
2543+
} else if _type == URL.self || _type == NSURL.self {
25142544
guard let urlString = try self.unbox(value, as: String.self) else {
25152545
return nil
25162546
}
@@ -2520,14 +2550,17 @@ extension __JSONDecoder {
25202550
debugDescription: "Invalid URL string."))
25212551
}
25222552
return url
2523-
} else if type == Decimal.self || type == NSDecimalNumber.self {
2553+
} else if _type == Decimal.self || _type == NSDecimalNumber.self {
25242554
return try self.unbox(value, as: Decimal.self)
2525-
} else if let stringKeyedDictType = type as? _JSONStringDictionaryDecodableMarker.Type {
2555+
} else if let stringKeyedDictType = _type as? _JSONStringDictionaryDecodableMarker.Type {
25262556
return try self.unbox(value, as: stringKeyedDictType)
25272557
} else {
25282558
self.storage.push(container: value)
25292559
defer { self.storage.popContainer() }
2530-
return try type.init(from: self)
2560+
if self.options.passthroughTypeResolver.isPassthroughType(value) && type(of: value) == _type {
2561+
return value
2562+
}
2563+
return try _type.init(from: self)
25312564
}
25322565
}
25332566
}

Firestore/Example/Podfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ if is_platform(:ios)
116116
target 'Firestore_IntegrationTests_iOS' do
117117
inherit! :search_paths
118118

119+
pod 'FirebaseSharedSwift', :path => '../../'
119120
pod 'FirebaseFirestoreSwift', :path => '../../'
120121
pod 'GoogleBenchmark', :podspec => 'GoogleBenchmark.podspec'
121122
pod 'GoogleTest', :podspec => 'GoogleTest.podspec'
@@ -152,6 +153,7 @@ if is_platform(:osx)
152153
target 'Firestore_IntegrationTests_macOS' do
153154
inherit! :search_paths
154155

156+
pod 'FirebaseSharedSwift', :path => '../../'
155157
pod 'FirebaseFirestoreSwift', :path => '../../'
156158
pod 'GoogleBenchmark', :podspec => 'GoogleBenchmark.podspec'
157159
pod 'GoogleTest', :podspec => 'GoogleTest.podspec'
@@ -181,6 +183,7 @@ if is_platform(:tvos)
181183
target 'Firestore_IntegrationTests_tvOS' do
182184
inherit! :search_paths
183185

186+
pod 'FirebaseSharedSwift', :path => '../../'
184187
pod 'FirebaseFirestoreSwift', :path => '../../'
185188
pod 'GoogleBenchmark', :podspec => 'GoogleBenchmark.podspec'
186189
pod 'GoogleTest', :podspec => 'GoogleTest.podspec'

Firestore/Swift/CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
# 10.0.0
2+
- [changed] `Firestore.Encoder` and `Firestore.Decoder` now wraps the shared `FirebaseDataEncoder` and `FirebaseDataDecoder` types which provides new customization options for encoding and decoding date to and from Firestore - similar to the options present on `JSONEncoder` and `JSONDecoder` from `Foundation`.
3+
- [added] `Firestore.Encoder.KeyEncodingStrategy`
4+
- [added] `Firestore.Encoder.DateEncodingStrategy`
5+
- [added] `Firestore.Encoder.DataEncodingStrategy`
6+
- [added] `Firestore.Encoder.NonConformingFloatEncodingStrategy`
7+
- [added] `Firestore.Decoder.KeyDecodingStrategy`
8+
- [added] `Firestore.Decoder.DateDecodingStrategy`
9+
- [added] `Firestore.Decoder.DataDecodingStrategy`
10+
- [added] `Firestore.Decoder.NonConformingFloatDecodingStrategy`
11+
112
# 9.0.0
213
- [added] **Breaking change:** `FirebaseFirestoreSwift` has exited beta and is
314
now generally available for use.

Firestore/Swift/Source/Codable/CodablePassThroughTypes.swift

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@
1515
*/
1616

1717
import Foundation
18+
import FirebaseSharedSwift
1819
import FirebaseFirestore
1920

20-
internal func isFirestorePassthroughType<T: Any>(_ value: T) -> Bool {
21-
return
22-
T.self == GeoPoint.self ||
23-
T.self == Timestamp.self ||
24-
T.self == FieldValue.self ||
25-
T.self == DocumentReference.self
21+
internal struct FirestorePassthroughTypes: StructureCodingPassthroughTypeResolver {
22+
static func isPassthroughType<T>(_ t: T) -> Bool {
23+
return
24+
t is GeoPoint ||
25+
t is Timestamp ||
26+
t is FieldValue ||
27+
t is DocumentReference
28+
}
2629
}

Firestore/Swift/Source/Codable/CollectionReference+WriteEncodable.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public extension CollectionReference {
3535
encoder: Firestore.Encoder = Firestore.Encoder(),
3636
completion: ((Error?) -> Void)? = nil) throws
3737
-> DocumentReference {
38-
return addDocument(data: try encoder.encode(value), completion: completion)
38+
let encoded = try encoder.encode(value)
39+
return addDocument(data: encoded, completion: completion)
3940
}
4041
}

Firestore/Swift/Source/Codable/DocumentID.swift

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
*/
1616

1717
import FirebaseFirestore
18+
import FirebaseSharedSwift
19+
20+
let documentRefUserInfoKey =
21+
CodingUserInfoKey(rawValue: "DocumentRefUserInfoKey")!
1822

1923
/// A type that can initialize itself from a Firestore `DocumentReference`,
2024
/// which makes it suitable for use with the `@DocumentID` property wrapper.
@@ -76,7 +80,7 @@ internal protocol DocumentIDProtocol {
7680
/// Firestore.Encoder leads to an error.
7781
@propertyWrapper
7882
public struct DocumentID<Value: DocumentIDWrappable & Codable>:
79-
DocumentIDProtocol, Codable {
83+
DocumentIDProtocol, Codable, StructureCodingUncodedUnkeyed {
8084
var value: Value?
8185

8286
public init(wrappedValue value: Value?) {
@@ -101,9 +105,12 @@ public struct DocumentID<Value: DocumentIDWrappable & Codable>:
101105
// MARK: - `Codable` implementation.
102106

103107
public init(from decoder: Decoder) throws {
104-
throw FirestoreDecodingError.decodingIsNotSupported(
105-
"DocumentID values can only be decoded with Firestore.Decoder"
106-
)
108+
guard let reference = decoder.userInfo[documentRefUserInfoKey] as? DocumentReference else {
109+
throw FirestoreDecodingError.decodingIsNotSupported(
110+
"Could not find DocumentReference for user info key: \(documentRefUserInfoKey)"
111+
)
112+
}
113+
try self.init(from: reference)
107114
}
108115

109116
public func encode(to encoder: Encoder) throws {

Firestore/Swift/Source/Codable/DocumentReference+WriteEncodable.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ public extension DocumentReference {
3434
func setData<T: Encodable>(from value: T,
3535
encoder: Firestore.Encoder = Firestore.Encoder(),
3636
completion: ((Error?) -> Void)? = nil) throws {
37-
setData(try encoder.encode(value), completion: completion)
37+
let encoded = try encoder.encode(value)
38+
setData(encoded, completion: completion)
3839
}
3940

4041
/// Encodes an instance of `Encodable` and overwrites the encoded data
@@ -57,7 +58,8 @@ public extension DocumentReference {
5758
merge: Bool,
5859
encoder: Firestore.Encoder = Firestore.Encoder(),
5960
completion: ((Error?) -> Void)? = nil) throws {
60-
setData(try encoder.encode(value), merge: merge, completion: completion)
61+
let encoded = try encoder.encode(value)
62+
setData(encoded, merge: merge, completion: completion)
6163
}
6264

6365
/// Encodes an instance of `Encodable` and writes the encoded data to the document referred
@@ -84,6 +86,7 @@ public extension DocumentReference {
8486
mergeFields: [Any],
8587
encoder: Firestore.Encoder = Firestore.Encoder(),
8688
completion: ((Error?) -> Void)? = nil) throws {
87-
setData(try encoder.encode(value), mergeFields: mergeFields, completion: completion)
89+
let encoded = try encoder.encode(value)
90+
setData(encoded, mergeFields: mergeFields, completion: completion)
8891
}
8992
}

0 commit comments

Comments
 (0)