IDLToStructuredSwiftTranslator.swift 9.6 KB

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