ClientCodeTranslator.swift 16 KB

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