Skip to content

Commit 8d92434

Browse files
authored
Add DocumentSnapshot.data(as:with:decoder:) (#6334)
This adds support for specifying alternative server timestamp behaviors when decoding snapshots. Fixes #6328.
1 parent feb5ce9 commit 8d92434

File tree

5 files changed

+66
-9
lines changed

5 files changed

+66
-9
lines changed

Firestore/Example/Tests/Util/XCTestCase+Await.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ typedef void (^FSTVoidErrorBlock)(NSError *_Nullable error);
4444
*/
4545
- (void)awaitExpectation:(XCTestExpectation *)expectation;
4646

47+
/**
48+
* Await specific expectations with a reasonable timeout. If the expectation fails, XCTFail the
49+
* test.
50+
*/
51+
- (void)awaitExpectations:(NSArray<XCTestExpectation *> *)expectations;
52+
4753
/**
4854
* Returns a reasonable timeout for testing against Firestore.
4955
*/

Firestore/Example/Tests/Util/XCTestCase+Await.mm

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ - (void)awaitExpectation:(XCTestExpectation *)expectation {
4040
[self waitForExpectations:@[ expectation ] timeout:kExpectationWaitSeconds];
4141
}
4242

43+
- (void)awaitExpectations:(NSArray<XCTestExpectation *> *)expectations {
44+
[self waitForExpectations:expectations timeout:kExpectationWaitSeconds];
45+
}
46+
4347
- (double)defaultExpectationWaitSeconds {
4448
return kExpectationWaitSeconds;
4549
}

Firestore/Swift/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# Unreleased
2+
- [feature] Added support for specifying `ServerTimestampBehavior` when
3+
decoding a `DocumentSnapshot`.
4+
15
# v0.4
26
- [feature] Added conditional conformance to the `Hashable` protocol for the
37
`@DocumentID`, `@ExplicitNull`, and `@ServerTimestamp` property wrappers.

Firestore/Swift/Source/Codable/DocumentSnapshot+ReadDecodable.swift

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019 Google
2+
* Copyright 2019 Google LLC
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,20 +21,24 @@ extension DocumentSnapshot {
2121
/// Retrieves all fields in a document and converts them to an instance of
2222
/// caller-specified type. Returns `nil` if the document does not exist.
2323
///
24+
/// By default, server-provided timestamps that have not yet been set to their
25+
/// final value will be returned as `NSNull`. Pass `serverTimestampBehavior`
26+
/// configure this behavior.
27+
///
2428
/// See `Firestore.Decoder` for more details about the decoding process.
2529
///
2630
/// - Parameters
2731
/// - type: The type to convert the document fields to.
32+
/// - serverTimestampBehavior: Configures how server timestamps that have
33+
/// not yet been set to their final value are returned from the snapshot.
2834
/// - decoder: The decoder to use to convert the document. `nil` to use
29-
/// default decoder.
35+
/// default decoder.
3036
public func data<T: Decodable>(as type: T.Type,
37+
with serverTimestampBehavior: ServerTimestampBehavior = .none,
3138
decoder: Firestore.Decoder? = nil) throws -> T? {
32-
var d = decoder
33-
if d == nil {
34-
d = Firestore.Decoder()
35-
}
36-
if let data = data() {
37-
return try d?.decode(T.self, from: data, in: reference)
39+
let d = decoder ?? Firestore.Decoder()
40+
if let data = data(with: serverTimestampBehavior) {
41+
return try d.decode(T.self, from: data, in: reference)
3842
}
3943
return nil
4044
}

Firestore/Swift/Tests/Integration/CodableIntegrationTests.swift

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019 Google
2+
* Copyright 2019 Google LLC
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -122,6 +122,45 @@ class CodableIntegrationTests: FSTIntegrationTestCase {
122122
}
123123
}
124124
}
125+
126+
func testServerTimestampBehavior() throws {
127+
struct Model: Codable, Equatable {
128+
var name: String
129+
@ServerTimestamp var ts: Timestamp? = nil
130+
}
131+
132+
disableNetwork()
133+
let docToWrite = documentRef()
134+
let now = Int64(Date().timeIntervalSince1970)
135+
let pastTimestamp = Timestamp(seconds: 807_940_800, nanoseconds: 0)
136+
137+
// Write a document with a current value to enable testing with .previous.
138+
let originalModel = Model(name: "name", ts: pastTimestamp)
139+
let completion1 = completionForExpectation(withName: "setData")
140+
try docToWrite.setData(from: originalModel, completion: completion1)
141+
142+
// Overwrite with a nil server timestamp so that ServerTimestampBehavior is testable.
143+
let newModel = Model(name: "name")
144+
let completion2 = completionForExpectation(withName: "setData")
145+
try docToWrite.setData(from: newModel, completion: completion2)
146+
147+
let snapshot = readDocument(forRef: docToWrite)
148+
var decoded = try snapshot.data(as: Model.self, with: .none)
149+
XCTAssertNil(decoded?.ts)
150+
151+
decoded = try snapshot.data(as: Model.self, with: .estimate)
152+
XCTAssertNotNil(decoded?.ts)
153+
XCTAssertNotNil(decoded?.ts?.seconds)
154+
XCTAssertGreaterThanOrEqual(decoded!.ts!.seconds, now)
155+
156+
decoded = try snapshot.data(as: Model.self, with: .previous)
157+
XCTAssertNotNil(decoded?.ts)
158+
XCTAssertNotNil(decoded?.ts?.seconds)
159+
XCTAssertEqual(decoded!.ts!.seconds, pastTimestamp.seconds)
160+
161+
enableNetwork()
162+
awaitExpectations()
163+
}
125164
#endif // swift(>=5.1)
126165

127166
@available(swift, deprecated: 5.1)

0 commit comments

Comments
 (0)