IDLToStructuredSwiftTranslator.swift 8.9 KB

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