IDLToStructuredSwiftTranslatorSnippetBasedTests.swift 22 KB

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