IDLToStructuredSwiftTranslator.swift 9.3 KB

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