IDLToStructuredSwiftTranslatorSnippetBasedTests.swift 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  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 testEmptyFileGeneration() throws {
  125. let expectedSwift =
  126. """
  127. /// Some really exciting license header 2023.
  128. // This file contained no services.
  129. """
  130. try self.assertIDLToStructuredSwiftTranslation(
  131. codeGenerationRequest: makeCodeGenerationRequest(
  132. services: [],
  133. dependencies: []
  134. ),
  135. expectedSwift: expectedSwift,
  136. accessLevel: .public,
  137. server: true
  138. )
  139. }
  140. private func assertIDLToStructuredSwiftTranslation(
  141. codeGenerationRequest: CodeGenerationRequest,
  142. expectedSwift: String,
  143. accessLevel: CodeGenerator.Config.AccessLevel,
  144. server: Bool = false
  145. ) throws {
  146. let translator = IDLToStructuredSwiftTranslator()
  147. let structuredSwift = try translator.translate(
  148. codeGenerationRequest: codeGenerationRequest,
  149. accessLevel: accessLevel,
  150. accessLevelOnImports: true,
  151. client: false,
  152. server: server
  153. )
  154. let renderer = TextBasedRenderer.default
  155. let sourceFile = try renderer.render(structured: structuredSwift)
  156. let contents = sourceFile.contents
  157. try XCTAssertEqualWithDiff(contents, expectedSwift)
  158. }
  159. func testSameNameServicesNoNamespaceError() throws {
  160. let serviceA = ServiceDescriptor(
  161. documentation: "Documentation for AService",
  162. name: ServiceName(
  163. identifyingName: "AService",
  164. typeName: "AService",
  165. propertyName: "aService"
  166. ),
  167. methods: []
  168. )
  169. let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceA])
  170. let translator = IDLToStructuredSwiftTranslator()
  171. XCTAssertThrowsError(
  172. ofType: CodeGenError.self,
  173. try translator.translate(
  174. codeGenerationRequest: codeGenerationRequest,
  175. accessLevel: .public,
  176. accessLevelOnImports: true,
  177. client: true,
  178. server: true
  179. )
  180. ) {
  181. error in
  182. XCTAssertEqual(
  183. error as CodeGenError,
  184. CodeGenError(
  185. code: .nonUniqueServiceName,
  186. message: """
  187. Services must have unique descriptors. \
  188. AService is the descriptor of at least two different services.
  189. """
  190. )
  191. )
  192. }
  193. }
  194. func testSameDescriptorsServicesNoNamespaceError() throws {
  195. let serviceA = ServiceDescriptor(
  196. documentation: "Documentation for AService",
  197. name: ServiceName(
  198. identifyingName: "AService",
  199. typeName: "AService",
  200. propertyName: "aService"
  201. ),
  202. methods: []
  203. )
  204. let serviceB = ServiceDescriptor(
  205. documentation: "Documentation for BService",
  206. name: ServiceName(
  207. identifyingName: "AService",
  208. typeName: "AService",
  209. propertyName: "aService"
  210. ),
  211. methods: []
  212. )
  213. let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceB])
  214. let translator = IDLToStructuredSwiftTranslator()
  215. XCTAssertThrowsError(
  216. ofType: CodeGenError.self,
  217. try translator.translate(
  218. codeGenerationRequest: codeGenerationRequest,
  219. accessLevel: .public,
  220. accessLevelOnImports: true,
  221. client: true,
  222. server: true
  223. )
  224. ) {
  225. error in
  226. XCTAssertEqual(
  227. error as CodeGenError,
  228. CodeGenError(
  229. code: .nonUniqueServiceName,
  230. message: """
  231. Services must have unique descriptors. AService is the descriptor of at least two different services.
  232. """
  233. )
  234. )
  235. }
  236. }
  237. func testSameDescriptorsSameNamespaceError() throws {
  238. let serviceA = ServiceDescriptor(
  239. documentation: "Documentation for AService",
  240. name: ServiceName(
  241. identifyingName: "namespacea.AService",
  242. typeName: "NamespaceA_AService",
  243. propertyName: "namespacea_aService"
  244. ),
  245. methods: []
  246. )
  247. let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceA])
  248. let translator = IDLToStructuredSwiftTranslator()
  249. XCTAssertThrowsError(
  250. ofType: CodeGenError.self,
  251. try translator.translate(
  252. codeGenerationRequest: codeGenerationRequest,
  253. accessLevel: .public,
  254. accessLevelOnImports: true,
  255. client: true,
  256. server: true
  257. )
  258. ) {
  259. error in
  260. XCTAssertEqual(
  261. error as CodeGenError,
  262. CodeGenError(
  263. code: .nonUniqueServiceName,
  264. message: """
  265. Services must have unique descriptors. \
  266. namespacea.AService is the descriptor of at least two different services.
  267. """
  268. )
  269. )
  270. }
  271. }
  272. func testSameGeneratedNameServicesSameNamespaceError() throws {
  273. let serviceA = ServiceDescriptor(
  274. documentation: "/// Documentation for AService\n",
  275. name: ServiceName(
  276. identifyingName: "namespacea.AService",
  277. typeName: "NamespaceA_AService",
  278. propertyName: "namespacea_aService"
  279. ),
  280. methods: []
  281. )
  282. let serviceB = ServiceDescriptor(
  283. documentation: "/// Documentation for BService\n",
  284. name: ServiceName(
  285. identifyingName: "namespacea.BService",
  286. typeName: "NamespaceA_AService",
  287. propertyName: "namespacea_aService"
  288. ),
  289. methods: []
  290. )
  291. let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceB])
  292. let translator = IDLToStructuredSwiftTranslator()
  293. XCTAssertThrowsError(
  294. ofType: CodeGenError.self,
  295. try translator.translate(
  296. codeGenerationRequest: codeGenerationRequest,
  297. accessLevel: .internal,
  298. accessLevelOnImports: true,
  299. client: true,
  300. server: true
  301. )
  302. ) { error in
  303. XCTAssertEqual(
  304. error as CodeGenError,
  305. CodeGenError(
  306. code: .nonUniqueServiceName,
  307. message: """
  308. There must be a unique (namespace, service_name) pair for each service. \
  309. NamespaceA_AService is used as a <namespace>_<service_name> construction for multiple services.
  310. """
  311. )
  312. )
  313. }
  314. }
  315. func testSameBaseNameMethodsSameServiceError() throws {
  316. let methodA = MethodDescriptor(
  317. documentation: "Documentation for MethodA",
  318. name: MethodName(identifyingName: "MethodA", typeName: "MethodA", functionName: "methodA"),
  319. isInputStreaming: false,
  320. isOutputStreaming: false,
  321. inputType: "NamespaceA_ServiceARequest",
  322. outputType: "NamespaceA_ServiceAResponse"
  323. )
  324. let service = ServiceDescriptor(
  325. documentation: "Documentation for AService",
  326. name: ServiceName(
  327. identifyingName: "namespacea.AService",
  328. typeName: "NamespaceA_AService",
  329. propertyName: "namespacea_aService"
  330. ),
  331. methods: [methodA, methodA]
  332. )
  333. let codeGenerationRequest = makeCodeGenerationRequest(services: [service])
  334. let translator = IDLToStructuredSwiftTranslator()
  335. XCTAssertThrowsError(
  336. ofType: CodeGenError.self,
  337. try translator.translate(
  338. codeGenerationRequest: codeGenerationRequest,
  339. accessLevel: .public,
  340. accessLevelOnImports: true,
  341. client: true,
  342. server: true
  343. )
  344. ) { error in
  345. XCTAssertEqual(
  346. error as CodeGenError,
  347. CodeGenError(
  348. code: .nonUniqueMethodName,
  349. message: """
  350. Methods of a service must have unique base names. \
  351. MethodA is used as a base name for multiple methods of the namespacea.AService service.
  352. """
  353. )
  354. )
  355. }
  356. }
  357. func testSameGeneratedUpperCaseNameMethodsSameServiceError() throws {
  358. let methodA = MethodDescriptor(
  359. documentation: "Documentation for MethodA",
  360. name: MethodName(
  361. identifyingName: "MethodA",
  362. typeName: "MethodA",
  363. functionName: "methodA"
  364. ),
  365. isInputStreaming: false,
  366. isOutputStreaming: false,
  367. inputType: "NamespaceA_ServiceARequest",
  368. outputType: "NamespaceA_ServiceAResponse"
  369. )
  370. let methodB = MethodDescriptor(
  371. documentation: "Documentation for MethodA",
  372. name: MethodName(
  373. identifyingName: "MethodB",
  374. typeName: "MethodA",
  375. functionName: "methodA"
  376. ),
  377. isInputStreaming: false,
  378. isOutputStreaming: false,
  379. inputType: "NamespaceA_ServiceARequest",
  380. outputType: "NamespaceA_ServiceAResponse"
  381. )
  382. let service = ServiceDescriptor(
  383. documentation: "Documentation for AService",
  384. name: ServiceName(
  385. identifyingName: "namespacea.AService",
  386. typeName: "NamespaceA_AService",
  387. propertyName: "namespacea_AService"
  388. ),
  389. methods: [methodA, methodB]
  390. )
  391. let codeGenerationRequest = makeCodeGenerationRequest(services: [service])
  392. let translator = IDLToStructuredSwiftTranslator()
  393. XCTAssertThrowsError(
  394. ofType: CodeGenError.self,
  395. try translator.translate(
  396. codeGenerationRequest: codeGenerationRequest,
  397. accessLevel: .public,
  398. accessLevelOnImports: true,
  399. client: true,
  400. server: true
  401. )
  402. ) { error in
  403. XCTAssertEqual(
  404. error as CodeGenError,
  405. CodeGenError(
  406. code: .nonUniqueMethodName,
  407. message: """
  408. Methods of a service must have unique generated upper case names. \
  409. MethodA is used as a generated upper case name for multiple methods of the \
  410. namespacea.AService service.
  411. """
  412. )
  413. )
  414. }
  415. }
  416. func testSameLowerCaseNameMethodsSameServiceError() throws {
  417. let methodA = MethodDescriptor(
  418. documentation: "Documentation for MethodA",
  419. name: MethodName(identifyingName: "MethodA", typeName: "MethodA", functionName: "methodA"),
  420. isInputStreaming: false,
  421. isOutputStreaming: false,
  422. inputType: "NamespaceA_ServiceARequest",
  423. outputType: "NamespaceA_ServiceAResponse"
  424. )
  425. let methodB = MethodDescriptor(
  426. documentation: "Documentation for MethodA",
  427. name: MethodName(identifyingName: "MethodB", typeName: "MethodB", functionName: "methodA"),
  428. isInputStreaming: false,
  429. isOutputStreaming: false,
  430. inputType: "NamespaceA_ServiceARequest",
  431. outputType: "NamespaceA_ServiceAResponse"
  432. )
  433. let service = ServiceDescriptor(
  434. documentation: "Documentation for AService",
  435. name: ServiceName(
  436. identifyingName: "namespacea.AService",
  437. typeName: "NamespaceA_AService",
  438. propertyName: "namespacea_aService"
  439. ),
  440. methods: [methodA, methodB]
  441. )
  442. let codeGenerationRequest = makeCodeGenerationRequest(services: [service])
  443. let translator = IDLToStructuredSwiftTranslator()
  444. XCTAssertThrowsError(
  445. ofType: CodeGenError.self,
  446. try translator.translate(
  447. codeGenerationRequest: codeGenerationRequest,
  448. accessLevel: .public,
  449. accessLevelOnImports: true,
  450. client: true,
  451. server: true
  452. )
  453. ) {
  454. error in
  455. XCTAssertEqual(
  456. error as CodeGenError,
  457. CodeGenError(
  458. code: .nonUniqueMethodName,
  459. message: """
  460. Methods of a service must have unique lower case names. \
  461. methodA is used as a signature name for multiple methods of the \
  462. namespacea.AService service.
  463. """
  464. )
  465. )
  466. }
  467. }
  468. func testSameGeneratedNameNoNamespaceServiceAndNamespaceError() throws {
  469. let serviceA = ServiceDescriptor(
  470. documentation: "Documentation for SameName service with no namespace",
  471. name: ServiceName(
  472. identifyingName: "SameName",
  473. typeName: "SameName_BService",
  474. propertyName: "sameName"
  475. ),
  476. methods: []
  477. )
  478. let serviceB = ServiceDescriptor(
  479. documentation: "Documentation for BService",
  480. name: ServiceName(
  481. identifyingName: "sameName.BService",
  482. typeName: "SameName_BService",
  483. propertyName: "sameName"
  484. ),
  485. methods: []
  486. )
  487. let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceB])
  488. let translator = IDLToStructuredSwiftTranslator()
  489. XCTAssertThrowsError(
  490. ofType: CodeGenError.self,
  491. try translator.translate(
  492. codeGenerationRequest: codeGenerationRequest,
  493. accessLevel: .public,
  494. accessLevelOnImports: true,
  495. client: true,
  496. server: true
  497. )
  498. ) {
  499. error in
  500. XCTAssertEqual(
  501. error as CodeGenError,
  502. CodeGenError(
  503. code: .nonUniqueServiceName,
  504. message: """
  505. There must be a unique (namespace, service_name) pair for each service. \
  506. SameName_BService is used as a <namespace>_<service_name> construction for multiple services.
  507. """
  508. )
  509. )
  510. }
  511. }
  512. }
  513. #endif // os(macOS) || os(Linux)