IDLToStructuredSwiftTranslator.swift 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. /*
  2. * Copyright 2023, 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. /// Creates a representation for the server and client code, as well as for the enums containing useful type aliases and properties.
  17. /// The representation is generated based on the ``CodeGenerationRequest`` object and user specifications,
  18. /// using types from ``StructuredSwiftRepresentation``.
  19. @available(gRPCSwift 2.0, *)
  20. package struct IDLToStructuredSwiftTranslator {
  21. package init() {}
  22. func translate(
  23. codeGenerationRequest: CodeGenerationRequest,
  24. accessLevel: CodeGenerator.Config.AccessLevel,
  25. accessLevelOnImports: Bool,
  26. client: Bool,
  27. server: Bool,
  28. grpcCoreModuleName: String,
  29. availability: AvailabilityDescription
  30. ) throws -> StructuredSwiftRepresentation {
  31. try self.validateInput(codeGenerationRequest)
  32. let accessModifier = AccessModifier(accessLevel)
  33. var codeBlocks: [CodeBlock] = []
  34. let metadataTranslator = MetadataTranslator()
  35. let serverTranslator = ServerCodeTranslator()
  36. let clientTranslator = ClientCodeTranslator()
  37. let namer = Namer(grpcCore: grpcCoreModuleName)
  38. for service in codeGenerationRequest.services {
  39. codeBlocks.append(
  40. CodeBlock(comment: .mark("\(service.name.identifyingName)", sectionBreak: true))
  41. )
  42. let metadata = metadataTranslator.translate(
  43. accessModifier: accessModifier,
  44. service: service,
  45. availability: availability,
  46. namer: namer
  47. )
  48. codeBlocks.append(contentsOf: metadata)
  49. if server {
  50. codeBlocks.append(
  51. CodeBlock(comment: .mark("\(service.name.identifyingName) (server)", sectionBreak: false))
  52. )
  53. let blocks = serverTranslator.translate(
  54. accessModifier: accessModifier,
  55. service: service,
  56. availability: availability,
  57. namer: namer,
  58. serializer: codeGenerationRequest.makeSerializerCodeSnippet,
  59. deserializer: codeGenerationRequest.makeDeserializerCodeSnippet
  60. )
  61. codeBlocks.append(contentsOf: blocks)
  62. }
  63. if client {
  64. codeBlocks.append(
  65. CodeBlock(comment: .mark("\(service.name.identifyingName) (client)", sectionBreak: false))
  66. )
  67. let blocks = clientTranslator.translate(
  68. accessModifier: accessModifier,
  69. service: service,
  70. availability: availability,
  71. namer: namer,
  72. serializer: codeGenerationRequest.makeSerializerCodeSnippet,
  73. deserializer: codeGenerationRequest.makeDeserializerCodeSnippet
  74. )
  75. codeBlocks.append(contentsOf: blocks)
  76. }
  77. }
  78. let imports: [ImportDescription]?
  79. if codeGenerationRequest.services.isEmpty {
  80. imports = nil
  81. codeBlocks.append(
  82. CodeBlock(comment: .inline("This file contained no services."))
  83. )
  84. } else {
  85. imports = try self.makeImports(
  86. dependencies: codeGenerationRequest.dependencies,
  87. accessLevel: accessLevel,
  88. accessLevelOnImports: accessLevelOnImports,
  89. grpcCoreModuleName: grpcCoreModuleName
  90. )
  91. }
  92. let fileDescription = FileDescription(
  93. topComment: .preFormatted(codeGenerationRequest.leadingTrivia),
  94. imports: imports,
  95. codeBlocks: codeBlocks
  96. )
  97. let fileName = String(codeGenerationRequest.fileName.split(separator: ".")[0])
  98. let file = NamedFileDescription(name: fileName, contents: fileDescription)
  99. return StructuredSwiftRepresentation(file: file)
  100. }
  101. package func makeImports(
  102. dependencies: [Dependency],
  103. accessLevel: CodeGenerator.Config.AccessLevel,
  104. accessLevelOnImports: Bool,
  105. grpcCoreModuleName: String
  106. ) throws -> [ImportDescription] {
  107. var imports: [ImportDescription] = []
  108. imports.append(
  109. ImportDescription(
  110. accessLevel: accessLevelOnImports ? AccessModifier(accessLevel) : nil,
  111. moduleName: grpcCoreModuleName
  112. )
  113. )
  114. for dependency in dependencies {
  115. let importDescription = try self.translateImport(
  116. dependency: dependency,
  117. accessLevelOnImports: accessLevelOnImports
  118. )
  119. imports.append(importDescription)
  120. }
  121. return imports
  122. }
  123. }
  124. @available(gRPCSwift 2.0, *)
  125. extension AccessModifier {
  126. init(_ accessLevel: CodeGenerator.Config.AccessLevel) {
  127. switch accessLevel.level {
  128. case .internal: self = .internal
  129. case .package: self = .package
  130. case .public: self = .public
  131. }
  132. }
  133. }
  134. @available(gRPCSwift 2.0, *)
  135. extension IDLToStructuredSwiftTranslator {
  136. private func translateImport(
  137. dependency: Dependency,
  138. accessLevelOnImports: Bool
  139. ) throws -> ImportDescription {
  140. var importDescription = ImportDescription(
  141. accessLevel: accessLevelOnImports ? AccessModifier(dependency.accessLevel) : nil,
  142. moduleName: dependency.module
  143. )
  144. if let item = dependency.item {
  145. if let matchedKind = ImportDescription.Kind(rawValue: item.kind.value.rawValue) {
  146. importDescription.item = ImportDescription.Item(kind: matchedKind, name: item.name)
  147. } else {
  148. throw CodeGenError(
  149. code: .invalidKind,
  150. message: "Invalid kind name for import: \(item.kind.value.rawValue)"
  151. )
  152. }
  153. }
  154. if let spi = dependency.spi {
  155. importDescription.spi = spi
  156. }
  157. switch dependency.preconcurrency.value {
  158. case .required:
  159. importDescription.preconcurrency = .always
  160. case .notRequired:
  161. importDescription.preconcurrency = .never
  162. case .requiredOnOS(let OSs):
  163. importDescription.preconcurrency = .onOS(OSs)
  164. }
  165. return importDescription
  166. }
  167. private func validateInput(_ codeGenerationRequest: CodeGenerationRequest) throws {
  168. try self.checkServiceDescriptorsAreUnique(codeGenerationRequest.services)
  169. let servicesByGeneratedEnumName = Dictionary(
  170. grouping: codeGenerationRequest.services,
  171. by: { $0.name.typeName }
  172. )
  173. try self.checkServiceEnumNamesAreUnique(for: servicesByGeneratedEnumName)
  174. for service in codeGenerationRequest.services {
  175. try self.checkMethodNamesAreUnique(in: service)
  176. }
  177. }
  178. // Verify service enum names are unique.
  179. private func checkServiceEnumNamesAreUnique(
  180. for servicesByGeneratedEnumName: [String: [ServiceDescriptor]]
  181. ) throws {
  182. for (generatedEnumName, services) in servicesByGeneratedEnumName {
  183. if services.count > 1 {
  184. throw CodeGenError(
  185. code: .nonUniqueServiceName,
  186. message: """
  187. There must be a unique (namespace, service_name) pair for each service. \
  188. \(generatedEnumName) is used as a <namespace>_<service_name> construction for multiple services.
  189. """
  190. )
  191. }
  192. }
  193. }
  194. // Verify method names are unique within a service.
  195. private func checkMethodNamesAreUnique(
  196. in service: ServiceDescriptor
  197. ) throws {
  198. // Check that the method descriptors are unique, by checking that the base names
  199. // of the methods of a specific service are unique.
  200. let baseNames = service.methods.map { $0.name.identifyingName }
  201. if let duplicatedBase = baseNames.getFirstDuplicate() {
  202. throw CodeGenError(
  203. code: .nonUniqueMethodName,
  204. message: """
  205. Methods of a service must have unique base names. \
  206. \(duplicatedBase) is used as a base name for multiple methods of the \(service.name.identifyingName) service.
  207. """
  208. )
  209. }
  210. // Check that generated upper case names for methods are unique within a service, to ensure that
  211. // the enums containing type aliases for each method of a service.
  212. let upperCaseNames = service.methods.map { $0.name.typeName }
  213. if let duplicatedGeneratedUpperCase = upperCaseNames.getFirstDuplicate() {
  214. throw CodeGenError(
  215. code: .nonUniqueMethodName,
  216. message: """
  217. Methods of a service must have unique generated upper case names. \
  218. \(duplicatedGeneratedUpperCase) is used as a generated upper case name for multiple methods of the \(service.name.identifyingName) service.
  219. """
  220. )
  221. }
  222. // Check that generated lower case names for methods are unique within a service, to ensure that
  223. // the function declarations and definitions from the same protocols and extensions have unique names.
  224. let lowerCaseNames = service.methods.map { $0.name.functionName }
  225. if let duplicatedLowerCase = lowerCaseNames.getFirstDuplicate() {
  226. throw CodeGenError(
  227. code: .nonUniqueMethodName,
  228. message: """
  229. Methods of a service must have unique lower case names. \
  230. \(duplicatedLowerCase) is used as a signature name for multiple methods of the \(service.name.identifyingName) service.
  231. """
  232. )
  233. }
  234. }
  235. private func checkServiceDescriptorsAreUnique(
  236. _ services: [ServiceDescriptor]
  237. ) throws {
  238. var descriptors: Set<String> = []
  239. for service in services {
  240. let name = service.name.identifyingName
  241. let (inserted, _) = descriptors.insert(name)
  242. if !inserted {
  243. throw CodeGenError(
  244. code: .nonUniqueServiceName,
  245. message: """
  246. Services must have unique descriptors. \
  247. \(name) is the descriptor of at least two different services.
  248. """
  249. )
  250. }
  251. }
  252. }
  253. }
  254. extension [String] {
  255. internal func getFirstDuplicate() -> String? {
  256. var seen = Set<String>()
  257. for element in self {
  258. if seen.contains(element) {
  259. return element
  260. }
  261. seen.insert(element)
  262. }
  263. return nil
  264. }
  265. }