IDLToStructuredSwiftTranslator.swift 8.3 KB

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