IDLToStructuredSwiftTranslator.swift 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  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.doc(codeGenerationRequest.leadingTrivia)
  33. let imports = try codeGenerationRequest.dependencies.reduce(
  34. into: [ImportDescription(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 IDLToStructuredSwiftTranslator {
  65. private func translateImport(
  66. dependency: CodeGenerationRequest.Dependency
  67. ) throws -> ImportDescription {
  68. var importDescription = ImportDescription(moduleName: dependency.module)
  69. if let item = dependency.item {
  70. if let matchedKind = ImportDescription.Kind(rawValue: item.kind.value.rawValue) {
  71. importDescription.item = ImportDescription.Item(kind: matchedKind, name: item.name)
  72. } else {
  73. throw CodeGenError(
  74. code: .invalidKind,
  75. message: "Invalid kind name for import: \(item.kind.value.rawValue)"
  76. )
  77. }
  78. }
  79. if let spi = dependency.spi {
  80. importDescription.spi = spi
  81. }
  82. switch dependency.preconcurrency.value {
  83. case .required:
  84. importDescription.preconcurrency = .always
  85. case .notRequired:
  86. importDescription.preconcurrency = .never
  87. case .requiredOnOS(let OSs):
  88. importDescription.preconcurrency = .onOS(OSs)
  89. }
  90. return importDescription
  91. }
  92. private func validateInput(_ codeGenerationRequest: CodeGenerationRequest) throws {
  93. let servicesByNamespace = Dictionary(
  94. grouping: codeGenerationRequest.services,
  95. by: { $0.namespace }
  96. )
  97. try self.checkServiceNamesAreUnique(for: servicesByNamespace)
  98. for service in codeGenerationRequest.services {
  99. try self.checkMethodNamesAreUnique(in: service)
  100. }
  101. }
  102. // Verify service names are unique within each namespace and that services with no namespace
  103. // don't have the same names as any of the namespaces.
  104. private func checkServiceNamesAreUnique(
  105. for servicesByNamespace: [String: [CodeGenerationRequest.ServiceDescriptor]]
  106. ) throws {
  107. // Check that if there are services in an empty namespace, none have names which match other namespaces
  108. if let noNamespaceServices = servicesByNamespace[""] {
  109. let namespaces = servicesByNamespace.keys
  110. for service in noNamespaceServices {
  111. if namespaces.contains(service.name) {
  112. throw CodeGenError(
  113. code: .nonUniqueServiceName,
  114. message: """
  115. Services with no namespace must not have the same names as the namespaces. \
  116. \(service.name) is used as a name for a service with no namespace and a namespace.
  117. """
  118. )
  119. }
  120. }
  121. }
  122. // Check that service names are unique within each namespace.
  123. for (namespace, services) in servicesByNamespace {
  124. var serviceNames: Set<String> = []
  125. for service in services {
  126. if serviceNames.contains(service.name) {
  127. let errorMessage: String
  128. if namespace.isEmpty {
  129. errorMessage = """
  130. Services in an empty namespace must have unique names. \
  131. \(service.name) is used as a name for multiple services without namespaces.
  132. """
  133. } else {
  134. errorMessage = """
  135. Services within the same namespace must have unique names. \
  136. \(service.name) is used as a name for multiple services in the \(service.namespace) namespace.
  137. """
  138. }
  139. throw CodeGenError(
  140. code: .nonUniqueServiceName,
  141. message: errorMessage
  142. )
  143. }
  144. serviceNames.insert(service.name)
  145. }
  146. }
  147. }
  148. // Verify method names are unique for the service.
  149. private func checkMethodNamesAreUnique(
  150. in service: CodeGenerationRequest.ServiceDescriptor
  151. ) throws {
  152. let methodNames = service.methods.map { $0.name }
  153. var seenNames = Set<String>()
  154. for methodName in methodNames {
  155. if seenNames.contains(methodName) {
  156. throw CodeGenError(
  157. code: .nonUniqueMethodName,
  158. message: """
  159. Methods of a service must have unique names. \
  160. \(methodName) is used as a name for multiple methods of the \(service.name) service.
  161. """
  162. )
  163. }
  164. seenNames.insert(methodName)
  165. }
  166. }
  167. }
  168. extension CodeGenerationRequest.ServiceDescriptor {
  169. var namespacedTypealiasPrefix: String {
  170. if self.namespace.isEmpty {
  171. return self.name
  172. } else {
  173. return "\(self.namespace).\(self.name)"
  174. }
  175. }
  176. var namespacedPrefix: String {
  177. if self.namespace.isEmpty {
  178. return self.name
  179. } else {
  180. return "\(self.namespace)_\(self.name)"
  181. }
  182. }
  183. }