StructuredSwift+ClientTests.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  1. /*
  2. * Copyright 2024, 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. extension StructuedSwiftTests {
  19. @Suite("Client")
  20. struct Client {
  21. @Test(
  22. "func <Method>(request:serializer:deserializer:options:onResponse:)",
  23. arguments: AccessModifier.allCases,
  24. RPCKind.allCases
  25. )
  26. func clientMethodSignature(access: AccessModifier, kind: RPCKind) {
  27. let decl: FunctionSignatureDescription = .clientMethod(
  28. accessLevel: access,
  29. name: "foo",
  30. input: "FooInput",
  31. output: "FooOutput",
  32. streamingInput: kind.streamsInput,
  33. streamingOutput: kind.streamsOutput,
  34. includeDefaults: false,
  35. includeSerializers: true
  36. )
  37. let requestType = kind.streamsInput ? "StreamingClientRequest" : "ClientRequest"
  38. let responseType = kind.streamsOutput ? "StreamingClientResponse" : "ClientResponse"
  39. let expected = """
  40. \(access) func foo<Result>(
  41. request: GRPCCore.\(requestType)<FooInput>,
  42. serializer: some GRPCCore.MessageSerializer<FooInput>,
  43. deserializer: some GRPCCore.MessageDeserializer<FooOutput>,
  44. options: GRPCCore.CallOptions,
  45. onResponse handleResponse: @Sendable @escaping (GRPCCore.\(responseType)<FooOutput>) async throws -> Result
  46. ) async throws -> Result where Result: Sendable
  47. """
  48. #expect(render(.function(signature: decl)) == expected)
  49. }
  50. @Test(
  51. "func <Method>(request:serializer:deserializer:options:onResponse:) (with defaults)",
  52. arguments: AccessModifier.allCases,
  53. [true, false]
  54. )
  55. func clientMethodSignatureWithDefaults(access: AccessModifier, streamsOutput: Bool) {
  56. let decl: FunctionSignatureDescription = .clientMethod(
  57. accessLevel: access,
  58. name: "foo",
  59. input: "FooInput",
  60. output: "FooOutput",
  61. streamingInput: false,
  62. streamingOutput: streamsOutput,
  63. includeDefaults: true,
  64. includeSerializers: false
  65. )
  66. let expected: String
  67. if streamsOutput {
  68. expected = """
  69. \(access) func foo<Result>(
  70. request: GRPCCore.ClientRequest<FooInput>,
  71. options: GRPCCore.CallOptions = .defaults,
  72. onResponse handleResponse: @Sendable @escaping (GRPCCore.StreamingClientResponse<FooOutput>) async throws -> Result
  73. ) async throws -> Result where Result: Sendable
  74. """
  75. } else {
  76. expected = """
  77. \(access) func foo<Result>(
  78. request: GRPCCore.ClientRequest<FooInput>,
  79. options: GRPCCore.CallOptions = .defaults,
  80. onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse<FooOutput>) async throws -> Result = { response in
  81. try response.message
  82. }
  83. ) async throws -> Result where Result: Sendable
  84. """
  85. }
  86. #expect(render(.function(signature: decl)) == expected)
  87. }
  88. @Test("protocol Foo_ClientProtocol: Sendable { ... }", arguments: AccessModifier.allCases)
  89. func clientProtocol(access: AccessModifier) {
  90. let decl: ProtocolDescription = .clientProtocol(
  91. accessLevel: access,
  92. name: "Foo_ClientProtocol",
  93. methods: [
  94. .init(
  95. documentation: "/// Some docs",
  96. name: .init(base: "Bar", generatedUpperCase: "Bar", generatedLowerCase: "bar"),
  97. isInputStreaming: false,
  98. isOutputStreaming: false,
  99. inputType: "BarInput",
  100. outputType: "BarOutput"
  101. )
  102. ]
  103. )
  104. let expected = """
  105. \(access) protocol Foo_ClientProtocol: Sendable {
  106. /// Some docs
  107. func bar<Result>(
  108. request: GRPCCore.ClientRequest<BarInput>,
  109. serializer: some GRPCCore.MessageSerializer<BarInput>,
  110. deserializer: some GRPCCore.MessageDeserializer<BarOutput>,
  111. options: GRPCCore.CallOptions,
  112. onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse<BarOutput>) async throws -> Result
  113. ) async throws -> Result where Result: Sendable
  114. }
  115. """
  116. #expect(render(.protocol(decl)) == expected)
  117. }
  118. @Test("func foo(...) { try await self.foo(...) }", arguments: AccessModifier.allCases)
  119. func clientMethodFunctionWithDefaults(access: AccessModifier) {
  120. let decl: FunctionDescription = .clientMethodWithDefaults(
  121. accessLevel: access,
  122. name: "foo",
  123. input: "FooInput",
  124. output: "FooOutput",
  125. streamingInput: false,
  126. streamingOutput: false,
  127. serializer: .identifierPattern("Serialize<FooInput>()"),
  128. deserializer: .identifierPattern("Deserialize<FooOutput>()")
  129. )
  130. let expected = """
  131. \(access) func foo<Result>(
  132. request: GRPCCore.ClientRequest<FooInput>,
  133. options: GRPCCore.CallOptions = .defaults,
  134. onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse<FooOutput>) async throws -> Result = { response in
  135. try response.message
  136. }
  137. ) async throws -> Result where Result: Sendable {
  138. try await self.foo(
  139. request: request,
  140. serializer: Serialize<FooInput>(),
  141. deserializer: Deserialize<FooOutput>(),
  142. options: options,
  143. onResponse: handleResponse
  144. )
  145. }
  146. """
  147. #expect(render(.function(decl)) == expected)
  148. }
  149. @Test(
  150. "extension Foo_ClientProtocol { ... } (methods with defaults)",
  151. arguments: AccessModifier.allCases
  152. )
  153. func extensionWithDefaultClientMethods(access: AccessModifier) {
  154. let decl: ExtensionDescription = .clientMethodSignatureWithDefaults(
  155. accessLevel: access,
  156. name: "Foo_ClientProtocol",
  157. methods: [
  158. MethodDescriptor(
  159. documentation: "",
  160. name: .init(base: "Bar", generatedUpperCase: "Bar", generatedLowerCase: "bar"),
  161. isInputStreaming: false,
  162. isOutputStreaming: false,
  163. inputType: "BarInput",
  164. outputType: "BarOutput"
  165. )
  166. ],
  167. serializer: { "Serialize<\($0)>()" },
  168. deserializer: { "Deserialize<\($0)>()" }
  169. )
  170. let expected = """
  171. extension Foo_ClientProtocol {
  172. \(access) func bar<Result>(
  173. request: GRPCCore.ClientRequest<BarInput>,
  174. options: GRPCCore.CallOptions = .defaults,
  175. onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse<BarOutput>) async throws -> Result = { response in
  176. try response.message
  177. }
  178. ) async throws -> Result where Result: Sendable {
  179. try await self.bar(
  180. request: request,
  181. serializer: Serialize<BarInput>(),
  182. deserializer: Deserialize<BarOutput>(),
  183. options: options,
  184. onResponse: handleResponse
  185. )
  186. }
  187. }
  188. """
  189. #expect(render(.extension(decl)) == expected)
  190. }
  191. @Test(
  192. "func foo<Result>(_:metadata:options:onResponse:) -> Result (exploded signature)",
  193. arguments: AccessModifier.allCases,
  194. RPCKind.allCases
  195. )
  196. func explodedClientMethodSignature(access: AccessModifier, kind: RPCKind) {
  197. let decl: FunctionSignatureDescription = .clientMethodExploded(
  198. accessLevel: access,
  199. name: "foo",
  200. input: "Input",
  201. output: "Output",
  202. streamingInput: kind.streamsInput,
  203. streamingOutput: kind.streamsOutput
  204. )
  205. let expected: String
  206. switch kind {
  207. case .unary:
  208. expected = """
  209. \(access) func foo<Result>(
  210. _ message: Input,
  211. metadata: GRPCCore.Metadata = [:],
  212. options: GRPCCore.CallOptions = .defaults,
  213. onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse<Output>) async throws -> Result = { response in
  214. try response.message
  215. }
  216. ) async throws -> Result where Result: Sendable
  217. """
  218. case .clientStreaming:
  219. expected = """
  220. \(access) func foo<Result>(
  221. metadata: GRPCCore.Metadata = [:],
  222. options: GRPCCore.CallOptions = .defaults,
  223. requestProducer producer: @Sendable @escaping (GRPCCore.RPCWriter<Input>) async throws -> Void,
  224. onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse<Output>) async throws -> Result = { response in
  225. try response.message
  226. }
  227. ) async throws -> Result where Result: Sendable
  228. """
  229. case .serverStreaming:
  230. expected = """
  231. \(access) func foo<Result>(
  232. _ message: Input,
  233. metadata: GRPCCore.Metadata = [:],
  234. options: GRPCCore.CallOptions = .defaults,
  235. onResponse handleResponse: @Sendable @escaping (GRPCCore.StreamingClientResponse<Output>) async throws -> Result
  236. ) async throws -> Result where Result: Sendable
  237. """
  238. case .bidirectionalStreaming:
  239. expected = """
  240. \(access) func foo<Result>(
  241. metadata: GRPCCore.Metadata = [:],
  242. options: GRPCCore.CallOptions = .defaults,
  243. requestProducer producer: @Sendable @escaping (GRPCCore.RPCWriter<Input>) async throws -> Void,
  244. onResponse handleResponse: @Sendable @escaping (GRPCCore.StreamingClientResponse<Output>) async throws -> Result
  245. ) async throws -> Result where Result: Sendable
  246. """
  247. }
  248. #expect(render(.function(signature: decl)) == expected)
  249. }
  250. @Test(
  251. "func foo<Result>(_:metadata:options:onResponse:) -> Result (exploded body)",
  252. arguments: [true, false]
  253. )
  254. func explodedClientMethodBody(streamingInput: Bool) {
  255. let blocks: [CodeBlock] = .clientMethodExploded(
  256. name: "foo",
  257. input: "Input",
  258. streamingInput: streamingInput
  259. )
  260. let expected: String
  261. if streamingInput {
  262. expected = """
  263. let request = GRPCCore.StreamingClientRequest<Input>(
  264. metadata: metadata,
  265. producer: producer
  266. )
  267. return try await self.foo(
  268. request: request,
  269. options: options,
  270. onResponse: handleResponse
  271. )
  272. """
  273. } else {
  274. expected = """
  275. let request = GRPCCore.ClientRequest<Input>(
  276. message: message,
  277. metadata: metadata
  278. )
  279. return try await self.foo(
  280. request: request,
  281. options: options,
  282. onResponse: handleResponse
  283. )
  284. """
  285. }
  286. #expect(render(blocks) == expected)
  287. }
  288. @Test("extension Foo_ClientProtocol { ... } (exploded)", arguments: AccessModifier.allCases)
  289. func explodedClientMethodExtension(access: AccessModifier) {
  290. let decl: ExtensionDescription = .explodedClientMethods(
  291. accessLevel: access,
  292. on: "Foo_ClientProtocol",
  293. methods: [
  294. .init(
  295. documentation: "/// Some docs",
  296. name: .init(base: "Bar", generatedUpperCase: "Bar", generatedLowerCase: "bar"),
  297. isInputStreaming: false,
  298. isOutputStreaming: true,
  299. inputType: "Input",
  300. outputType: "Output"
  301. )
  302. ]
  303. )
  304. let expected = """
  305. extension Foo_ClientProtocol {
  306. /// Some docs
  307. \(access) func bar<Result>(
  308. _ message: Input,
  309. metadata: GRPCCore.Metadata = [:],
  310. options: GRPCCore.CallOptions = .defaults,
  311. onResponse handleResponse: @Sendable @escaping (GRPCCore.StreamingClientResponse<Output>) async throws -> Result
  312. ) async throws -> Result where Result: Sendable {
  313. let request = GRPCCore.ClientRequest<Input>(
  314. message: message,
  315. metadata: metadata
  316. )
  317. return try await self.bar(
  318. request: request,
  319. options: options,
  320. onResponse: handleResponse
  321. )
  322. }
  323. }
  324. """
  325. #expect(render(.extension(decl)) == expected)
  326. }
  327. @Test(
  328. "func foo(request:serializer:deserializer:options:onResponse:) (client method impl.)",
  329. arguments: AccessModifier.allCases
  330. )
  331. func clientMethodImplementation(access: AccessModifier) {
  332. let decl: FunctionDescription = .clientMethod(
  333. accessLevel: access,
  334. name: "foo",
  335. input: "Input",
  336. output: "Output",
  337. serviceEnum: "BarService",
  338. methodEnum: "Foo",
  339. streamingInput: false,
  340. streamingOutput: false
  341. )
  342. let expected = """
  343. \(access) func foo<Result>(
  344. request: GRPCCore.ClientRequest<Input>,
  345. serializer: some GRPCCore.MessageSerializer<Input>,
  346. deserializer: some GRPCCore.MessageDeserializer<Output>,
  347. options: GRPCCore.CallOptions = .defaults,
  348. onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse<Output>) async throws -> Result = { response in
  349. try response.message
  350. }
  351. ) async throws -> Result where Result: Sendable {
  352. try await self.client.unary(
  353. request: request,
  354. descriptor: BarService.Method.Foo.descriptor,
  355. serializer: serializer,
  356. deserializer: deserializer,
  357. options: options,
  358. onResponse: handleResponse
  359. )
  360. }
  361. """
  362. #expect(render(.function(decl)) == expected)
  363. }
  364. @Test("struct FooClient: Foo_ClientProtocol { ... }", arguments: AccessModifier.allCases)
  365. func client(access: AccessModifier) {
  366. let decl: StructDescription = .client(
  367. accessLevel: access,
  368. name: "FooClient",
  369. serviceEnum: "BarService",
  370. clientProtocol: "Foo_ClientProtocol",
  371. methods: [
  372. .init(
  373. documentation: "/// Unary docs",
  374. name: .init(
  375. base: "Unary",
  376. generatedUpperCase: "Unary",
  377. generatedLowerCase: "unary"
  378. ),
  379. isInputStreaming: false,
  380. isOutputStreaming: false,
  381. inputType: "Input",
  382. outputType: "Output"
  383. ),
  384. .init(
  385. documentation: "/// ClientStreaming docs",
  386. name: .init(
  387. base: "ClientStreaming",
  388. generatedUpperCase: "ClientStreaming",
  389. generatedLowerCase: "clientStreaming"
  390. ),
  391. isInputStreaming: true,
  392. isOutputStreaming: false,
  393. inputType: "Input",
  394. outputType: "Output"
  395. ),
  396. .init(
  397. documentation: "/// ServerStreaming docs",
  398. name: .init(
  399. base: "ServerStreaming",
  400. generatedUpperCase: "ServerStreaming",
  401. generatedLowerCase: "serverStreaming"
  402. ),
  403. isInputStreaming: false,
  404. isOutputStreaming: true,
  405. inputType: "Input",
  406. outputType: "Output"
  407. ),
  408. .init(
  409. documentation: "/// BidiStreaming docs",
  410. name: .init(
  411. base: "BidiStreaming",
  412. generatedUpperCase: "BidiStreaming",
  413. generatedLowerCase: "bidiStreaming"
  414. ),
  415. isInputStreaming: true,
  416. isOutputStreaming: true,
  417. inputType: "Input",
  418. outputType: "Output"
  419. ),
  420. ]
  421. )
  422. let expected = """
  423. \(access) struct FooClient: Foo_ClientProtocol {
  424. private let client: GRPCCore.GRPCClient
  425. \(access) init(wrapping client: GRPCCore.GRPCClient) {
  426. self.client = client
  427. }
  428. /// Unary docs
  429. \(access) func unary<Result>(
  430. request: GRPCCore.ClientRequest<Input>,
  431. serializer: some GRPCCore.MessageSerializer<Input>,
  432. deserializer: some GRPCCore.MessageDeserializer<Output>,
  433. options: GRPCCore.CallOptions = .defaults,
  434. onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse<Output>) async throws -> Result = { response in
  435. try response.message
  436. }
  437. ) async throws -> Result where Result: Sendable {
  438. try await self.client.unary(
  439. request: request,
  440. descriptor: BarService.Method.Unary.descriptor,
  441. serializer: serializer,
  442. deserializer: deserializer,
  443. options: options,
  444. onResponse: handleResponse
  445. )
  446. }
  447. /// ClientStreaming docs
  448. \(access) func clientStreaming<Result>(
  449. request: GRPCCore.StreamingClientRequest<Input>,
  450. serializer: some GRPCCore.MessageSerializer<Input>,
  451. deserializer: some GRPCCore.MessageDeserializer<Output>,
  452. options: GRPCCore.CallOptions = .defaults,
  453. onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse<Output>) async throws -> Result = { response in
  454. try response.message
  455. }
  456. ) async throws -> Result where Result: Sendable {
  457. try await self.client.clientStreaming(
  458. request: request,
  459. descriptor: BarService.Method.ClientStreaming.descriptor,
  460. serializer: serializer,
  461. deserializer: deserializer,
  462. options: options,
  463. onResponse: handleResponse
  464. )
  465. }
  466. /// ServerStreaming docs
  467. \(access) func serverStreaming<Result>(
  468. request: GRPCCore.ClientRequest<Input>,
  469. serializer: some GRPCCore.MessageSerializer<Input>,
  470. deserializer: some GRPCCore.MessageDeserializer<Output>,
  471. options: GRPCCore.CallOptions = .defaults,
  472. onResponse handleResponse: @Sendable @escaping (GRPCCore.StreamingClientResponse<Output>) async throws -> Result
  473. ) async throws -> Result where Result: Sendable {
  474. try await self.client.serverStreaming(
  475. request: request,
  476. descriptor: BarService.Method.ServerStreaming.descriptor,
  477. serializer: serializer,
  478. deserializer: deserializer,
  479. options: options,
  480. onResponse: handleResponse
  481. )
  482. }
  483. /// BidiStreaming docs
  484. \(access) func bidiStreaming<Result>(
  485. request: GRPCCore.StreamingClientRequest<Input>,
  486. serializer: some GRPCCore.MessageSerializer<Input>,
  487. deserializer: some GRPCCore.MessageDeserializer<Output>,
  488. options: GRPCCore.CallOptions = .defaults,
  489. onResponse handleResponse: @Sendable @escaping (GRPCCore.StreamingClientResponse<Output>) async throws -> Result
  490. ) async throws -> Result where Result: Sendable {
  491. try await self.client.bidirectionalStreaming(
  492. request: request,
  493. descriptor: BarService.Method.BidiStreaming.descriptor,
  494. serializer: serializer,
  495. deserializer: deserializer,
  496. options: options,
  497. onResponse: handleResponse
  498. )
  499. }
  500. }
  501. """
  502. #expect(render(.struct(decl)) == expected)
  503. }
  504. }
  505. }