QueryRef.swift 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. // Copyright 2024 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import Foundation
  15. import Combine
  16. import Observation
  17. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  18. public enum ResultsPublisherType {
  19. case auto // automatically determine ObservableQueryRef
  20. case observableObject // pre-iOS 17 ObservableObject
  21. case observableMacro // iOS 17+ Observation framework
  22. }
  23. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  24. public struct QueryRequest<Variable: OperationVariable>: OperationRequest, Hashable, Equatable {
  25. public private(set) var operationName: String
  26. public private(set) var variables: Variable?
  27. public init(operationName: String, variables: Variable? = nil) {
  28. self.operationName = operationName
  29. self.variables = variables
  30. }
  31. // Hashable and Equatable implementation
  32. public func hash(into hasher: inout Hasher) {
  33. hasher.combine(operationName)
  34. if let variables {
  35. hasher.combine(variables)
  36. }
  37. }
  38. public static func == (lhs: QueryRequest, rhs: QueryRequest) -> Bool {
  39. guard lhs.operationName == rhs.operationName else {
  40. return false
  41. }
  42. if lhs.variables == nil && rhs.variables == nil {
  43. return true
  44. }
  45. guard let lhsVar = lhs.variables,
  46. let rhsVar = rhs.variables,
  47. lhsVar == rhsVar else {
  48. return false
  49. }
  50. return true
  51. }
  52. }
  53. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  54. public protocol QueryRef: OperationRef {
  55. // This call starts query execution and publishes data
  56. func subscribe() async throws -> AnyPublisher<Result<ResultData, DataConnectError>, Never>
  57. }
  58. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  59. actor GenericQueryRef<ResultData: Decodable, Variable: OperationVariable>: QueryRef {
  60. private var resultsPublisher = PassthroughSubject<Result<ResultData, DataConnectError>,
  61. Never>()
  62. var request: QueryRequest<Variable>
  63. private var grpcClient: GrpcClient
  64. init(request: QueryRequest<Variable>, grpcClient: GrpcClient) {
  65. self.request = request
  66. self.grpcClient = grpcClient
  67. }
  68. // This call starts query execution and publishes data to data var
  69. // In v0, it simply reloads query results
  70. public func subscribe() -> AnyPublisher<Result<ResultData, DataConnectError>, Never> {
  71. Task {
  72. do {
  73. _ = try await reloadResults()
  74. } catch {}
  75. }
  76. return resultsPublisher.eraseToAnyPublisher()
  77. }
  78. // one-shot execution. It will fetch latest data, update any caches
  79. // and updates the published data var
  80. public func execute() async throws -> OperationResult<ResultData> {
  81. let resultData = try await reloadResults()
  82. return OperationResult(data: resultData)
  83. }
  84. private func reloadResults() async throws -> ResultData {
  85. let results = try await grpcClient.executeQuery(
  86. request: request,
  87. resultType: ResultData.self
  88. )
  89. await updateData(data: results.data)
  90. return results.data
  91. }
  92. func updateData(data: ResultData) async {
  93. resultsPublisher.send(.success(data))
  94. }
  95. }
  96. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  97. public protocol ObservableQueryRef: QueryRef {
  98. // results of fetch.
  99. var data: ResultData? { get }
  100. // last error received. if last fetch was successful this is cleared
  101. var lastError: DataConnectError? { get }
  102. }
  103. /*
  104. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  105. extension ObservableQueryRef {
  106. public func subscribe() async throws -> AnyPublisher<Result<ResultDataType, DataConnectError>, Never> {
  107. //return Empty<Result<ResultDataType, DataConnectError>, Never>()
  108. }
  109. }
  110. */
  111. // QueryRef class used with ObservableObject protocol
  112. // data: Published variable that contains bindable results of the query.
  113. // lastError: Published variable that contains DataConnectError if last fetch had error.
  114. // If last fetch was successful, this variable is cleared
  115. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  116. public class QueryRefObservableObject<
  117. ResultData: Decodable,
  118. Variable: OperationVariable
  119. >: ObservableObject, ObservableQueryRef {
  120. private var request: QueryRequest<Variable>
  121. private var baseRef: GenericQueryRef<ResultData, Variable>
  122. private var resultsCancellable: AnyCancellable?
  123. init(request: QueryRequest<Variable>, dataType: ResultData.Type, grpcClient: GrpcClient) {
  124. self.request = request
  125. baseRef = GenericQueryRef(request: request, grpcClient: grpcClient)
  126. setupSubscription()
  127. }
  128. private func setupSubscription() {
  129. Task {
  130. resultsCancellable = await baseRef.subscribe()
  131. .receive(on: DispatchQueue.main)
  132. .sink(receiveValue: { result in
  133. switch result {
  134. case let .success(resultData):
  135. self.data = resultData
  136. self.lastError = nil
  137. case let .failure(dcerror):
  138. self.lastError = dcerror
  139. }
  140. })
  141. }
  142. }
  143. // ObservableQueryRef implementation
  144. @Published public private(set) var data: ResultData?
  145. @Published public private(set) var lastError: DataConnectError?
  146. // QueryRef implementation
  147. public func execute() async throws -> OperationResult<ResultData> {
  148. let result = try await baseRef.execute()
  149. return result
  150. }
  151. public func subscribe() async throws
  152. -> AnyPublisher<Result<ResultData, DataConnectError>, Never> {
  153. return await baseRef.subscribe()
  154. }
  155. }
  156. // QueryRef class compatible with the Observation framework introduced in iOS 17
  157. // data: Published variable that contains bindable results of the query.
  158. // lastError: Published variable that contains DataConnectError if last fetch had error.
  159. // If last fetch was successful, this variable is cleared
  160. @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *)
  161. @Observable
  162. public class QueryRefObservation<
  163. ResultData: Decodable,
  164. Variable: OperationVariable
  165. >: ObservableQueryRef {
  166. @ObservationIgnored
  167. private var request: QueryRequest<Variable>
  168. @ObservationIgnored
  169. private var baseRef: GenericQueryRef<ResultData, Variable>
  170. @ObservationIgnored
  171. private var resultsCancellable: AnyCancellable?
  172. init(request: QueryRequest<Variable>, dataType: ResultData.Type, grpcClient: GrpcClient) {
  173. self.request = request
  174. baseRef = GenericQueryRef(request: request, grpcClient: grpcClient)
  175. setupSubscription()
  176. }
  177. private func setupSubscription() {
  178. Task {
  179. resultsCancellable = await baseRef.subscribe()
  180. .receive(on: DispatchQueue.main)
  181. .sink(receiveValue: { result in
  182. switch result {
  183. case let .success(resultData):
  184. self.data = resultData
  185. self.lastError = nil
  186. case let .failure(dcerror):
  187. self.lastError = dcerror
  188. }
  189. })
  190. }
  191. }
  192. // ObservableQueryRef implementation
  193. public private(set) var data: ResultData?
  194. public private(set) var lastError: DataConnectError?
  195. // QueryRef implementation
  196. public func execute() async throws -> OperationResult<ResultData> {
  197. let result = try await baseRef.execute()
  198. return result
  199. }
  200. public func subscribe() async throws
  201. -> AnyPublisher<Result<ResultData, DataConnectError>, Never> {
  202. return await baseRef.subscribe()
  203. }
  204. }