ClientCodeTranslator.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  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", the ``ClientCodeTranslator`` will create
  21. /// a representation for the following generated code:
  22. ///
  23. /// ```swift
  24. /// public protocol foo_BarClientProtocol: Sendable {
  25. /// func baz<R: Sendable>(
  26. /// request: ClientRequest.Single<foo.Bar.Methods.baz.Input>,
  27. /// serializer: some MessageSerializer<foo.Bar.Methods.baz.Input>,
  28. /// deserializer: some MessageDeserializer<foo.Bar.Methods.baz.Output>,
  29. /// _ body: @Sendable @escaping (ClientResponse.Single<foo.Bar.Methods.baz.Output>) async throws -> R
  30. /// ) async throws -> ServerResponse.Stream<foo.Bar.Methods.bazOutput>
  31. /// }
  32. /// extension foo.Bar.ClientProtocol {
  33. /// public func get<R: Sendable>(
  34. /// request: ClientRequest.Single<foo.Bar.Methods.baz.Input>,
  35. /// _ body: @Sendable @escaping (ClientResponse.Single<foo.Bar.Methods.baz.Output>) async throws -> R
  36. /// ) async rethrows -> R {
  37. /// try await self.baz(
  38. /// request: request,
  39. /// serializer: ProtobufSerializer<foo.Bar.Methods.baz.Input>(),
  40. /// deserializer: ProtobufDeserializer<foo.Bar.Methods.baz.Output>(),
  41. /// body
  42. /// )
  43. /// }
  44. /// struct foo_BarClient: foo.Bar.ClientProtocol {
  45. /// let client: GRPCCore.GRPCClient
  46. /// init(client: GRPCCore.GRPCClient) {
  47. /// self.client = client
  48. /// }
  49. /// func methodA<R: Sendable>(
  50. /// request: ClientRequest.Stream<namespaceA.ServiceA.Methods.methodA.Input>,
  51. /// serializer: some MessageSerializer<namespaceA.ServiceA.Methods.methodA.Input>,
  52. /// deserializer: some MessageDeserializer<namespaceA.ServiceA.Methods.methodA.Output>,
  53. /// _ body: @Sendable @escaping (ClientResponse.Single<namespaceA.ServiceA.Methods.methodA.Output>) async throws -> R
  54. /// ) async rethrows -> R {
  55. /// try await self.client.clientStreaming(
  56. /// request: request,
  57. /// descriptor: namespaceA.ServiceA.Methods.methodA.descriptor,
  58. /// serializer: serializer,
  59. /// deserializer: deserializer,
  60. /// handler: body
  61. /// )
  62. /// }
  63. /// }
  64. ///```
  65. struct ClientCodeTranslator: SpecializedTranslator {
  66. func translate(from codeGenerationRequest: CodeGenerationRequest) throws -> [CodeBlock] {
  67. var codeBlocks = [CodeBlock]()
  68. for service in codeGenerationRequest.services {
  69. codeBlocks.append(
  70. .declaration(
  71. .commentable(
  72. .doc(service.documentation),
  73. self.makeClientProtocol(for: service, in: codeGenerationRequest)
  74. )
  75. )
  76. )
  77. codeBlocks.append(
  78. .declaration(self.makeExtensionProtocol(for: service, in: codeGenerationRequest))
  79. )
  80. codeBlocks.append(
  81. .declaration(
  82. .commentable(
  83. .doc(service.documentation),
  84. self.makeClientStruct(for: service, in: codeGenerationRequest)
  85. )
  86. )
  87. )
  88. }
  89. return codeBlocks
  90. }
  91. }
  92. extension ClientCodeTranslator {
  93. private func makeClientProtocol(
  94. for service: CodeGenerationRequest.ServiceDescriptor,
  95. in codeGenerationRequest: CodeGenerationRequest
  96. ) -> Declaration {
  97. let methods = service.methods.map {
  98. self.makeClientProtocolMethod(
  99. for: $0,
  100. in: service,
  101. from: codeGenerationRequest,
  102. generateSerializerDeserializer: false
  103. )
  104. }
  105. let clientProtocol = Declaration.protocol(
  106. ProtocolDescription(
  107. name: "\(service.namespacedPrefix)ClientProtocol",
  108. conformances: ["Sendable"],
  109. members: methods
  110. )
  111. )
  112. return clientProtocol
  113. }
  114. private func makeExtensionProtocol(
  115. for service: CodeGenerationRequest.ServiceDescriptor,
  116. in codeGenerationRequest: CodeGenerationRequest
  117. ) -> Declaration {
  118. let methods = service.methods.map {
  119. self.makeClientProtocolMethod(
  120. for: $0,
  121. in: service,
  122. from: codeGenerationRequest,
  123. generateSerializerDeserializer: true
  124. )
  125. }
  126. let clientProtocolExtension = Declaration.extension(
  127. ExtensionDescription(
  128. onType: "\(service.namespacedTypealiasPrefix).ClientProtocol",
  129. declarations: methods
  130. )
  131. )
  132. return clientProtocolExtension
  133. }
  134. private func makeClientProtocolMethod(
  135. for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor,
  136. in service: CodeGenerationRequest.ServiceDescriptor,
  137. from codeGenerationRequest: CodeGenerationRequest,
  138. generateSerializerDeserializer: Bool
  139. ) -> Declaration {
  140. let methodParameters = self.makeParameters(
  141. for: method,
  142. in: service,
  143. from: codeGenerationRequest,
  144. generateSerializerDeserializer: generateSerializerDeserializer
  145. )
  146. let functionSignature = FunctionSignatureDescription(
  147. kind: .function(
  148. name: method.name,
  149. isStatic: false
  150. ),
  151. generics: [.member("R")],
  152. parameters: methodParameters,
  153. keywords: [.async, .throws],
  154. returnType: .identifierType(.member("R")),
  155. whereClause: WhereClause(requirements: [.conformance("R", "Sendable")])
  156. )
  157. if generateSerializerDeserializer {
  158. let body = self.makeSerializerDeserializerCall(
  159. for: method,
  160. in: service,
  161. from: codeGenerationRequest
  162. )
  163. return .function(signature: functionSignature, body: body)
  164. } else {
  165. return .commentable(.doc(method.documentation), .function(signature: functionSignature))
  166. }
  167. }
  168. private func makeSerializerDeserializerCall(
  169. for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor,
  170. in service: CodeGenerationRequest.ServiceDescriptor,
  171. from codeGenerationRequest: CodeGenerationRequest
  172. ) -> [CodeBlock] {
  173. let functionCall = Expression.functionCall(
  174. calledExpression: .memberAccess(
  175. MemberAccessDescription(left: .identifierPattern("self"), right: method.name)
  176. ),
  177. arguments: [
  178. FunctionArgumentDescription(label: "request", expression: .identifierPattern("request")),
  179. FunctionArgumentDescription(
  180. label: "serializer",
  181. expression: .identifierPattern(
  182. codeGenerationRequest.lookupSerializer(
  183. self.methodInputOutputTypealias(for: method, service: service, type: .input)
  184. )
  185. )
  186. ),
  187. FunctionArgumentDescription(
  188. label: "deserializer",
  189. expression: .identifierPattern(
  190. codeGenerationRequest.lookupDeserializer(
  191. self.methodInputOutputTypealias(for: method, service: service, type: .output)
  192. )
  193. )
  194. ),
  195. FunctionArgumentDescription(expression: .identifierPattern("body")),
  196. ]
  197. )
  198. let awaitFunctionCall = Expression.unaryKeyword(kind: .await, expression: functionCall)
  199. let tryAwaitFunctionCall = Expression.unaryKeyword(kind: .try, expression: awaitFunctionCall)
  200. return [CodeBlock(item: .expression(tryAwaitFunctionCall))]
  201. }
  202. private func makeParameters(
  203. for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor,
  204. in service: CodeGenerationRequest.ServiceDescriptor,
  205. from codeGenerationRequest: CodeGenerationRequest,
  206. generateSerializerDeserializer: Bool
  207. ) -> [ParameterDescription] {
  208. var parameters = [ParameterDescription]()
  209. parameters.append(self.clientRequestParameter(for: method, in: service))
  210. if !generateSerializerDeserializer {
  211. parameters.append(self.serializerParameter(for: method, in: service))
  212. parameters.append(self.deserializerParameter(for: method, in: service))
  213. }
  214. parameters.append(self.bodyParameter(for: method, in: service))
  215. return parameters
  216. }
  217. private func clientRequestParameter(
  218. for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor,
  219. in service: CodeGenerationRequest.ServiceDescriptor
  220. ) -> ParameterDescription {
  221. let requestType = method.isInputStreaming ? "Stream" : "Single"
  222. let clientRequestType = ExistingTypeDescription.member(["ClientRequest", requestType])
  223. return ParameterDescription(
  224. label: "request",
  225. type: .generic(
  226. wrapper: clientRequestType,
  227. wrapped: .member(
  228. self.methodInputOutputTypealias(for: method, service: service, type: .input)
  229. )
  230. )
  231. )
  232. }
  233. private func serializerParameter(
  234. for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor,
  235. in service: CodeGenerationRequest.ServiceDescriptor
  236. ) -> ParameterDescription {
  237. return ParameterDescription(
  238. label: "serializer",
  239. type: ExistingTypeDescription.some(
  240. .generic(
  241. wrapper: .member("MessageSerializer"),
  242. wrapped: .member(
  243. self.methodInputOutputTypealias(for: method, service: service, type: .input)
  244. )
  245. )
  246. )
  247. )
  248. }
  249. private func deserializerParameter(
  250. for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor,
  251. in service: CodeGenerationRequest.ServiceDescriptor
  252. ) -> ParameterDescription {
  253. return ParameterDescription(
  254. label: "deserializer",
  255. type: ExistingTypeDescription.some(
  256. .generic(
  257. wrapper: .member("MessageDeserializer"),
  258. wrapped: .member(
  259. self.methodInputOutputTypealias(for: method, service: service, type: .output)
  260. )
  261. )
  262. )
  263. )
  264. }
  265. private func bodyParameter(
  266. for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor,
  267. in service: CodeGenerationRequest.ServiceDescriptor
  268. ) -> ParameterDescription {
  269. let clientStreaming = method.isOutputStreaming ? "Stream" : "Single"
  270. let closureParameterType = ExistingTypeDescription.generic(
  271. wrapper: .member(["ClientResponse", clientStreaming]),
  272. wrapped: .member(
  273. self.methodInputOutputTypealias(for: method, service: service, type: .output)
  274. )
  275. )
  276. let bodyClosure = ClosureSignatureDescription(
  277. parameters: [.init(type: closureParameterType)],
  278. keywords: [.async, .throws],
  279. returnType: .identifierType(.member("R")),
  280. sendable: true,
  281. escaping: true
  282. )
  283. return ParameterDescription(name: "body", type: .closure(bodyClosure))
  284. }
  285. private func makeClientStruct(
  286. for service: CodeGenerationRequest.ServiceDescriptor,
  287. in codeGenerationRequest: CodeGenerationRequest
  288. ) -> Declaration {
  289. let clientProperty = Declaration.variable(
  290. kind: .let,
  291. left: "client",
  292. type: .member(["GRPCCore", "GRPCClient"])
  293. )
  294. let initializer = self.makeClientVariable()
  295. let methods = service.methods.map {
  296. Declaration.commentable(
  297. .doc($0.documentation),
  298. self.makeClientMethod(for: $0, in: service, from: codeGenerationRequest)
  299. )
  300. }
  301. return .struct(
  302. StructDescription(
  303. name: "\(service.namespacedPrefix)Client",
  304. conformances: ["\(service.namespacedTypealiasPrefix).ClientProtocol"],
  305. members: [clientProperty, initializer] + methods
  306. )
  307. )
  308. }
  309. private func makeClientVariable() -> Declaration {
  310. let initializerBody = Expression.assignment(
  311. left: .memberAccess(
  312. MemberAccessDescription(left: .identifierPattern("self"), right: "client")
  313. ),
  314. right: .identifierPattern("client")
  315. )
  316. return .function(
  317. signature: .init(
  318. kind: .initializer,
  319. parameters: [.init(label: "client", type: .member(["GRPCCore", "GRPCClient"]))]
  320. ),
  321. body: [CodeBlock(item: .expression(initializerBody))]
  322. )
  323. }
  324. private func clientMethod(
  325. isInputStreaming: Bool,
  326. isOutputStreaming: Bool
  327. ) -> String {
  328. switch (isInputStreaming, isOutputStreaming) {
  329. case (true, true):
  330. return "bidirectionalStreaming"
  331. case (true, false):
  332. return "clientStreaming"
  333. case (false, true):
  334. return "serverStreaming"
  335. case (false, false):
  336. return "unary"
  337. }
  338. }
  339. private func makeClientMethod(
  340. for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor,
  341. in service: CodeGenerationRequest.ServiceDescriptor,
  342. from codeGenerationRequest: CodeGenerationRequest
  343. ) -> Declaration {
  344. let parameters = self.makeParameters(
  345. for: method,
  346. in: service,
  347. from: codeGenerationRequest,
  348. generateSerializerDeserializer: false
  349. )
  350. let grpcMethodName = self.clientMethod(
  351. isInputStreaming: method.isInputStreaming,
  352. isOutputStreaming: method.isOutputStreaming
  353. )
  354. let functionCall = Expression.functionCall(
  355. calledExpression: .memberAccess(
  356. MemberAccessDescription(left: .identifierPattern("self.client"), right: "\(grpcMethodName)")
  357. ),
  358. arguments: [
  359. .init(label: "request", expression: .identifierPattern("request")),
  360. .init(
  361. label: "descriptor",
  362. expression: .identifierPattern(
  363. "\(service.namespacedTypealiasPrefix).Methods.\(method.name).descriptor"
  364. )
  365. ),
  366. .init(label: "serializer", expression: .identifierPattern("serializer")),
  367. .init(label: "deserializer", expression: .identifierPattern("deserializer")),
  368. .init(label: "handler", expression: .identifierPattern("body")),
  369. ]
  370. )
  371. let body = UnaryKeywordDescription(
  372. kind: .try,
  373. expression: .unaryKeyword(kind: .await, expression: functionCall)
  374. )
  375. return .function(
  376. kind: .function(
  377. name: "\(method.name)",
  378. isStatic: false
  379. ),
  380. generics: [.member("R")],
  381. parameters: parameters,
  382. keywords: [.async, .throws],
  383. returnType: .identifierType(.member("R")),
  384. whereClause: WhereClause(requirements: [.conformance("R", "Sendable")]),
  385. body: [.expression(.unaryKeyword(body))]
  386. )
  387. }
  388. fileprivate enum InputOutputType {
  389. case input
  390. case output
  391. }
  392. /// Generates the fully qualified name of the typealias for the input or output type of a method.
  393. private func methodInputOutputTypealias(
  394. for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor,
  395. service: CodeGenerationRequest.ServiceDescriptor,
  396. type: InputOutputType
  397. ) -> String {
  398. var components: String = "\(service.namespacedTypealiasPrefix).Methods.\(method.name)"
  399. switch type {
  400. case .input:
  401. components.append(".Input")
  402. case .output:
  403. components.append(".Output")
  404. }
  405. return components
  406. }
  407. }