mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-09 22:36:01 +05:45
* Rewrite SelfDocumentID as a property wrapper Make wrapped types conform to DocumentReferenceConvertible and use that protocol to implement the conversion. * Change DocumentReferenceConvertible to DocumentIDWrappable * Make ServerTimestampWrappable use static functions Use the same form as DocumentIDWrappable. * Update extension String: ServerTimestampWrappable * Use #if compiler, not #if swift The FirebaseFirestoreSwift module is declared as conforming to Swift 4.0 so #if swift is still false, even when compiling with Swift 5.1. Using #if compiler will see the actual compiler version. Property wrappers don't depend on runtime features, so this works. Also, allow Wrappable protocols to throw during conversions.
182 lines
5.8 KiB
Swift
182 lines
5.8 KiB
Swift
/*
|
|
* Copyright 2019 Google
|
|
*
|
|
* 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
|
|
|
|
#if compiler(>=5.1)
|
|
/// 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`, `Date`, and `NSDate`
|
|
/// 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 NSDate: ServerTimestampWrappable {
|
|
public static func wrap(_ timestamp: Timestamp) throws -> Self {
|
|
let interval = timestamp.dateValue().timeIntervalSince1970
|
|
return NSDate(timeIntervalSince1970: interval) as! Self
|
|
}
|
|
|
|
public static func unwrap(_ value: NSDate) throws -> Timestamp {
|
|
return Timestamp(date: value as Date)
|
|
}
|
|
}
|
|
|
|
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<Timestamp>` 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<Value>: Codable, Equatable
|
|
where Value: ServerTimestampWrappable & Codable & Equatable {
|
|
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())
|
|
}
|
|
}
|
|
}
|
|
#endif // compiler(>=5.1)
|
|
|
|
/// A compatibility version of `ServerTimestamp` that does not use property
|
|
/// wrappers, suitable for use in older versions of Swift.
|
|
///
|
|
/// Wraps a `Timestamp` field to mark that it should be populated with a server
|
|
/// timestamp. If a `Codable` object being written contains a `.pending` for an
|
|
/// `Swift4ServerTimestamp` field, it will be replaced with
|
|
/// `FieldValue.serverTimestamp()` as it is sent.
|
|
///
|
|
/// Example:
|
|
/// ```
|
|
/// struct CustomModel {
|
|
/// var ts: Swift4ServerTimestamp
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// Then `CustomModel(ts: .pending)` will tell server to fill `ts` with current
|
|
/// timestamp.
|
|
@available(swift, deprecated: 5.1)
|
|
public enum Swift4ServerTimestamp: Codable, Equatable {
|
|
/// When being read (decoded) from Firestore, NSNull values will be mapped to
|
|
/// `pending`. When being written (encoded) to Firestore, `pending` means
|
|
/// requesting server to set timestamp on the field (essentially setting value
|
|
/// to FieldValue.serverTimestamp()).
|
|
case pending
|
|
|
|
/// When being read (decoded) from Firestore, non-nil Timestamp will be mapped
|
|
/// to `resolved`. When being written (encoded) to Firestore,
|
|
/// `resolved(stamp)` will set the field value to `stamp`.
|
|
case resolved(Timestamp)
|
|
|
|
/// Returns this value as an `Optional<Timestamp>`.
|
|
///
|
|
/// If the server timestamp is still pending, the returned optional will be
|
|
/// `.none`. Once resolved, the returned optional will be `.some` with the
|
|
/// resolved timestamp.
|
|
public var timestamp: Timestamp? {
|
|
switch self {
|
|
case .pending:
|
|
return .none
|
|
case let .resolved(timestamp):
|
|
return .some(timestamp)
|
|
}
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.singleValueContainer()
|
|
if container.decodeNil() {
|
|
self = .pending
|
|
} else {
|
|
let value = try container.decode(Timestamp.self)
|
|
self = .resolved(value)
|
|
}
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.singleValueContainer()
|
|
switch self {
|
|
case .pending:
|
|
try container.encode(FieldValue.serverTimestamp())
|
|
case let .resolved(value: value):
|
|
try container.encode(value)
|
|
}
|
|
}
|
|
}
|