Skip to content

Commit 8866b22

Browse files
andrewheardncooke3
andauthored
Firestore: Make @documentid value setter private (#10167)
* Firestore: Make @documentid value setter private Updated the `DocumentID` Swift property wrapper's `value` property to be private(set) since it is ignored in the methods that write to Firestore. * Make initializer private * Fix formatting * Fix tests Made the setter internally accessible and imported FirebaseFirestoreSwift with @testable. * Add a changelog entry and formatting fixes * Documentation fixes * [skip ci] Add runtime warnings to `@DocumentID` setter and initializer (#10275) * [Firestore] Mark `@DocumentID` setter deprecated and add log statement in init * Fix typo in import * Update CHANGELOG * Review and remove deprecation * Review and add FIRLogWarningSwift * Add FirebaseCorExtension to Firestore's Podfile * [skip ci] Fix merged comment Co-authored-by: Nick Cooke <[email protected]> Co-authored-by: Nick Cooke <[email protected]>
1 parent faa4488 commit 8866b22

File tree

8 files changed

+116
-31
lines changed

8 files changed

+116
-31
lines changed

FirebaseCore/Extension/FIRLogger.h

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -118,19 +118,31 @@ extern void FIRLogDebug(FIRLoggerService service, NSString *messageCode, NSStrin
118118

119119
// TODO: Come up with a better logging scheme for Swift.
120120
/**
121-
* Logs a message to the Xcode console and the device log. If running from AppStore, will
121+
* Logs a debug message to the Xcode console and the device log. If running from AppStore, will
122122
* not log any messages with a level higher than FirebaseLoggerLevelNotice to avoid log spamming.
123123
* This function is intended to be used by Swift clients that do not support variadic parameters.
124-
* (required) log level (one of the FirebaseLoggerLevel enum values).
125-
* (required) service name of type FirebaseLoggerService.
126-
* (required) message code starting with "I-" which means iOS, followed by a capitalized
127-
* three-character service identifier and a six digit integer message ID that is unique
128-
* within the service.
129-
* An example of the message code is @"I-COR000001".
130-
* (required) message string.
124+
*
125+
* @param service The service name of type `FirebaseLoggerService`.
126+
* @param messageCode The mesage code. starting with "I-" which means iOS, followed by a capitalized
127+
* three-character service identifier and a six digit integer message ID that is unique within the
128+
* service. An example of the message code is @"I-COR000001".
129+
* @param message The message string.
131130
*/
132131
extern void FIRLogDebugSwift(FIRLoggerService service, NSString *messageCode, NSString *message);
133132

133+
/**
134+
* Logs a warning message to the Xcode console and the device log. If running from AppStore, will
135+
* not log any messages with a level higher than FirebaseLoggerLevelNotice to avoid log spamming.
136+
* This function is intended to be used by Swift clients that do not support variadic parameters.
137+
*
138+
* @param service The service name of type `FirebaseLoggerService`.
139+
* @param messageCode The mesage code. starting with "I-" which means iOS, followed by a capitalized
140+
* three-character service identifier and a six digit integer message ID that is unique within the
141+
* service. An example of the message code is @"I-COR000001".
142+
* @param message The message string.
143+
*/
144+
extern void FIRLogWarningSwift(FIRLoggerService service, NSString *messageCode, NSString *message);
145+
134146
#ifdef __cplusplus
135147
} // extern "C"
136148
#endif // __cplusplus

FirebaseCore/Sources/FIRLogger.m

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ void FIRLogDebugSwift(FIRLoggerService service, NSString *messageCode, NSString
162162
FIRLogDebug(service, messageCode, @"%@", message);
163163
}
164164

165+
void FIRLogWarningSwift(FIRLoggerService service, NSString *messageCode, NSString *message) {
166+
FIRLogWarning(service, messageCode, @"%@", message);
167+
}
168+
165169
#undef FIR_MAKE_LOGGER
166170

167171
#pragma mark - FIRLoggerWrapper

FirebaseFirestoreSwift.podspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling,
3434
'Firestore/Swift/Source/**/*.swift',
3535
]
3636

