IDLToStructuredSwiftTranslatorSnippetBasedTests.swift 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636
  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. #if os(macOS) || os(Linux) // swift-format doesn't like canImport(Foundation.Process)
  17. import XCTest
  18. @testable import GRPCCodeGen
  19. final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase {
  20. func testGeneration() throws {
  21. var dependencies = [Dependency]()
  22. dependencies.append(
  23. Dependency(module: "Foo", spi: "Secret", accessLevel: .internal)
  24. )
  25. dependencies.append(
  26. Dependency(
  27. item: .init(kind: .enum, name: "Bar"),
  28. module: "Foo",
  29. spi: "Secret",
  30. accessLevel: .internal
  31. )
  32. )
  33. let serviceA = ServiceDescriptor(
  34. documentation: "/// Documentation for AService\n",
  35. name: ServiceName(
  36. identifyingName: "namespaceA.ServiceA",
  37. typeName: "NamespaceA_ServiceA",
  38. propertyName: "namespaceA_ServiceA"
  39. ),
  40. methods: []
  41. )
  42. let expectedSwift =
  43. """
  44. /// Some really exciting license header 2023.
  45. public import GRPCCore
  46. @_spi(Secret) internal import Foo
  47. @_spi(Secret) internal import enum Foo.Bar
  48. // MARK: - namespaceA.ServiceA
  49. /// Namespace containing generated types for the "namespaceA.ServiceA" service.
  50. public enum NamespaceA_ServiceA {
  51. /// Service descriptor for the "namespaceA.ServiceA" service.
  52. public static let descriptor = GRPCCore.ServiceDescriptor(fullyQualifiedService: "namespaceA.ServiceA")
  53. /// Namespace for method metadata.
  54. public enum Method {
  55. /// Descriptors for all methods in the "namespaceA.ServiceA" service.
  56. public static let descriptors: [GRPCCore.MethodDescriptor] = []
  57. }
  58. }
  59. extension GRPCCore.ServiceDescriptor {
  60. /// Service descriptor for the "namespaceA.ServiceA" service.
  61. public static let namespaceA_ServiceA = GRPCCore.ServiceDescriptor(fullyQualifiedService: "namespaceA.ServiceA")
  62. }
  63. // MARK: namespaceA.ServiceA (server)
  64. extension NamespaceA_ServiceA {
  65. /// Streaming variant of the service protocol for the "namespaceA.ServiceA" service.
  66. ///
  67. /// This protocol is the lowest-level of the service protocols generated for this service
  68. /// giving you the most flexibility over the implementation of your service. This comes at
  69. /// the cost of more verbose and less strict APIs. Each RPC requires you to implement it in
  70. /// terms of a request stream and response stream. Where only a single request or response
  71. /// message is expected, you are responsible for enforcing this invariant is maintained.
  72. ///
  73. /// Where possible, prefer using the stricter, less-verbose ``ServiceProtocol``
  74. /// or ``SimpleServiceProtocol`` instead.
  75. ///
  76. /// > Source IDL Documentation:
  77. /// >
  78. /// > Documentation for AService
  79. public protocol StreamingServiceProtocol: GRPCCore.RegistrableRPCService {}
  80. /// Service protocol for the "namespaceA.ServiceA" service.
  81. ///
  82. /// This protocol is higher level than ``StreamingServiceProtocol`` but lower level than
  83. /// the ``SimpleServiceProtocol``, it provides access to request and response metadata and
  84. /// trailing response metadata. If you don't need these then consider using
  85. /// the ``SimpleServiceProtocol``. If you need fine grained control over your RPCs then
  86. /// use ``StreamingServiceProtocol``.
  87. ///
  88. /// > Source IDL Documentation:
  89. /// >
  90. /// > Documentation for AService
  91. public protocol ServiceProtocol: NamespaceA_ServiceA.StreamingServiceProtocol {}
  92. /// Simple service protocol for the "namespaceA.ServiceA" service.
  93. ///
  94. /// This is the highest level protocol for the service. The API is the easiest to use but
  95. /// doesn't provide access to request or response metadata. If you need access to these
  96. /// then use ``ServiceProtocol`` instead.
  97. ///
  98. /// > Source IDL Documentation:
  99. /// >
  100. /// > Documentation for AService
  101. public protocol SimpleServiceProtocol: NamespaceA_ServiceA.ServiceProtocol {}
  102. }
  103. // Default implementation of 'registerMethods(with:)'.
  104. extension NamespaceA_ServiceA.StreamingServiceProtocol {
  105. public func registerMethods<Transport>(with router: inout GRPCCore.RPCRouter<Transport>) where Transport: GRPCCore.ServerTransport {}
  106. }
  107. // Default implementation of streaming methods from 'StreamingServiceProtocol'.
  108. extension NamespaceA_ServiceA.ServiceProtocol {
  109. }
  110. // Default implementation of methods from 'ServiceProtocol'.
  111. extension NamespaceA_ServiceA.SimpleServiceProtocol {
  112. }
  113. """
  114. try self.assertIDLToStructuredSwiftTranslation(
  115. codeGenerationRequest: makeCodeGenerationRequest(
  116. services: [serviceA],
  117. dependencies: dependencies
  118. ),
  119. expectedSwift: expectedSwift,
  120. accessLevel: .public,
  121. server: true
  122. )
  123. }
  124. func testGenerateWithDifferentModuleName() throws {
  125. let service = ServiceDescriptor(
  126. documentation: "/// Documentation for FooService\n",
  127. name: ServiceName(
  128. identifyingName: "foo.FooService",
  129. typeName: "Foo_FooService",
  130. propertyName: "foo_FooService"
  131. ),
  132. methods: [
  133. MethodDescriptor(
  134. documentation: "",
  135. name: MethodName(
  136. identifyingName: "Unary",
  137. typeName: "Unary",
  138. functionName: "unary"
  139. ),
  140. isInputStreaming: false,
  141. isOutputStreaming: false,
  142. inputType: "Foo",
  143. outputType: "Bar"
  144. ),
  145. MethodDescriptor(
  146. documentation: "",
  147. name: MethodName(
  148. identifyingName: "ClientStreaming",
  149. typeName: "ClientStreaming",
  150. functionName: "clientStreaming"
  151. ),
  152. isInputStreaming: true,
  153. isOutputStreaming: false,
  154. inputType: "Foo",
  155. outputType: "Bar"
  156. ),
  157. MethodDescriptor(
  158. documentation: "",
  159. name: MethodName(
  160. identifyingName: "ServerStreaming",
  161. typeName: "ServerStreaming",
  162. functionName: "serverStreaming"
  163. ),
  164. isInputStreaming: false,
  165. isOutputStreaming: true,
  166. inputType: "Foo",
  167. outputType: "Bar"
  168. ),
  169. MethodDescriptor(
  170. documentation: "",
  171. name: MethodName(
  172. identifyingName: "BidiStreaming",
  173. typeName: "BidiStreaming",
  174. functionName: "bidiStreaming"
  175. ),
  176. isInputStreaming: true,
  177. isOutputStreaming: true,
  178. inputType: "Foo",
  179. outputType: "Bar"
  180. ),
  181. ]
  182. )
  183. let request = makeCodeGenerationRequest(services: [service])
  184. let translator = IDLToStructuredSwiftTranslator()
  185. let structuredSwift = try translator.translate(
  186. codeGenerationRequest: request,
  187. accessLevel: .internal,
  188. accessLevelOnImports: false,
  189. client: true,
  190. server: true,
  191. grpcCoreModuleName: String("GRPCCore".reversed())
  192. )
  193. let renderer = TextBasedRenderer.default
  194. let sourceFile = try renderer.render(structured: structuredSwift)
  195. let contents = sourceFile.contents
  196. XCTAssertFalse(contents.contains("GRPCCore"))
  197. }
  198. func testEmptyFileGeneration() throws {
  199. let expectedSwift =
  200. """
  201. /// Some really exciting license header 2023.
  202. // This file contained no services.
  203. """
  204. try self.assertIDLToStructuredSwiftTranslation(
  205. codeGenerationRequest: makeCodeGenerationRequest(
  206. services: [],
  207. dependencies: []
  208. ),
  209. expectedSwift: expectedSwift,
  210. accessLevel: .public,
  211. server: true
  212. )
  213. }
  214. private func assertIDLToStructuredSwiftTranslation(
  215. codeGenerationRequest: CodeGenerationRequest,
  216. expectedSwift: String,
  217. accessLevel: CodeGenerator.Config.AccessLevel,
  218. server: Bool = false,
  219. grpcCoreModuleName: String = "GRPCCore"
  220. ) throws {
  221. let translator = IDLToStructuredSwiftTranslator()
  222. let structuredSwift = try translator.translate(
  223. codeGenerationRequest: codeGenerationRequest,
  224. accessLevel: accessLevel,
  225. accessLevelOnImports: true,
  226. client: false,
  227. server: server,
  228. grpcCoreModuleName: grpcCoreModuleName
  229. )
  230. let renderer = TextBasedRenderer.default
  231. let sourceFile = try renderer.render(structured: structuredSwift)
  232. let contents = sourceFile.contents
  233. try XCTAssertEqualWithDiff(contents, expectedSwift)
  234. }
  235. func testSameNameServicesNoNamespaceError() throws {
  236. let serviceA = ServiceDescriptor(
  237. documentation: "Documentation for AService",
  238. name: ServiceName(
  239. identifyingName: "AService",
  240. typeName: "AService",
  241. propertyName: "aService"
  242. ),
  243. methods: []
  244. )
  245. let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceA])
  246. let translator = IDLToStructuredSwiftTranslator()
  247. XCTAssertThrowsError(
  248. ofType: CodeGenError.self,
  249. try translator.translate(
  250. codeGenerationRequest: codeGenerationRequest,
  251. accessLevel: .public,
  252. accessLevelOnImports: true,
  253. client: true,
  254. server: true,
  255. grpcCoreModuleName: "GRPCCore"
  256. )
  257. ) {
  258. error in
  259. XCTAssertEqual(
  260. error as CodeGenError,
  261. CodeGenError(
  262. code: .nonUniqueServiceName,
  263. message: """
  264. Services must have unique descriptors. \
  265. AService is the descriptor of at least two different services.
  266. """
  267. )
  268. )
  269. }
  270. }
  271. func testSameDescriptorsServicesNoNamespaceError() throws {
  272. let serviceA = ServiceDescriptor(
  273. documentation: "Documentation for AService",
  274. name: ServiceName(
  275. identifyingName: "AService",
  276. typeName: "AService",
  277. propertyName: "aService"
  278. ),
  279. methods: []
  280. )
  281. let serviceB = ServiceDescriptor(
  282. documentation: "Documentation for BService",
  283. name: ServiceName(
  284. identifyingName: "AService",
  285. typeName: "AService",
  286. propertyName: "aService"
  287. ),
  288. methods: []
  289. )
  290. let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceB])
  291. let translator = IDLToStructuredSwiftTranslator()
  292. XCTAssertThrowsError(
  293. ofType: CodeGenError.self,
  294. try translator.translate(
  295. codeGenerationRequest: codeGenerationRequest,
  296. accessLevel: .public,
  297. accessLevelOnImports: true,
  298. client: true,
  299. server: true,
  300. grpcCoreModuleName: "GRPCCore"
  301. )
  302. ) {
  303. error in
  304. XCTAssertEqual(
  305. error as CodeGenError,
  306. CodeGenError(
  307. code: .nonUniqueServiceName,
  308. message: """
  309. Services must have unique descriptors. AService is the descriptor of at least two different services.
  310. """
  311. )
  312. )
  313. }
  314. }
  315. func testSameDescriptorsSameNamespaceError() throws {
  316. let serviceA = ServiceDescriptor(
  317. documentation: "Documentation for AService",
  318. name: ServiceName(
  319. identifyingName: "namespacea.AService",
  320. typeName: "NamespaceA_AService",
  321. propertyName: "namespacea_aService"
  322. ),
  323. methods: []
  324. )
  325. let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceA])
  326. let translator = IDLToStructuredSwiftTranslator()
  327. XCTAssertThrowsError(
  328. ofType: CodeGenError.self,
  329. try translator.translate(
  330. codeGenerationRequest: codeGenerationRequest,
  331. accessLevel: .public,
  332. accessLevelOnImports: true,
  333. client: true,
  334. server: true,
  335. grpcCoreModuleName: "GRPCCore"
  336. )
  337. ) {
  338. error in
  339. XCTAssertEqual(
  340. error as CodeGenError,
  341. CodeGenError(
  342. code: .nonUniqueServiceName,
  343. message: """
  344. Services must have unique descriptors. \
  345. namespacea.AService is the descriptor of at least two different services.
  346. """
  347. )
  348. )
  349. }
  350. }
  351. func testSameGeneratedNameServicesSameNamespaceError() throws {
  352. let serviceA = ServiceDescriptor(
  353. documentation: "/// Documentation for AService\n",
  354. name: ServiceName(
  355. identifyingName: "namespacea.AService",
  356. typeName: "NamespaceA_AService",
  357. propertyName: "namespacea_aService"
  358. ),
  359. methods: []
  360. )
  361. let serviceB = ServiceDescriptor(
  362. documentation: "/// Documentation for BService\n",
  363. name: ServiceName(
  364. identifyingName: "namespacea.BService",
  365. typeName: "NamespaceA_AService",
  366. propertyName: "namespacea_aService"
  367. ),
  368. methods: []
  369. )
  370. let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceB])
  371. let translator = IDLToStructuredSwiftTranslator()
  372. XCTAssertThrowsError(
  373. ofType: CodeGenError.self,
  374. try translator.translate(
  375. codeGenerationRequest: codeGenerationRequest,
  376. accessLevel: .internal,
  377. accessLevelOnImports: true,
  378. client: true,
  379. server: true,
  380. grpcCoreModuleName: "GRPCCore"
  381. )
  382. ) { error in
  383. XCTAssertEqual(
  384. error as CodeGenError,
  385. CodeGenError(
  386. code: .nonUniqueServiceName,
  387. message: """
  388. There must be a unique (namespace, service_name) pair for each service. \
  389. NamespaceA_AService is used as a <namespace>_<service_name> construction for multiple services.
  390. """
  391. )
  392. )
  393. }
  394. }
  395. func testSameBaseNameMethodsSameServiceError() throws {
  396. let methodA = MethodDescriptor(
  397. documentation: "Documentation for MethodA",
  398. name: MethodName(identifyingName: "MethodA", typeName: "MethodA", functionName: "methodA"),
  399. isInputStreaming: false,
  400. isOutputStreaming: false,
  401. inputType: "NamespaceA_ServiceARequest",
  402. outputType: "NamespaceA_ServiceAResponse"
  403. )
  404. let service = ServiceDescriptor(
  405. documentation: "Documentation for AService",
  406. name: ServiceName(
  407. identifyingName: "namespacea.AService",
  408. typeName: "NamespaceA_AService",
  409. propertyName: "namespacea_aService"
  410. ),
  411. methods: [methodA, methodA]
  412. )
  413. let codeGenerationRequest = makeCodeGenerationRequest(services: [service])
  414. let translator = IDLToStructuredSwiftTranslator()
  415. XCTAssertThrowsError(
  416. ofType: CodeGenError.self,
  417. try translator.translate(
  418. codeGenerationRequest: codeGenerationRequest,
  419. accessLevel: .public,
  420. accessLevelOnImports: true,
  421. client: true,
  422. server: true,
  423. grpcCoreModuleName: "GRPCCore"
  424. )
  425. ) { error in
  426. XCTAssertEqual(
  427. error as CodeGenError,
  428. CodeGenError(
  429. code: .nonUniqueMethodName,
  430. message: """
  431. Methods of a service must have unique base names. \
  432. MethodA is used as a base name for multiple methods of the namespacea.AService service.
  433. """
  434. )
  435. )
  436. }
  437. }
  438. func testSameGeneratedUpperCaseNameMethodsSameServiceError() throws {
  439. let methodA = MethodDescriptor(
  440. documentation: "Documentation for MethodA",
  441. name: MethodName(
  442. identifyingName: "MethodA",
  443. typeName: "MethodA",
  444. functionName: "methodA"
  445. ),
  446. isInputStreaming: false,
  447. isOutputStreaming: false,
  448. inputType: "NamespaceA_ServiceARequest",
  449. outputType: "NamespaceA_ServiceAResponse"
  450. )
  451. let methodB = MethodDescriptor(
  452. documentation: "Documentation for MethodA",
  453. name: MethodName(
  454. identifyingName: "MethodB",
  455. typeName: "MethodA",
  456. functionName: "methodA"
  457. ),
  458. isInputStreaming: false,
  459. isOutputStreaming: false,
  460. inputType: "NamespaceA_ServiceARequest",
  461. outputType: "NamespaceA_ServiceAResponse"
  462. )
  463. let service = ServiceDescriptor(
  464. documentation: "Documentation for AService",
  465. name: ServiceName(
  466. identifyingName: "namespacea.AService",
  467. typeName: "NamespaceA_AService",
  468. propertyName: "namespacea_AService"
  469. ),
  470. methods: [methodA, methodB]
  471. )
  472. let codeGenerationRequest = makeCodeGenerationRequest(services: [service])
  473. let translator = IDLToStructuredSwiftTranslator()
  474. XCTAssertThrowsError(
  475. ofType: CodeGenError.self,
  476. try translator.translate(
  477. codeGenerationRequest: codeGenerationRequest,
  478. accessLevel: .public,
  479. accessLevelOnImports: true,
  480. client: true,
  481. server: true,
  482. grpcCoreModuleName: "GRPCCore"
  483. )
  484. ) { error in
  485. XCTAssertEqual(
  486. error as CodeGenError,
  487. CodeGenError(
  488. code: .nonUniqueMethodName,
  489. message: """
  490. Methods of a service must have unique generated upper case names. \
  491. MethodA is used as a generated upper case name for multiple methods of the \
  492. namespacea.AService service.
  493. """
  494. )
  495. )
  496. }
  497. }
  498. func testSameLowerCaseNameMethodsSameServiceError() throws {
  499. let methodA = MethodDescriptor(
  500. documentation: "Documentation for MethodA",
  501. name: MethodName(identifyingName: "MethodA", typeName: "MethodA", functionName: "methodA"),
  502. isInputStreaming: false,
  503. isOutputStreaming: false,
  504. inputType: "NamespaceA_ServiceARequest",
  505. outputType: "NamespaceA_ServiceAResponse"
  506. )
  507. let methodB = MethodDescriptor(
  508. documentation: "Documentation for MethodA",
  509. name: MethodName(identifyingName: "MethodB", typeName: "MethodB", functionName: "methodA"),
  510. isInputStreaming: false,
  511. isOutputStreaming: false,
  512. inputType: "NamespaceA_ServiceARequest",
  513. outputType: "NamespaceA_ServiceAResponse"
  514. )
  515. let service = ServiceDescriptor(
  516. documentation: "Documentation for AService",
  517. name: ServiceName(
  518. identifyingName: "namespacea.AService",
  519. typeName: "NamespaceA_AService",
  520. propertyName: "namespacea_aService"
  521. ),
  522. methods: [methodA, methodB]
  523. )
  524. let codeGenerationRequest = makeCodeGenerationRequest(services: [service])
  525. let translator = IDLToStructuredSwiftTranslator()
  526. XCTAssertThrowsError(
  527. ofType: CodeGenError.self,
  528. try translator.translate(
  529. codeGenerationRequest: codeGenerationRequest,
  530. accessLevel: .public,
  531. accessLevelOnImports: true,
  532. client: true,
  533. server: true,
  534. grpcCoreModuleName: "GRPCCore"
  535. )
  536. ) {
  537. error in
  538. XCTAssertEqual(
  539. error as CodeGenError,
  540. CodeGenError(
  541. code: .nonUniqueMethodName,
  542. message: """
  543. Methods of a service must have unique lower case names. \
  544. methodA is used as a signature name for multiple methods of the \
  545. namespacea.AService service.
  546. """
  547. )
  548. )
  549. }
  550. }
  551. func testSameGeneratedNameNoNamespaceServiceAndNamespaceError() throws {
  552. let serviceA = ServiceDescriptor(
  553. documentation: "Documentation for SameName service with no namespace",
  554. name: ServiceName(
  555. identifyingName: "SameName",
  556. typeName: "SameName_BService",
  557. propertyName: "sameName"
  558. ),
  559. methods: []
  560. )
  561. let serviceB = ServiceDescriptor(
  562. documentation: "Documentation for BService",
  563. name: ServiceName(
  564. identifyingName: "sameName.BService",
  565. typeName: "SameName_BService",
  566. propertyName: "sameName"
  567. ),
  568. methods: []
  569. )
  570. let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceB])
  571. let translator = IDLToStructuredSwiftTranslator()
  572. XCTAssertThrowsError(
  573. ofType: CodeGenError.self,
  574. try translator.translate(
  575. codeGenerationRequest: codeGenerationRequest,
  576. accessLevel: .public,
  577. accessLevelOnImports: true,
  578. client: true,
  579. server: true,
  580. grpcCoreModuleName: "GRPCCore"
  581. )
  582. ) {
  583. error in
  584. XCTAssertEqual(
  585. error as CodeGenError,
  586. CodeGenError(
  587. code: .nonUniqueServiceName,
  588. message: """
  589. There must be a unique (namespace, service_name) pair for each service. \
  590. SameName_BService is used as a <namespace>_<service_name> construction for multiple services.
  591. """
  592. )
  593. )
  594. }
  595. }
  596. }
  597. #endif // os(macOS) || os(Linux)