ServerTimestamp.swift 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  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
  65. where Value: ServerTimestampWrappable & Codable {
  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. extension ServerTimestamp: Equatable where Value: Equatable {}
  93. extension ServerTimestamp: Hashable where Value: Hashable {}
  94. #endif // compiler(>=5.1)
  95. /// A compatibility version of `ServerTimestamp` that does not use property
  96. /// wrappers, suitable for use in older versions of Swift.
  97. ///
  98. /// Wraps a `Timestamp` field to mark that it should be populated with a server
  99. /// timestamp. If a `Codable` object being written contains a `.pending` for an
  100. /// `Swift4ServerTimestamp` field, it will be replaced with
  101. /// `FieldValue.serverTimestamp()` as it is sent.
  102. ///
  103. /// Example:
  104. /// ```
  105. /// struct CustomModel {
  106. /// var ts: Swift4ServerTimestamp
  107. /// }
  108. /// ```
  109. ///
  110. /// Then `CustomModel(ts: .pending)` will tell server to fill `ts` with current
  111. /// timestamp.
  112. @available(swift, deprecated: 5.1)
  113. public enum Swift4ServerTimestamp: Codable, Equatable {
  114. /// When being read (decoded) from Firestore, NSNull values will be mapped to
  115. /// `pending`. When being written (encoded) to Firestore, `pending` means
  116. /// requesting server to set timestamp on the field (essentially setting value
  117. /// to FieldValue.serverTimestamp()).
  118. case pending
  119. /// When being read (decoded) from Firestore, non-nil Timestamp will be mapped
  120. /// to `resolved`. When being written (encoded) to Firestore,
  121. /// `resolved(stamp)` will set the field value to `stamp`.
  122. case resolved(Timestamp)
  123. /// Returns this value as an `Optional<Timestamp>`.
  124. ///
  125. /// If the server timestamp is still pending, the returned optional will be
  126. /// `.none`. Once resolved, the returned optional will be `.some` with the
  127. /// resolved timestamp.
  128. public var timestamp: Timestamp? {
  129. switch self {
  130. case .pending:
  131. return .none
  132. case let .resolved(timestamp):
  133. return .some(timestamp)
  134. }
  135. }
  136. public init(from decoder: Decoder) throws {
  137. let container = try decoder.singleValueContainer()
  138. if container.decodeNil() {
  139. self = .pending
  140. } else {
  141. let value = try container.decode(Timestamp.self)
  142. self = .resolved(value)
  143. }
  144. }
  145. public func encode(to encoder: Encoder) throws {
  146. var container = encoder.singleValueContainer()
  147. switch self {
  148. case .pending:
  149. try container.encode(FieldValue.serverTimestamp())
  150. case let .resolved(value: value):
  151. try container.encode(value)
  152. }
  153. }
  154. }