/* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import FirebaseFirestore /// A type that can initialize itself from a Firestore Timestamp, which makes /// it suitable for use with the `@ServerTimestamp` property wrapper. /// /// Firestore includes extensions that make `Timestamp` and `Date` conform to /// `ServerTimestampWrappable`. public protocol ServerTimestampWrappable { /// Creates a new instance by converting from the given `Timestamp`. /// /// - Parameter timestamp: The timestamp from which to convert. static func wrap(_ timestamp: Timestamp) throws -> Self /// Converts this value into a Firestore `Timestamp`. /// /// - Returns: A `Timestamp` representation of this value. static func unwrap(_ value: Self) throws -> Timestamp } extension Date: ServerTimestampWrappable { public static func wrap(_ timestamp: Timestamp) throws -> Self { return timestamp.dateValue() } public static func unwrap(_ value: Self) throws -> Timestamp { return Timestamp(date: value) } } extension Timestamp: ServerTimestampWrappable { public static func wrap(_ timestamp: Timestamp) throws -> Self { return timestamp as! Self } public static func unwrap(_ value: Timestamp) throws -> Timestamp { return value } } /// A property wrapper that marks an `Optional` field to be /// populated with a server timestamp. If a `Codable` object being written /// contains a `nil` for an `@ServerTimestamp`-annotated field, it will be /// replaced with `FieldValue.serverTimestamp()` as it is sent. /// /// Example: /// ``` /// struct CustomModel { /// @ServerTimestamp var ts: Timestamp? /// } /// ``` /// /// Then writing `CustomModel(ts: nil)` will tell server to fill `ts` with /// current timestamp. @propertyWrapper public struct ServerTimestamp: Codable where Value: ServerTimestampWrappable & Codable { var value: Value? public init(wrappedValue value: Value?) { self.value = value } public var wrappedValue: Value? { get { value } set { value = newValue } } // MARK: Codable public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if container.decodeNil() { value = nil } else { value = try Value.wrap(try container.decode(Timestamp.self)) } } public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() if let value = value { try container.encode(Value.unwrap(value)) } else { try container.encode(FieldValue.serverTimestamp()) } } } extension ServerTimestamp: Equatable where Value: Equatable {} extension ServerTimestamp: Hashable where Value: Hashable {}