IDLToStructuredSwiftTranslator.swift 8.6 KB

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