Skip to content

Instantly share code, notes, and snippets.

@bannzai
Created August 1, 2024 06:03
Show Gist options
  • Save bannzai/7e0481a435f74a19ec1c23e9bcbae10f to your computer and use it in GitHub Desktop.
Save bannzai/7e0481a435f74a19ec1c23e9bcbae10f to your computer and use it in GitHub Desktop.
Document.swift
import Foundation
import FirebaseFirestore
import FirebaseFirestoreSwift
protocol SetIdentifiable: Identifiable {
mutating func set(id: ID)
}
typealias AnyEntity = Codable & Sendable & Hashable & Identifiable & SetIdentifiable
// onCall等でEntityを返す際にDocumentIDで以下のエラーになる。そのためonCallでのレスポンスはEntityを使用して、Firestoreからのデータ取得はDocument<Entity>を使用する
// - decodingIsNotSupported : "Could not find DocumentReference for user info key: CodingUserInfoKey(rawValue: \"DocumentRefUserInfoKey\").\nDocumentID values can only be decoded with Firestore.Decoder"
@dynamicMemberLookup
struct Document<Entity: AnyEntity>: Hashable, Identifiable, Sendable {
@DocumentID var id: Identifier<Entity>?
var entity: Entity
@ClientCreatedTimestamp var createdDateTime: Date?
@ClientUpdatedTimestamp var updatedDateTime: Date?
@ServerCreatedTimestamp var serverCreatedDateTime: Date?
@ServerUpdatedTimestamp var serverUpdatedDateTime: Date?
enum TimestampCodingKeys: CodingKey {
case createdDateTime
case updatedDateTime
case serverCreatedDateTime
case serverUpdatedDateTime
}
subscript <V>(dynamicMember keyPath: WritableKeyPath<Entity, V>) -> V {
get {
entity[keyPath: keyPath]
}
set {
entity[keyPath: keyPath] = newValue
}
}
subscript <V>(dynamicMember keyPath: KeyPath<Entity, V>) -> V {
entity[keyPath: keyPath]
}
}
extension Document: Codable where Entity.ID == Self.ID {
init(from decoder: Decoder) throws {
let singleValueContainer = try decoder.singleValueContainer()
_id = try singleValueContainer.decode(DocumentID<Identifier<Entity>>.self)
entity = try singleValueContainer.decode(Entity.self)
entity.set(id: id)
let timestamps = try decoder.container(keyedBy: TimestampCodingKeys.self)
_createdDateTime = try timestamps.decodeIfPresent(ClientCreatedTimestamp.self, forKey: .createdDateTime) ?? .init(wrappedValue: nil)
_updatedDateTime = try timestamps.decodeIfPresent(ClientUpdatedTimestamp.self, forKey: .updatedDateTime) ?? .init(wrappedValue: nil)
_serverCreatedDateTime = try timestamps.decodeIfPresent(ServerCreatedTimestamp.self, forKey: .serverCreatedDateTime) ?? .init(wrappedValue: nil)
_serverUpdatedDateTime = try timestamps.decodeIfPresent(ServerUpdatedTimestamp.self, forKey: .serverUpdatedDateTime) ?? .init(wrappedValue: nil)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(entity)
var timestamps = encoder.container(keyedBy: TimestampCodingKeys.self)
try timestamps.encode(createdDateTime, forKey: .createdDateTime)
try timestamps.encode(updatedDateTime, forKey: .updatedDateTime)
try timestamps.encode(serverCreatedDateTime, forKey: .serverCreatedDateTime)
try timestamps.encode(serverUpdatedDateTime, forKey: .serverUpdatedDateTime)
}
}
@bannzai
Copy link
Author

bannzai commented Aug 1, 2024

FirestoreのDocumentと、onCallでDocumentを返却するときにTimestampの情報がonCallの場合は欠落するので(FirestoreのSDKを通さないレスポンスだから)、頑張って共通化した。上手くいったけど、型宣言の時に都度 Document という宣言になるのとうっかりすると同じCollectionでDocumentアリ・ナシの別の型で.setとかしちゃいそうなので供養

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment