ClientCodeTranslatorSnippetBasedTests.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. /*
  2. * Copyright 2023, gRPC Authors All rights reserved.
  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 Testing
  17. @testable import GRPCCodeGen
  18. @Suite
  19. struct ClientCodeTranslatorSnippetBasedTests {
  20. @Test
  21. @available(gRPCSwift 2.0, *)
  22. func translate() {
  23. let method = MethodDescriptor(
  24. documentation: "/// Documentation for MethodA",
  25. name: MethodName(identifyingName: "MethodA", typeName: "MethodA", functionName: "methodA"),
  26. isInputStreaming: false,
  27. isOutputStreaming: false,
  28. inputType: "NamespaceA_ServiceARequest",
  29. outputType: "NamespaceA_ServiceAResponse"
  30. )
  31. let service = ServiceDescriptor(
  32. documentation: "/// Documentation for ServiceA",
  33. name: ServiceName(
  34. identifyingName: "namespaceA.ServiceA",
  35. typeName: "NamespaceA_ServiceA",
  36. propertyName: ""
  37. ),
  38. methods: [method]
  39. )
  40. let expectedSwift = """
  41. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  42. extension NamespaceA_ServiceA {
  43. /// Generated client protocol for the "namespaceA.ServiceA" service.
  44. ///
  45. /// You don't need to implement this protocol directly, use the generated
  46. /// implementation, ``Client``.
  47. ///
  48. /// > Source IDL Documentation:
  49. /// >
  50. /// > Documentation for ServiceA
  51. public protocol ClientProtocol: Sendable {
  52. /// Call the "MethodA" method.
  53. ///
  54. /// > Source IDL Documentation:
  55. /// >
  56. /// > Documentation for MethodA
  57. ///
  58. /// - Parameters:
  59. /// - request: A request containing a single `NamespaceA_ServiceARequest` message.
  60. /// - serializer: A serializer for `NamespaceA_ServiceARequest` messages.
  61. /// - deserializer: A deserializer for `NamespaceA_ServiceAResponse` messages.
  62. /// - options: Options to apply to this RPC.
  63. /// - handleResponse: A closure which handles the response, the result of which is
  64. /// returned to the caller. Returning from the closure will cancel the RPC if it
  65. /// hasn't already finished.
  66. /// - Returns: The result of `handleResponse`.
  67. func methodA<Result>(
  68. request: GRPCCore.ClientRequest<NamespaceA_ServiceARequest>,
  69. serializer: some GRPCCore.MessageSerializer<NamespaceA_ServiceARequest>,
  70. deserializer: some GRPCCore.MessageDeserializer<NamespaceA_ServiceAResponse>,
  71. options: GRPCCore.CallOptions,
  72. onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse<NamespaceA_ServiceAResponse>) async throws -> Result
  73. ) async throws -> Result where Result: Sendable
  74. }
  75. /// Generated client for the "namespaceA.ServiceA" service.
  76. ///
  77. /// The ``Client`` provides an implementation of ``ClientProtocol`` which wraps
  78. /// a `GRPCCore.GRPCCClient`. The underlying `GRPCClient` provides the long-lived
  79. /// means of communication with the remote peer.
  80. ///
  81. /// > Source IDL Documentation:
  82. /// >
  83. /// > Documentation for ServiceA
  84. public struct Client<Transport>: ClientProtocol where Transport: GRPCCore.ClientTransport {
  85. private let client: GRPCCore.GRPCClient<Transport>
  86. /// Creates a new client wrapping the provided `GRPCCore.GRPCClient`.
  87. ///
  88. /// - Parameters:
  89. /// - client: A `GRPCCore.GRPCClient` providing a communication channel to the service.
  90. public init(wrapping client: GRPCCore.GRPCClient<Transport>) {
  91. self.client = client
  92. }
  93. /// Call the "MethodA" method.
  94. ///
  95. /// > Source IDL Documentation:
  96. /// >
  97. /// > Documentation for MethodA
  98. ///
  99. /// - Parameters:
  100. /// - request: A request containing a single `NamespaceA_ServiceARequest` message.
  101. /// - serializer: A serializer for `NamespaceA_ServiceARequest` messages.
  102. /// - deserializer: A deserializer for `NamespaceA_ServiceAResponse` messages.
  103. /// - options: Options to apply to this RPC.
  104. /// - handleResponse: A closure which handles the response, the result of which is
  105. /// returned to the caller. Returning from the closure will cancel the RPC if it
  106. /// hasn't already finished.
  107. /// - Returns: The result of `handleResponse`.
  108. public func methodA<Result>(
  109. request: GRPCCore.ClientRequest<NamespaceA_ServiceARequest>,
  110. serializer: some GRPCCore.MessageSerializer<NamespaceA_ServiceARequest>,
  111. deserializer: some GRPCCore.MessageDeserializer<NamespaceA_ServiceAResponse>,
  112. options: GRPCCore.CallOptions = .defaults,
  113. onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse<NamespaceA_ServiceAResponse>) async throws -> Result = { response in
  114. try response.message
  115. }
  116. ) async throws -> Result where Result: Sendable {
  117. try await self.client.unary(
  118. request: request,
  119. descriptor: NamespaceA_ServiceA.Method.MethodA.descriptor,
  120. serializer: serializer,
  121. deserializer: deserializer,
  122. options: options,
  123. onResponse: handleResponse
  124. )
  125. }
  126. }
  127. }
  128. // Helpers providing default arguments to 'ClientProtocol' methods.
  129. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  130. extension NamespaceA_ServiceA.ClientProtocol {
  131. /// Call the "MethodA" method.
  132. ///
  133. /// > Source IDL Documentation:
  134. /// >
  135. /// > Documentation for MethodA
  136. ///
  137. /// - Parameters:
  138. /// - request: A request containing a single `NamespaceA_ServiceARequest` message.
  139. /// - options: Options to apply to this RPC.
  140. /// - handleResponse: A closure which handles the response, the result of which is
  141. /// returned to the caller. Returning from the closure will cancel the RPC if it
  142. /// hasn't already finished.
  143. /// - Returns: The result of `handleResponse`.
  144. public func methodA<Result>(
  145. request: GRPCCore.ClientRequest<NamespaceA_ServiceARequest>,
  146. options: GRPCCore.CallOptions = .defaults,
  147. onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse<NamespaceA_ServiceAResponse>) async throws -> Result = { response in
  148. try response.message
  149. }
  150. ) async throws -> Result where Result: Sendable {
  151. try await self.methodA(
  152. request: request,
  153. serializer: GRPCProtobuf.ProtobufSerializer<NamespaceA_ServiceARequest>(),
  154. deserializer: GRPCProtobuf.ProtobufDeserializer<NamespaceA_ServiceAResponse>(),
  155. options: options,
  156. onResponse: handleResponse
  157. )
  158. }
  159. }
  160. // Helpers providing sugared APIs for 'ClientProtocol' methods.
  161. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  162. extension NamespaceA_ServiceA.ClientProtocol {
  163. /// Call the "MethodA" method.
  164. ///
  165. /// > Source IDL Documentation:
  166. /// >
  167. /// > Documentation for MethodA
  168. ///
  169. /// - Parameters:
  170. /// - message: request message to send.
  171. /// - metadata: Additional metadata to send, defaults to empty.
  172. /// - options: Options to apply to this RPC, defaults to `.defaults`.
  173. /// - handleResponse: A closure which handles the response, the result of which is
  174. /// returned to the caller. Returning from the closure will cancel the RPC if it
  175. /// hasn't already finished.
  176. /// - Returns: The result of `handleResponse`.
  177. public func methodA<Result>(
  178. _ message: NamespaceA_ServiceARequest,
  179. metadata: GRPCCore.Metadata = [:],
  180. options: GRPCCore.CallOptions = .defaults,
  181. onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse<NamespaceA_ServiceAResponse>) async throws -> Result = { response in
  182. try response.message
  183. }
  184. ) async throws -> Result where Result: Sendable {
  185. let request = GRPCCore.ClientRequest<NamespaceA_ServiceARequest>(
  186. message: message,
  187. metadata: metadata
  188. )
  189. return try await self.methodA(
  190. request: request,
  191. options: options,
  192. onResponse: handleResponse
  193. )
  194. }
  195. }
  196. """
  197. let rendered = self.render(accessLevel: .public, service: service)
  198. #expect(rendered == expectedSwift)
  199. }
  200. @available(gRPCSwift 2.0, *)
  201. private func render(
  202. accessLevel: AccessModifier,
  203. service: ServiceDescriptor
  204. ) -> String {
  205. let translator = ClientCodeTranslator()
  206. let codeBlocks = translator.translate(
  207. accessModifier: accessLevel,
  208. service: service,
  209. availability: .macOS15Aligned
  210. ) {
  211. "GRPCProtobuf.ProtobufSerializer<\($0)>()"
  212. } deserializer: {
  213. "GRPCProtobuf.ProtobufDeserializer<\($0)>()"
  214. }
  215. let renderer = TextBasedRenderer.default
  216. renderer.renderCodeBlocks(codeBlocks)
  217. return renderer.renderedContents()
  218. }
  219. }