IDLToStructuredSwiftTranslatorSnippetBasedTests.swift 19 KB

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