ServerTimestamp.swift 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. /*
  2. * Copyright 2019 Google LLC
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import FirebaseFirestore
  17. #if compiler(>=5.1)
  18. /// A type that can initialize itself from a Firestore Timestamp, which makes
  19. /// it suitable for use with the `@ServerTimestamp` property wrapper.
  20. ///
  21. /// Firestore includes extensions that make `Timestamp` and `Date` conform to
  22. /// `ServerTimestampWrappable`.
  23. public protocol ServerTimestampWrappable {
  24. /// Creates a new instance by converting from the given `Timestamp`.
  25. ///
  26. /// - Parameter timestamp: The timestamp from which to convert.
  27. static func wrap(_ timestamp: Timestamp) throws -> Self
  28. /// Converts this value into a Firestore `Timestamp`.
  29. ///
  30. /// - Returns: A `Timestamp` representation of this value.
  31. static func unwrap(_ value: Self) throws -> Timestamp
  32. }
  33. extension Date: ServerTimestampWrappable {
  34. public static func wrap(_ timestamp: Timestamp) throws -> Self {
  35. return timestamp.dateValue()
  36. }
  37. public static func unwrap(_ value: Self) throws -> Timestamp {
  38. return Timestamp(date: value)
  39. }
  40. }
  41. extension Timestamp: ServerTimestampWrappable {
  42. public static func wrap(_ timestamp: Timestamp) throws -> Self {
  43. return timestamp as! Self
  44. }
  45. public static func unwrap(_ value: Timestamp) throws -> Timestamp {
  46. return value
  47. }
  48. }
  49. /// A property wrapper that marks an `Optional<Timestamp>` field to be
  50. /// populated with a server timestamp. If a `Codable` object being written
  51. /// contains a `nil` for an `@ServerTimestamp`-annotated field, it will be
  52. /// replaced with `FieldValue.serverTimestamp()` as it is sent.
  53. ///
  54. /// Example:
  55. /// ```
  56. /// struct CustomModel {
  57. /// @ServerTimestamp var ts: Timestamp?
  58. /// }
  59. /// ```
  60. ///
  61. /// Then writing `CustomModel(ts: nil)` will tell server to fill `ts` with
  62. /// current timestamp.
  63. @propertyWrapper
  64. public struct ServerTimestamp<Value>: Codable, Equatable
  65. where Value: ServerTimestampWrappable & Codable & Equatable {
  66. var value: Value?
  67. public init(wrappedValue value: Value?) {
  68. self.value = value
  69. }
  70. public var wrappedValue: Value? {
  71. get { value }
  72. set { value = newValue }
  73. }
  74. // MARK: Codable
  75. public init(from decoder: Decoder) throws {
  76. let container = try decoder.singleValueContainer()
  77. if container.decodeNil() {
  78. value = nil
  79. } else {
  80. value = try Value.wrap(try container.decode(Timestamp.self))
  81. }
  82. }
  83. public func encode(to encoder: Encoder) throws {
  84. var container = encoder.singleValueContainer()
  85. if let value = value {
  86. try container.encode(Value.unwrap(value))
  87. } else {
  88. try container.encode(FieldValue.serverTimestamp())
  89. }
  90. }
  91. }
  92. #endif // compiler(>=5.1)
  93. /// A compatibility version of `ServerTimestamp` that does not use property
  94. /// wrappers, suitable for use in older versions of Swift.
  95. ///
  96. /// Wraps a `Timestamp` field to mark that it should be populated with a server
  97. /// timestamp. If a `Codable` object being written contains a `.pending` for an
  98. /// `Swift4ServerTimestamp` field, it will be replaced with
  99. /// `FieldValue.serverTimestamp()` as it is sent.
  100. ///
  101. /// Example:
  102. /// ```
  103. /// struct CustomModel {
  104. /// var ts: Swift4ServerTimestamp
  105. /// }
  106. /// ```
  107. ///
  108. /// Then `CustomModel(ts: .pending)` will tell server to fill `ts` with current
  109. /// timestamp.
  110. @available(swift, deprecated: 5.1)
  111. public enum Swift4ServerTimestamp: Codable, Equatable {
  112. /// When being read (decoded) from Firestore, NSNull values will be mapped to
  113. /// `pending`. When being written (encoded) to Firestore, `pending` means
  114. /// requesting server to set timestamp on the field (essentially setting value
  115. /// to FieldValue.serverTimestamp()).
  116. case pending
  117. /// When being read (decoded) from Firestore, non-nil Timestamp will be mapped
  118. /// to `resolved`. When being written (encoded) to Firestore,
  119. /// `resolved(stamp)` will set the field value to `stamp`.
  120. case resolved(Timestamp)
  121. /// Returns this value as an `Optional<Timestamp>`.
  122. ///
  123. /// If the server timestamp is still pending, the returned optional will be
  124. /// `.none`. Once resolved, the returned optional will be `.some` with the
  125. /// resolved timestamp.
  126. public var timestamp: Timestamp? {
  127. switch self {
  128. case .pending:
  129. return .none
  130. case let .resolved(timestamp):
  131. return .some(timestamp)
  132. }
  133. }
  134. public init(from decoder: Decoder) throws {
  135. let container = try decoder.singleValueContainer()
  136. if container.decodeNil() {
  137. self = .pending
  138. } else {
  139. let value = try container.decode(Timestamp.self)
  140. self = .resolved(value)
  141. }
  142. }
  143. public func encode(to encoder: Encoder) throws {
  144. var container = encoder.singleValueContainer()
  145. switch self {
  146. case .pending:
  147. try container.encode(FieldValue.serverTimestamp())
  148. case let .resolved(value: value):
  149. try container.encode(value)
  150. }
  151. }
  152. }