IDLToStructuredSwiftTranslator.swift 9.6 KB

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