37+
s.dependency 'FirebaseCoreExtension', '~> 10.0'
3738
s.dependency 'FirebaseFirestore', '~> 10.0'
3839
s.dependency 'FirebaseSharedSwift', '~> 10.0'
3940

Firestore/Example/Podfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ def configure_local_pods()
6262
# to its podspec can still function. See Firestore-*-xcodebuild in
6363
# scripts/install_prereqs.sh for more details.
6464
pod 'FirebaseCore', :path => '../..'
65-
pod 'FirebaseCoreInternal',:path => '../..'
65+
pod 'FirebaseCoreInternal', :path => '../..'
66+
pod 'FirebaseCoreExtension',:path => '../..'
6667

6768
# Pull in local sources conditionally.
6869
maybe_local_pod 'FirebaseAuth'

Firestore/Swift/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
# 10.0.0
2+
- [changed] **Breaking Change:** Made the `@DocumentID` property wrapper value
3+
setter internal to clarify that the value is ignored during writes. (#9368)
4+
- [changed] Initializing a `@DocumentID` property wrapper with a non-nil value
5+
or using the `@DocumentID` property wrapper value setter will log a warning.
6+
This is because the set value will be ignored. (#9368)
27
- [changed] `Firestore.Encoder` and `Firestore.Decoder` now wraps the shared
38
`FirebaseDataEncoder` and `FirebaseDataDecoder` types, which provides new
49
customization options for encoding and decoding data to and from Firestore

Firestore/Swift/Source/Codable/DocumentID.swift

Lines changed: 78 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import FirebaseFirestore
1818
import FirebaseSharedSwift
19+
@_implementationOnly import FirebaseCoreExtension
1920

2021
extension CodingUserInfoKey {
2122
static let documentRefUserInfoKey =
@@ -65,57 +66,114 @@ internal protocol DocumentIDProtocol {
6566
init(from documentReference: DocumentReference?) throws
6667
}
6768

68-
/// A value that is populated in Codable objects with the `DocumentReference`
69-
/// of the current document by the Firestore.Decoder when a document is read.
69+
/// A property wrapper type that marks a `DocumentReference?` or `String?` field to
70+
/// be populated with a document identifier when it is read.
7071
///
71-
/// If the field name used for this type conflicts with a read document field,
72-
/// an error is thrown. For example, if a custom object has a field `firstName`
73-
/// annotated with `@DocumentID`, and there is a property from the document
74-
/// named `firstName` as well, an error is thrown when you try to read the
75-
/// document.
72+
/// Apply the `@DocumentID` annotation to a `DocumentReference?` or `String?`
73+
/// property in a `Codable` object to have it populated with the document
74+
/// identifier when it is read and decoded from Firestore.
7675
///
77-
/// When writing a Codable object containing an `@DocumentID` annotated field,
78-
/// its value is ignored. This allows you to read a document from one path and
79-
/// write it into another without adjusting the value here.
76+
/// - Important: The name of the property annotated with `@DocumentID` must not
77+
/// match the name of any fields in the Firestore document being read or else
78+
/// an error will be thrown. For example, if the `Codable` object has a
79+
/// property named `firstName` annotated with `@DocumentID`, and the Firestore
80+
/// document contains a field named `firstName`, an error will be thrown when
81+
/// attempting to decode the document.
8082
///
81-
/// NOTE: Trying to encode/decode this type using encoders/decoders other than
82-
/// Firestore.Encoder leads to an error.
83+
/// - Example Read:
84+
/// ````
85+
/// struct Player: Codable {
86+
/// @DocumentID var playerID: String?
87+
/// var health: Int64
88+
/// }
89+
///
90+
/// let p = try! await Firestore.firestore()
91+
/// .collection("players")
92+
/// .document("player-1")
93+
/// .getDocument(as: Player.self)
94+
/// print("\(p.playerID!) Health: \(p.health)")
95+
///
96+
/// // Prints: "Player: player-1, Health: 95"
97+
/// ````
98+
///
99+
/// - Important: Trying to encode/decode this type using encoders/decoders other than
100+
/// Firestore.Encoder throws an error.
101+
///
102+
/// - Important: When writing a Codable object containing an `@DocumentID` annotated field,
103+
/// its value is ignored. This allows you to read a document from one path and
104+
/// write it into another without adjusting the value here.
83105
@propertyWrapper
84106
public struct DocumentID<Value: DocumentIDWrappable & Codable>:
85-
DocumentIDProtocol, Codable, StructureCodingUncodedUnkeyed {
86-
var value: Value?
107+
StructureCodingUncodedUnkeyed {
108+
private var value: Value? = nil
87109

88110
public init(wrappedValue value: Value?) {
111+
if let value = value {
112+
logWarning(for: value)
113+
}
89114
self.value = value
90115
}
91116

92117
public var wrappedValue: Value? {
93118
get { value }
94-
set { value = newValue }
119+
set {
120+
if let someNewValue = newValue {
121+
logWarning(for: someNewValue)
122+
}
123+
value = newValue
124+
}
95125
}
96126

97-
// MARK: - `DocumentIDProtocol` conformance
127+
private func logWarning(for value: Value) {
128+
FIRLogWarningSwift(
129+
"[FirebaseFirestoreSwift]",
130+
"I-FST000002",
131+
"""
132+
Attempting to initialize or set a @DocumentID property with a non-nil
133+
value: \(value). The document ID is managed by Firestore and any
134+
initialized or set value will be ignored. The ID is automatically set
135+
when reading from Firestore."
136+
"""
137+
)
138+
}
139+
}
98140

99-
public init(from documentReference: DocumentReference?) throws {
141+
extension DocumentID: DocumentIDProtocol {
142+
internal init(from documentReference: DocumentReference?) throws {
100143
if let documentReference = documentReference {
101144
value = try Value.wrap(documentReference)
102145
} else {
103146
value = nil
104147
}
105148
}
149+
}
106150

107-
// MARK: - `Codable` implementation.
108-
151+
extension DocumentID: Codable {
152+
/// A `Codable` object containing an `@DocumentID` annotated field should
153+
/// only be decoded with `Firestore.Decoder`; this initializer throws if an
154+
/// unsupported decoder is used.
155+
///
156+
/// - Parameter decoder: A decoder.
157+
/// - Throws: ``FirestoreDecodingError``
109158
public init(from decoder: Decoder) throws {
110159
guard let reference = decoder
111160
.userInfo[CodingUserInfoKey.documentRefUserInfoKey] as? DocumentReference else {
112161
throw FirestoreDecodingError.decodingIsNotSupported(
113-
"Could not find DocumentReference for user info key: \(CodingUserInfoKey.documentRefUserInfoKey)"
162+
"""
163+
Could not find DocumentReference for user info key: \(CodingUserInfoKey
164+
.documentRefUserInfoKey).
165+
DocumentID values can only be decoded with Firestore.Decoder
166+
"""
114167
)
115168
}
116169
try self.init(from: reference)
117170
}
118171

172+
/// A `Codable` object containing an `@DocumentID` annotated field can only
173+
/// be encoded with `Firestore.Encoder`; this initializer always throws.
174+
///
175+
/// - Parameter encoder: An invalid encoder.
176+
/// - Throws: ``FirestoreEncodingError``
119177
public func encode(to encoder: Encoder) throws {
120178
throw FirestoreEncodingError.encodingIsNotSupported(
121179
"DocumentID values can only be encoded with Firestore.Encoder"

Firestore/Swift/Tests/Integration/CodableIntegrationTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import Foundation
1818
import FirebaseFirestore
19-
import FirebaseFirestoreSwift
19+
@testable import FirebaseFirestoreSwift
2020

2121
class CodableIntegrationTests: FSTIntegrationTestCase {
2222
private enum WriteFlavor {

Package.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,11 @@ let package = Package(
701701

702702
.target(
703703
name: "FirebaseFirestoreSwift",
704-
dependencies: ["FirebaseFirestore", "FirebaseSharedSwift"],
704+
dependencies: [
705+
"FirebaseCoreExtension",
706+
"FirebaseFirestore",
707+
"FirebaseSharedSwift",
708+
],
705709
path: "Firestore",
706710
exclude: [
707711
"CHANGELOG.md",

0 commit comments

Comments
 (0)