|
|
@@ -16,6 +16,7 @@
|
|
|
|
|
|
import FirebaseFirestore
|
|
|
import FirebaseSharedSwift
|
|
|
+@_implementationOnly import FirebaseCoreExtension
|
|
|
|
|
|
extension CodingUserInfoKey {
|
|
|
static let documentRefUserInfoKey =
|
|
|
@@ -65,57 +66,114 @@ internal protocol DocumentIDProtocol {
|
|
|
init(from documentReference: DocumentReference?) throws
|
|
|
}
|
|
|
|
|
|
-/// A value that is populated in Codable objects with the `DocumentReference`
|
|
|
-/// of the current document by the Firestore.Decoder when a document is read.
|
|
|
+/// A property wrapper type that marks a `DocumentReference?` or `String?` field to
|
|
|
+/// be populated with a document identifier when it is read.
|
|
|
///
|
|
|
-/// If the field name used for this type conflicts with a read document field,
|
|
|
-/// an error is thrown. For example, if a custom object has a field `firstName`
|
|
|
-/// annotated with `@DocumentID`, and there is a property from the document
|
|
|
-/// named `firstName` as well, an error is thrown when you try to read the
|
|
|
-/// document.
|
|
|
+/// Apply the `@DocumentID` annotation to a `DocumentReference?` or `String?`
|
|
|
+/// property in a `Codable` object to have it populated with the document
|
|
|
+/// identifier when it is read and decoded from Firestore.
|
|
|
///
|
|
|
-/// When writing a Codable object containing an `@DocumentID` annotated field,
|
|
|
-/// its value is ignored. This allows you to read a document from one path and
|
|
|
-/// write it into another without adjusting the value here.
|
|
|
+/// - Important: The name of the property annotated with `@DocumentID` must not
|
|
|
+/// match the name of any fields in the Firestore document being read or else
|
|
|
+/// an error will be thrown. For example, if the `Codable` object has a
|
|
|
+/// property named `firstName` annotated with `@DocumentID`, and the Firestore
|
|
|
+/// document contains a field named `firstName`, an error will be thrown when
|
|
|
+/// attempting to decode the document.
|
|
|
///
|
|
|
-/// NOTE: Trying to encode/decode this type using encoders/decoders other than
|
|
|
-/// Firestore.Encoder leads to an error.
|
|
|
+/// - Example Read:
|
|
|
+/// ````
|
|
|
+/// struct Player: Codable {
|
|
|
+/// @DocumentID var playerID: String?
|
|
|
+/// var health: Int64
|
|
|
+/// }
|
|
|
+///
|
|
|
+/// let p = try! await Firestore.firestore()
|
|
|
+/// .collection("players")
|
|
|
+/// .document("player-1")
|
|
|
+/// .getDocument(as: Player.self)
|
|
|
+/// print("\(p.playerID!) Health: \(p.health)")
|
|
|
+///
|
|
|
+/// // Prints: "Player: player-1, Health: 95"
|
|
|
+/// ````
|
|
|
+///
|
|
|
+/// - Important: Trying to encode/decode this type using encoders/decoders other than
|
|
|
+/// Firestore.Encoder throws an error.
|
|
|
+///
|
|
|
+/// - Important: When writing a Codable object containing an `@DocumentID` annotated field,
|
|
|
+/// its value is ignored. This allows you to read a document from one path and
|
|
|
+/// write it into another without adjusting the value here.
|
|
|
@propertyWrapper
|
|
|
public struct DocumentID<Value: DocumentIDWrappable & Codable>:
|
|
|
- DocumentIDProtocol, Codable, StructureCodingUncodedUnkeyed {
|
|
|
- var value: Value?
|
|
|
+ StructureCodingUncodedUnkeyed {
|
|
|
+ private var value: Value? = nil
|
|
|
|
|
|
public init(wrappedValue value: Value?) {
|
|
|
+ if let value = value {
|
|
|
+ logWarning(for: value)
|
|
|
+ }
|
|
|
self.value = value
|
|
|
}
|
|
|
|
|
|
public var wrappedValue: Value? {
|
|
|
get { value }
|
|
|
- set { value = newValue }
|
|
|
+ set {
|
|
|
+ if let someNewValue = newValue {
|
|
|
+ logWarning(for: someNewValue)
|
|
|
+ }
|
|
|
+ value = newValue
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- // MARK: - `DocumentIDProtocol` conformance
|
|
|
+ private func logWarning(for value: Value) {
|
|
|
+ FIRLogWarningSwift(
|
|
|
+ "[FirebaseFirestoreSwift]",
|
|
|
+ "I-FST000002",
|
|
|
+ """
|
|
|
+ Attempting to initialize or set a @DocumentID property with a non-nil
|
|
|
+ value: \(value). The document ID is managed by Firestore and any
|
|
|
+ initialized or set value will be ignored. The ID is automatically set
|
|
|
+ when reading from Firestore."
|
|
|
+ """
|
|
|
+ )
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- public init(from documentReference: DocumentReference?) throws {
|
|
|
+extension DocumentID: DocumentIDProtocol {
|
|
|
+ internal init(from documentReference: DocumentReference?) throws {
|
|
|
if let documentReference = documentReference {
|
|
|
value = try Value.wrap(documentReference)
|
|
|
} else {
|
|
|
value = nil
|
|
|
}
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- // MARK: - `Codable` implementation.
|
|
|
-
|
|
|
+extension DocumentID: Codable {
|
|
|
+ /// A `Codable` object containing an `@DocumentID` annotated field should
|
|
|
+ /// only be decoded with `Firestore.Decoder`; this initializer throws if an
|
|
|
+ /// unsupported decoder is used.
|
|
|
+ ///
|
|
|
+ /// - Parameter decoder: A decoder.
|
|
|
+ /// - Throws: ``FirestoreDecodingError``
|
|
|
public init(from decoder: Decoder) throws {
|
|
|
guard let reference = decoder
|
|
|
.userInfo[CodingUserInfoKey.documentRefUserInfoKey] as? DocumentReference else {
|
|
|
throw FirestoreDecodingError.decodingIsNotSupported(
|
|
|
- "Could not find DocumentReference for user info key: \(CodingUserInfoKey.documentRefUserInfoKey)"
|
|
|
+ """
|
|
|
+ Could not find DocumentReference for user info key: \(CodingUserInfoKey
|
|
|
+ .documentRefUserInfoKey).
|
|
|
+ DocumentID values can only be decoded with Firestore.Decoder
|
|
|
+ """
|
|
|
)
|
|
|
}
|
|
|
try self.init(from: reference)
|
|
|
}
|
|
|
|
|
|
+ /// A `Codable` object containing an `@DocumentID` annotated field can only
|
|
|
+ /// be encoded with `Firestore.Encoder`; this initializer always throws.
|
|
|
+ ///
|
|
|
+ /// - Parameter encoder: An invalid encoder.
|
|
|
+ /// - Throws: ``FirestoreEncodingError``
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
|
throw FirestoreEncodingError.encodingIsNotSupported(
|
|
|
"DocumentID values can only be encoded with Firestore.Encoder"
|