ClientCodeTranslatorSnippetBasedTests.swift 10 KB

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