ClientCodeTranslator.swift 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  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. /// Creates a representation for the client code that will be generated based on the ``CodeGenerationRequest`` object
  17. /// specifications, using types from ``StructuredSwiftRepresentation``.
  18. ///
  19. /// For example, in the case of a service called "Bar", in the "foo" namespace which has
  20. /// one method "baz" with input type "Input" and output type "Output", the ``ClientCodeTranslator`` will create
  21. /// a representation for the following generated code:
  22. ///
  23. /// ```swift
  24. /// @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  25. /// public protocol Foo_BarClientProtocol: Sendable {
  26. /// func baz<R>(
  27. /// request: GRPCCore.ClientRequest.Single<Foo_Bar_Input>,
  28. /// serializer: some GRPCCore.MessageSerializer<Foo_Bar_Input>,
  29. /// deserializer: some GRPCCore.MessageDeserializer<Foo_Bar_Output>,
  30. /// options: GRPCCore.CallOptions = .defaults,
  31. /// _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single<Foo_Bar_Output>) async throws -> R
  32. /// ) async throws -> R where R: Sendable
  33. /// }
  34. /// @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  35. /// extension Foo_Bar.ClientProtocol {
  36. /// public func baz<R>(
  37. /// request: GRPCCore.ClientRequest.Single<Foo_Bar_Input>,
  38. /// options: GRPCCore.CallOptions = .defaults,
  39. /// _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single<Foo_Bar_Output>) async throws -> R = {
  40. /// try $0.message
  41. /// }
  42. /// ) async throws -> R where R: Sendable {
  43. /// try await self.baz(
  44. /// request: request,
  45. /// serializer: GRPCProtobuf.ProtobufSerializer<Foo_Bar_Input>(),
  46. /// deserializer: GRPCProtobuf.ProtobufDeserializer<Foo_Bar_Output>(),
  47. /// options: options,
  48. /// body
  49. /// )
  50. /// }
  51. /// @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  52. /// public struct Foo_BarClient: Foo_Bar.ClientProtocol {
  53. /// private let client: GRPCCore.GRPCClient
  54. /// public init(wrapping client: GRPCCore.GRPCClient) {
  55. /// self.client = client
  56. /// }
  57. /// public func methodA<R>(
  58. /// request: GRPCCore.ClientRequest.Stream<Foo_Bar_Input>,
  59. /// serializer: some GRPCCore.MessageSerializer<Foo_Bar_Input>,
  60. /// deserializer: some GRPCCore.MessageDeserializer<Foo_Bar_Output>,
  61. /// options: GRPCCore.CallOptions = .defaults,
  62. /// _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single<Foo_Bar_Output>) async throws -> R = {
  63. /// try $0.message
  64. /// }
  65. /// ) async throws -> R where R: Sendable {
  66. /// try await self.client.unary(
  67. /// request: request,
  68. /// descriptor: NamespaceA.ServiceA.Method.MethodA.descriptor,
  69. /// serializer: serializer,
  70. /// deserializer: deserializer,
  71. /// options: options,
  72. /// handler: body
  73. /// )
  74. /// }
  75. /// }
  76. ///```
  77. struct ClientCodeTranslator: SpecializedTranslator {
  78. var accessLevel: SourceGenerator.Configuration.AccessLevel
  79. init(accessLevel: SourceGenerator.Configuration.AccessLevel) {
  80. self.accessLevel = accessLevel
  81. }
  82. func translate(from request: CodeGenerationRequest) throws -> [CodeBlock] {
  83. var blocks = [CodeBlock]()
  84. for service in request.services {
  85. let `protocol` = self.makeClientProtocol(for: service, in: request)
  86. blocks.append(.declaration(.commentable(.preFormatted(service.documentation), `protocol`)))
  87. let defaultImplementation = self.makeDefaultImplementation(for: service, in: request)
  88. blocks.append(.declaration(defaultImplementation))
  89. let sugaredAPI = self.makeSugaredAPI(forService: service, request: request)
  90. blocks.append(.declaration(sugaredAPI))
  91. let clientStruct = self.makeClientStruct(for: service, in: request)
  92. blocks.append(.declaration(.commentable(.preFormatted(service.documentation), clientStruct)))
  93. }
  94. return blocks
  95. }
  96. }
  97. extension ClientCodeTranslator {
  98. private func makeClientProtocol(
  99. for service: CodeGenerationRequest.ServiceDescriptor,
  100. in codeGenerationRequest: CodeGenerationRequest
  101. ) -> Declaration {
  102. let methods = service.methods.map {
  103. self.makeClientProtocolMethod(
  104. for: $0,
  105. in: service,
  106. from: codeGenerationRequest,
  107. includeBody: false,
  108. includeDefaultCallOptions: false
  109. )
  110. }
  111. let clientProtocol = Declaration.protocol(
  112. ProtocolDescription(
  113. accessModifier: self.accessModifier,
  114. name: "\(service.namespacedGeneratedName)ClientProtocol",
  115. conformances: ["Sendable"],
  116. members: methods
  117. )
  118. )
  119. return .guarded(self.availabilityGuard, clientProtocol)
  120. }
  121. private func makeDefaultImplementation(
  122. for service: CodeGenerationRequest.ServiceDescriptor,
  123. in codeGenerationRequest: CodeGenerationRequest
  124. ) -> Declaration {
  125. let methods = service.methods.map {
  126. self.makeClientProtocolMethod(
  127. for: $0,
  128. in: service,
  129. from: codeGenerationRequest,
  130. includeBody: true,
  131. accessModifier: self.accessModifier,
  132. includeDefaultCallOptions: true
  133. )
  134. }
  135. let clientProtocolExtension = Declaration.extension(
  136. ExtensionDescription(
  137. onType: "\(service.namespacedGeneratedName).ClientProtocol",
  138. declarations: methods
  139. )
  140. )
  141. return .guarded(
  142. self.availabilityGuard,
  143. clientProtocolExtension
  144. )
  145. }
  146. private func makeSugaredAPI(
  147. forService service: CodeGenerationRequest.ServiceDescriptor,
  148. request: CodeGenerationRequest
  149. ) -> Declaration {
  150. let sugaredAPIExtension = Declaration.extension(
  151. ExtensionDescription(
  152. onType: "\(service.namespacedGeneratedName).ClientProtocol",
  153. declarations: service.methods.map { method in
  154. self.makeSugaredMethodDeclaration(
  155. method: method,
  156. accessModifier: self.accessModifier
  157. )
  158. }
  159. )
  160. )
  161. return .guarded(self.availabilityGuard, sugaredAPIExtension)
  162. }
  163. private func makeSugaredMethodDeclaration(
  164. method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor,
  165. accessModifier: AccessModifier?
  166. ) -> Declaration {
  167. let signature = FunctionSignatureDescription(
  168. accessModifier: accessModifier,
  169. kind: .function(name: method.name.generatedLowerCase),
  170. generics: [.member("Result")],
  171. parameters: self.makeParametersForSugaredMethodDeclaration(method: method),
  172. keywords: [.async, .throws],
  173. returnType: .identifierPattern("Result"),
  174. whereClause: WhereClause(
  175. requirements: [
  176. .conformance("Result", "Sendable")
  177. ]
  178. )
  179. )
  180. let functionDescription = FunctionDescription(
  181. signature: signature,
  182. body: self.makeFunctionBodyForSugaredMethodDeclaration(method: method)
  183. )
  184. if method.documentation.isEmpty {
  185. return .function(functionDescription)
  186. } else {
  187. return .commentable(.preFormatted(method.documentation), .function(functionDescription))
  188. }
  189. }
  190. private func makeParametersForSugaredMethodDeclaration(
  191. method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor
  192. ) -> [ParameterDescription] {
  193. var parameters = [ParameterDescription]()
  194. // Unary inputs have a 'message' parameter
  195. if !method.isInputStreaming {
  196. parameters.append(
  197. ParameterDescription(
  198. label: "_",
  199. name: "message",
  200. type: .member([method.inputType])
  201. )
  202. )
  203. }
  204. parameters.append(
  205. ParameterDescription(
  206. label: "metadata",
  207. type: .member(["GRPCCore", "Metadata"]),
  208. defaultValue: .literal(.dictionary([]))
  209. )
  210. )
  211. parameters.append(
  212. ParameterDescription(
  213. label: "options",
  214. type: .member(["GRPCCore", "CallOptions"]),
  215. defaultValue: .memberAccess(.dot("defaults"))
  216. )
  217. )
  218. // Streaming inputs have a writer callback
  219. if method.isInputStreaming {
  220. parameters.append(
  221. ParameterDescription(
  222. label: "requestProducer",
  223. type: .closure(
  224. ClosureSignatureDescription(
  225. parameters: [
  226. ParameterDescription(
  227. type: .generic(
  228. wrapper: .member(["GRPCCore", "RPCWriter"]),
  229. wrapped: .member(method.inputType)
  230. )
  231. )
  232. ],
  233. keywords: [.async, .throws],
  234. returnType: .identifierPattern("Void"),
  235. sendable: true,
  236. escaping: true
  237. )
  238. )
  239. )
  240. )
  241. }
  242. // All methods have a response handler.
  243. var responseHandler = ParameterDescription(label: "onResponse", name: "handleResponse")
  244. let responseKind = method.isOutputStreaming ? "Stream" : "Single"
  245. responseHandler.type = .closure(
  246. ClosureSignatureDescription(
  247. parameters: [
  248. ParameterDescription(
  249. type: .generic(
  250. wrapper: .member(["GRPCCore", "ClientResponse", responseKind]),
  251. wrapped: .member(method.outputType)
  252. )
  253. )
  254. ],
  255. keywords: [.async, .throws],
  256. returnType: .identifierPattern("Result"),
  257. sendable: true,
  258. escaping: true
  259. )
  260. )
  261. if !method.isOutputStreaming {
  262. responseHandler.defaultValue = .closureInvocation(
  263. ClosureInvocationDescription(
  264. body: [.expression(.try(.identifierPattern("$0").dot("message")))]
  265. )
  266. )
  267. }
  268. parameters.append(responseHandler)
  269. return parameters
  270. }
  271. private func makeFunctionBodyForSugaredMethodDeclaration(
  272. method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor
  273. ) -> [CodeBlock] {
  274. // Produces the following:
  275. //
  276. // let request = GRPCCore.ClientRequest.Single<Input>(message: message, metadata: metadata)
  277. // return try await method(request: request, options: options, responseHandler: responseHandler)
  278. //
  279. // or:
  280. //
  281. // let request = GRPCCore.ClientRequest.Stream<Input>(metadata: metadata, producer: writer)
  282. // return try await method(request: request, options: options, responseHandler: responseHandler)
  283. // First, make the init for the ClientRequest
  284. let requestType = method.isInputStreaming ? "Stream" : "Single"
  285. var requestInit = FunctionCallDescription(
  286. calledExpression: .identifier(
  287. .type(
  288. .generic(
  289. wrapper: .member(["GRPCCore", "ClientRequest", requestType]),
  290. wrapped: .member(method.inputType)
  291. )
  292. )
  293. )
  294. )
  295. if method.isInputStreaming {
  296. requestInit.arguments.append(
  297. FunctionArgumentDescription(
  298. label: "metadata",
  299. expression: .identifierPattern("metadata")
  300. )
  301. )
  302. requestInit.arguments.append(
  303. FunctionArgumentDescription(
  304. label: "producer",
  305. expression: .identifierPattern("requestProducer")
  306. )
  307. )
  308. } else {
  309. requestInit.arguments.append(
  310. FunctionArgumentDescription(
  311. label: "message",
  312. expression: .identifierPattern("message")
  313. )
  314. )
  315. requestInit.arguments.append(
  316. FunctionArgumentDescription(
  317. label: "metadata",
  318. expression: .identifierPattern("metadata")
  319. )
  320. )
  321. }
  322. // Now declare the request:
  323. //
  324. // let request = <RequestInit>
  325. let request = VariableDescription(
  326. kind: .let,
  327. left: .identifier(.pattern("request")),
  328. right: .functionCall(requestInit)
  329. )
  330. var blocks = [CodeBlock]()
  331. blocks.append(.declaration(.variable(request)))
  332. // Finally, call the underlying method.
  333. let methodCall = FunctionCallDescription(
  334. calledExpression: .identifierPattern("self").dot(method.name.generatedLowerCase),
  335. arguments: [
  336. FunctionArgumentDescription(label: "request", expression: .identifierPattern("request")),
  337. FunctionArgumentDescription(label: "options", expression: .identifierPattern("options")),
  338. FunctionArgumentDescription(expression: .identifierPattern("handleResponse")),
  339. ]
  340. )
  341. blocks.append(.expression(.return(.try(.await(.functionCall(methodCall))))))
  342. return blocks
  343. }
  344. private func makeClientProtocolMethod(
  345. for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor,
  346. in service: CodeGenerationRequest.ServiceDescriptor,
  347. from codeGenerationRequest: CodeGenerationRequest,
  348. includeBody: Bool,
  349. accessModifier: AccessModifier? = nil,
  350. includeDefaultCallOptions: Bool
  351. ) -> Declaration {
  352. let isProtocolExtension = includeBody
  353. let methodParameters = self.makeParameters(
  354. for: method,
  355. in: service,
  356. from: codeGenerationRequest,
  357. // The serializer/deserializer for the protocol extension method will be auto-generated.
  358. includeSerializationParameters: !isProtocolExtension,
  359. includeDefaultCallOptions: includeDefaultCallOptions,
  360. includeDefaultResponseHandler: isProtocolExtension && !method.isOutputStreaming
  361. )
  362. let functionSignature = FunctionSignatureDescription(
  363. accessModifier: accessModifier,
  364. kind: .function(
  365. name: method.name.generatedLowerCase,
  366. isStatic: false
  367. ),
  368. generics: [.member("R")],
  369. parameters: methodParameters,
  370. keywords: [.async, .throws],
  371. returnType: .identifierType(.member("R")),
  372. whereClause: WhereClause(requirements: [.conformance("R", "Sendable")])
  373. )
  374. if includeBody {
  375. let body = self.makeClientProtocolMethodCall(
  376. for: method,
  377. in: service,
  378. from: codeGenerationRequest
  379. )
  380. return .function(signature: functionSignature, body: body)
  381. } else {
  382. return .commentable(
  383. .preFormatted(method.documentation),
  384. .function(signature: functionSignature)
  385. )
  386. }
  387. }
  388. private func makeClientProtocolMethodCall(
  389. for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor,
  390. in service: CodeGenerationRequest.ServiceDescriptor,
  391. from codeGenerationRequest: CodeGenerationRequest
  392. ) -> [CodeBlock] {
  393. let functionCall = Expression.functionCall(
  394. calledExpression: .memberAccess(
  395. MemberAccessDescription(
  396. left: .identifierPattern("self"),
  397. right: method.name.generatedLowerCase
  398. )
  399. ),
  400. arguments: [
  401. FunctionArgumentDescription(label: "request", expression: .identifierPattern("request")),
  402. FunctionArgumentDescription(
  403. label: "serializer",
  404. expression: .identifierPattern(codeGenerationRequest.lookupSerializer(method.inputType))
  405. ),
  406. FunctionArgumentDescription(
  407. label: "deserializer",
  408. expression: .identifierPattern(
  409. codeGenerationRequest.lookupDeserializer(method.outputType)
  410. )
  411. ),
  412. FunctionArgumentDescription(label: "options", expression: .identifierPattern("options")),
  413. FunctionArgumentDescription(expression: .identifierPattern("body")),
  414. ]
  415. )
  416. let awaitFunctionCall = Expression.unaryKeyword(kind: .await, expression: functionCall)
  417. let tryAwaitFunctionCall = Expression.unaryKeyword(kind: .try, expression: awaitFunctionCall)
  418. return [CodeBlock(item: .expression(tryAwaitFunctionCall))]
  419. }
  420. private func makeParameters(
  421. for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor,
  422. in service: CodeGenerationRequest.ServiceDescriptor,
  423. from codeGenerationRequest: CodeGenerationRequest,
  424. includeSerializationParameters: Bool,
  425. includeDefaultCallOptions: Bool,
  426. includeDefaultResponseHandler: Bool
  427. ) -> [ParameterDescription] {
  428. var parameters = [ParameterDescription]()
  429. parameters.append(self.clientRequestParameter(for: method, in: service))
  430. if includeSerializationParameters {
  431. parameters.append(self.serializerParameter(for: method, in: service))
  432. parameters.append(self.deserializerParameter(for: method, in: service))
  433. }
  434. parameters.append(
  435. ParameterDescription(
  436. label: "options",
  437. type: .member(["GRPCCore", "CallOptions"]),
  438. defaultValue: includeDefaultCallOptions
  439. ? .memberAccess(MemberAccessDescription(right: "defaults")) : nil
  440. )
  441. )
  442. parameters.append(
  443. self.bodyParameter(
  444. for: method,
  445. in: service,
  446. includeDefaultResponseHandler: includeDefaultResponseHandler
  447. )
  448. )
  449. return parameters
  450. }
  451. private func clientRequestParameter(
  452. for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor,
  453. in service: CodeGenerationRequest.ServiceDescriptor
  454. ) -> ParameterDescription {
  455. let requestType = method.isInputStreaming ? "Stream" : "Single"
  456. let clientRequestType = ExistingTypeDescription.member([
  457. "GRPCCore", "ClientRequest", requestType,
  458. ])
  459. return ParameterDescription(
  460. label: "request",
  461. type: .generic(
  462. wrapper: clientRequestType,
  463. wrapped: .member(method.inputType)
  464. )
  465. )
  466. }
  467. private func serializerParameter(
  468. for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor,
  469. in service: CodeGenerationRequest.ServiceDescriptor
  470. ) -> ParameterDescription {
  471. return ParameterDescription(
  472. label: "serializer",
  473. type: ExistingTypeDescription.some(
  474. .generic(
  475. wrapper: .member(["GRPCCore", "MessageSerializer"]),
  476. wrapped: .member(method.inputType)
  477. )
  478. )
  479. )
  480. }
  481. private func deserializerParameter(
  482. for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor,
  483. in service: CodeGenerationRequest.ServiceDescriptor
  484. ) -> ParameterDescription {
  485. return ParameterDescription(
  486. label: "deserializer",
  487. type: ExistingTypeDescription.some(
  488. .generic(
  489. wrapper: .member(["GRPCCore", "MessageDeserializer"]),
  490. wrapped: .member(method.outputType)
  491. )
  492. )
  493. )
  494. }
  495. private func bodyParameter(
  496. for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor,
  497. in service: CodeGenerationRequest.ServiceDescriptor,
  498. includeDefaultResponseHandler: Bool
  499. ) -> ParameterDescription {
  500. let clientStreaming = method.isOutputStreaming ? "Stream" : "Single"
  501. let closureParameterType = ExistingTypeDescription.generic(
  502. wrapper: .member(["GRPCCore", "ClientResponse", clientStreaming]),
  503. wrapped: .member(method.outputType)
  504. )
  505. let bodyClosure = ClosureSignatureDescription(
  506. parameters: [.init(type: closureParameterType)],
  507. keywords: [.async, .throws],
  508. returnType: .identifierType(.member("R")),
  509. sendable: true,
  510. escaping: true
  511. )
  512. var defaultResponseHandler: Expression? = nil
  513. if includeDefaultResponseHandler {
  514. defaultResponseHandler = .closureInvocation(
  515. ClosureInvocationDescription(
  516. body: [.expression(.try(.identifierPattern("$0").dot("message")))]
  517. )
  518. )
  519. }
  520. return ParameterDescription(
  521. name: "body",
  522. type: .closure(bodyClosure),
  523. defaultValue: defaultResponseHandler
  524. )
  525. }
  526. private func makeClientStruct(
  527. for service: CodeGenerationRequest.ServiceDescriptor,
  528. in codeGenerationRequest: CodeGenerationRequest
  529. ) -> Declaration {
  530. let clientProperty = Declaration.variable(
  531. accessModifier: .private,
  532. kind: .let,
  533. left: "client",
  534. type: .member(["GRPCCore", "GRPCClient"])
  535. )
  536. let initializer = self.makeClientVariable()
  537. let methods = service.methods.map {
  538. Declaration.commentable(
  539. .preFormatted($0.documentation),
  540. self.makeClientMethod(for: $0, in: service, from: codeGenerationRequest)
  541. )
  542. }
  543. return .guarded(
  544. self.availabilityGuard,
  545. .struct(
  546. StructDescription(
  547. accessModifier: self.accessModifier,
  548. name: "\(service.namespacedGeneratedName)Client",
  549. conformances: ["\(service.namespacedGeneratedName).ClientProtocol"],
  550. members: [clientProperty, initializer] + methods
  551. )
  552. )
  553. )
  554. }
  555. private func makeClientVariable() -> Declaration {
  556. let initializerBody = Expression.assignment(
  557. left: .memberAccess(
  558. MemberAccessDescription(left: .identifierPattern("self"), right: "client")
  559. ),
  560. right: .identifierPattern("client")
  561. )
  562. return .function(
  563. signature: .init(
  564. accessModifier: self.accessModifier,
  565. kind: .initializer,
  566. parameters: [
  567. .init(label: "wrapping", name: "client", type: .member(["GRPCCore", "GRPCClient"]))
  568. ]
  569. ),
  570. body: [CodeBlock(item: .expression(initializerBody))]
  571. )
  572. }
  573. private func clientMethod(
  574. isInputStreaming: Bool,
  575. isOutputStreaming: Bool
  576. ) -> String {
  577. switch (isInputStreaming, isOutputStreaming) {
  578. case (true, true):
  579. return "bidirectionalStreaming"
  580. case (true, false):
  581. return "clientStreaming"
  582. case (false, true):
  583. return "serverStreaming"
  584. case (false, false):
  585. return "unary"
  586. }
  587. }
  588. private func makeClientMethod(
  589. for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor,
  590. in service: CodeGenerationRequest.ServiceDescriptor,
  591. from codeGenerationRequest: CodeGenerationRequest
  592. ) -> Declaration {
  593. let parameters = self.makeParameters(
  594. for: method,
  595. in: service,
  596. from: codeGenerationRequest,
  597. includeSerializationParameters: true,
  598. includeDefaultCallOptions: true,
  599. includeDefaultResponseHandler: !method.isOutputStreaming
  600. )
  601. let grpcMethodName = self.clientMethod(
  602. isInputStreaming: method.isInputStreaming,
  603. isOutputStreaming: method.isOutputStreaming
  604. )
  605. let functionCall = Expression.functionCall(
  606. calledExpression: .memberAccess(
  607. MemberAccessDescription(left: .identifierPattern("self.client"), right: "\(grpcMethodName)")
  608. ),
  609. arguments: [
  610. .init(label: "request", expression: .identifierPattern("request")),
  611. .init(
  612. label: "descriptor",
  613. expression: .identifierPattern(
  614. "\(service.namespacedGeneratedName).Method.\(method.name.generatedUpperCase).descriptor"
  615. )
  616. ),
  617. .init(label: "serializer", expression: .identifierPattern("serializer")),
  618. .init(label: "deserializer", expression: .identifierPattern("deserializer")),
  619. .init(label: "options", expression: .identifierPattern("options")),
  620. .init(label: "handler", expression: .identifierPattern("body")),
  621. ]
  622. )
  623. let body = UnaryKeywordDescription(
  624. kind: .try,
  625. expression: .unaryKeyword(kind: .await, expression: functionCall)
  626. )
  627. return .function(
  628. accessModifier: self.accessModifier,
  629. kind: .function(
  630. name: "\(method.name.generatedLowerCase)",
  631. isStatic: false
  632. ),
  633. generics: [.member("R")],
  634. parameters: parameters,
  635. keywords: [.async, .throws],
  636. returnType: .identifierType(.member("R")),
  637. whereClause: WhereClause(requirements: [.conformance("R", "Sendable")]),
  638. body: [.expression(.unaryKeyword(body))]
  639. )
  640. }
  641. fileprivate enum InputOutputType {
  642. case input
  643. case output
  644. }
  645. }