ReflectionService.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  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. import DequeModule
  17. import Foundation
  18. import GRPC
  19. import SwiftProtobuf
  20. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  21. public final class ReflectionService: CallHandlerProvider, Sendable {
  22. private let provider: Provider
  23. public var serviceName: Substring {
  24. switch self.provider {
  25. case .v1(let provider):
  26. return provider.serviceName
  27. case .v1Alpha(let provider):
  28. return provider.serviceName
  29. }
  30. }
  31. /// Creates a `ReflectionService` by loading serialized reflection data created by `protoc-gen-grpc-swift`.
  32. ///
  33. /// You can generate serialized reflection data using the `protoc-gen-grpc-swift` plugin for `protoc` by
  34. /// setting the `ReflectionData` option to `True`.
  35. ///
  36. /// - Parameter fileURLs: The URLs of the files containing serialized reflection data.
  37. /// - Parameter version: The version of the reflection service to create.
  38. ///
  39. /// - Throws: When a file can't be read from disk or parsed.
  40. public convenience init(reflectionDataFileURLs fileURLs: [URL], version: Version) throws {
  41. let filePaths: [String]
  42. #if os(Linux)
  43. filePaths = fileURLs.map { $0.path }
  44. #else
  45. if #available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *) {
  46. filePaths = fileURLs.map { $0.path() }
  47. } else {
  48. filePaths = fileURLs.map { $0.path }
  49. }
  50. #endif
  51. try self.init(reflectionDataFilePaths: filePaths, version: version)
  52. }
  53. /// Creates a `ReflectionService` by loading serialized reflection data created by `protoc-gen-grpc-swift`.
  54. ///
  55. /// You can generate serialized reflection data using the `protoc-gen-grpc-swift` plugin for `protoc` by
  56. /// setting the `ReflectionData` option to `True`. The paths provided should be absolute or relative to the
  57. /// current working directory.
  58. ///
  59. /// - Parameter filePaths: The paths to files containing serialized reflection data.
  60. /// - Parameter version: The version of the reflection service to create.
  61. ///
  62. /// - Throws: When a file can't be read from disk or parsed.
  63. public init(reflectionDataFilePaths filePaths: [String], version: Version) throws {
  64. let fileDescriptorProtos = try ReflectionService.readSerializedFileDescriptorProtos(
  65. atPaths: filePaths
  66. )
  67. switch version.wrapped {
  68. case .v1:
  69. self.provider = .v1(
  70. try ReflectionServiceProviderV1(fileDescriptorProtos: fileDescriptorProtos)
  71. )
  72. case .v1Alpha:
  73. self.provider = .v1Alpha(
  74. try ReflectionServiceProviderV1Alpha(fileDescriptorProtos: fileDescriptorProtos)
  75. )
  76. }
  77. }
  78. public init(fileDescriptorProtos: [Google_Protobuf_FileDescriptorProto], version: Version) throws
  79. {
  80. switch version.wrapped {
  81. case .v1:
  82. self.provider = .v1(
  83. try ReflectionServiceProviderV1(fileDescriptorProtos: fileDescriptorProtos)
  84. )
  85. case .v1Alpha:
  86. self.provider = .v1Alpha(
  87. try ReflectionServiceProviderV1Alpha(fileDescriptorProtos: fileDescriptorProtos)
  88. )
  89. }
  90. }
  91. public func handle(
  92. method name: Substring,
  93. context: GRPC.CallHandlerContext
  94. ) -> GRPC.GRPCServerHandlerProtocol? {
  95. switch self.provider {
  96. case .v1(let reflectionV1Provider):
  97. return reflectionV1Provider.handle(method: name, context: context)
  98. case .v1Alpha(let reflectionV1AlphaProvider):
  99. return reflectionV1AlphaProvider.handle(method: name, context: context)
  100. }
  101. }
  102. }
  103. internal struct ReflectionServiceData: Sendable {
  104. internal struct FileDescriptorProtoData: Sendable {
  105. internal var serializedFileDescriptorProto: Data
  106. internal var dependencyFileNames: [String]
  107. }
  108. private struct ExtensionDescriptor: Sendable, Hashable {
  109. internal let extendeeTypeName: String
  110. internal let fieldNumber: Int32
  111. }
  112. internal var fileDescriptorDataByFilename: [String: FileDescriptorProtoData]
  113. internal var serviceNames: [String]
  114. internal var fileNameBySymbol: [String: String]
  115. // Stores the file names for each extension identified by an ExtensionDescriptor object.
  116. private var fileNameByExtensionDescriptor: [ExtensionDescriptor: String]
  117. // Stores the field numbers for each type that has extensions.
  118. private var fieldNumbersByType: [String: [Int32]]
  119. internal init(fileDescriptors: [Google_Protobuf_FileDescriptorProto]) throws {
  120. self.serviceNames = []
  121. self.fileDescriptorDataByFilename = [:]
  122. self.fileNameBySymbol = [:]
  123. self.fileNameByExtensionDescriptor = [:]
  124. self.fieldNumbersByType = [:]
  125. for fileDescriptorProto in fileDescriptors {
  126. let serializedFileDescriptorProto: Data
  127. do {
  128. serializedFileDescriptorProto = try fileDescriptorProto.serializedData()
  129. } catch {
  130. throw GRPCStatus(
  131. code: .invalidArgument,
  132. message:
  133. "The \(fileDescriptorProto.name) could not be serialized."
  134. )
  135. }
  136. let protoData = FileDescriptorProtoData(
  137. serializedFileDescriptorProto: serializedFileDescriptorProto,
  138. dependencyFileNames: fileDescriptorProto.dependency
  139. )
  140. self.fileDescriptorDataByFilename[fileDescriptorProto.name] = protoData
  141. self.serviceNames.append(
  142. contentsOf: fileDescriptorProto.service.map { fileDescriptorProto.package + "." + $0.name }
  143. )
  144. // Populating the <symbol, file name> dictionary.
  145. for qualifiedSybolName in fileDescriptorProto.qualifiedSymbolNames {
  146. let oldValue = self.fileNameBySymbol.updateValue(
  147. fileDescriptorProto.name,
  148. forKey: qualifiedSybolName
  149. )
  150. if let oldValue = oldValue {
  151. throw GRPCStatus(
  152. code: .alreadyExists,
  153. message:
  154. "The \(qualifiedSybolName) symbol from \(fileDescriptorProto.name) already exists in \(oldValue)."
  155. )
  156. }
  157. }
  158. for typeName in fileDescriptorProto.qualifiedMessageTypes {
  159. self.fieldNumbersByType[typeName] = []
  160. }
  161. // Populating the <extension descriptor, file name> dictionary and the <typeName, [FieldNumber]> one.
  162. for `extension` in fileDescriptorProto.extension {
  163. let typeName = String(`extension`.extendee.drop(while: { $0 == "." }))
  164. let extensionDescriptor = ExtensionDescriptor(
  165. extendeeTypeName: typeName,
  166. fieldNumber: `extension`.number
  167. )
  168. let oldFileName = self.fileNameByExtensionDescriptor.updateValue(
  169. fileDescriptorProto.name,
  170. forKey: extensionDescriptor
  171. )
  172. if let oldFileName = oldFileName {
  173. throw GRPCStatus(
  174. code: .alreadyExists,
  175. message:
  176. """
  177. The extension of the \(extensionDescriptor.extendeeTypeName) type with the field number equal to \
  178. \(extensionDescriptor.fieldNumber) from \(fileDescriptorProto.name) already exists in \(oldFileName).
  179. """
  180. )
  181. }
  182. self.fieldNumbersByType[typeName, default: []].append(`extension`.number)
  183. }
  184. }
  185. }
  186. internal func serialisedFileDescriptorProtosForDependenciesOfFile(
  187. named fileName: String
  188. ) -> Result<[Data], GRPCStatus> {
  189. var toVisit = Deque<String>()
  190. var visited = Set<String>()
  191. var serializedFileDescriptorProtos: [Data] = []
  192. toVisit.append(fileName)
  193. while let currentFileName = toVisit.popFirst() {
  194. if let protoData = self.fileDescriptorDataByFilename[currentFileName] {
  195. toVisit.append(
  196. contentsOf: protoData.dependencyFileNames
  197. .filter { name in
  198. return !visited.contains(name)
  199. }
  200. )
  201. let serializedFileDescriptorProto = protoData.serializedFileDescriptorProto
  202. serializedFileDescriptorProtos.append(serializedFileDescriptorProto)
  203. } else {
  204. return .failure(
  205. GRPCStatus(
  206. code: .notFound,
  207. message: "The provided file or a dependency of the provided file could not be found."
  208. )
  209. )
  210. }
  211. visited.insert(currentFileName)
  212. }
  213. return .success(serializedFileDescriptorProtos)
  214. }
  215. internal func nameOfFileContainingSymbol(named symbolName: String) -> Result<String, GRPCStatus> {
  216. guard let fileName = self.fileNameBySymbol[symbolName] else {
  217. return .failure(
  218. GRPCStatus(
  219. code: .notFound,
  220. message: "The provided symbol could not be found."
  221. )
  222. )
  223. }
  224. return .success(fileName)
  225. }
  226. internal func nameOfFileContainingExtension(
  227. extendeeName: String,
  228. fieldNumber number: Int32
  229. ) -> Result<String, GRPCStatus> {
  230. let key = ExtensionDescriptor(extendeeTypeName: extendeeName, fieldNumber: number)
  231. guard let fileName = self.fileNameByExtensionDescriptor[key] else {
  232. return .failure(
  233. GRPCStatus(
  234. code: .notFound,
  235. message: "The provided extension could not be found."
  236. )
  237. )
  238. }
  239. return .success(fileName)
  240. }
  241. // Returns an empty array if the type has no extensions.
  242. internal func extensionsFieldNumbersOfType(
  243. named typeName: String
  244. ) -> Result<[Int32], GRPCStatus> {
  245. guard let fieldNumbers = self.fieldNumbersByType[typeName] else {
  246. return .failure(
  247. GRPCStatus(
  248. code: .invalidArgument,
  249. message: "The provided type is invalid."
  250. )
  251. )
  252. }
  253. return .success(fieldNumbers)
  254. }
  255. }
  256. extension Google_Protobuf_FileDescriptorProto {
  257. var qualifiedServiceAndMethodNames: [String] {
  258. var names: [String] = []
  259. for service in self.service {
  260. names.append(self.package + "." + service.name)
  261. names.append(
  262. contentsOf: service.method
  263. .map { self.package + "." + service.name + "." + $0.name }
  264. )
  265. }
  266. return names
  267. }
  268. var qualifiedMessageTypes: [String] {
  269. return self.messageType.map {
  270. self.package + "." + $0.name
  271. }
  272. }
  273. var qualifiedEnumTypes: [String] {
  274. return self.enumType.map {
  275. self.package + "." + $0.name
  276. }
  277. }
  278. var qualifiedSymbolNames: [String] {
  279. var names = self.qualifiedServiceAndMethodNames
  280. names.append(contentsOf: self.qualifiedMessageTypes)
  281. names.append(contentsOf: self.qualifiedEnumTypes)
  282. return names
  283. }
  284. }
  285. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  286. extension ReflectionService {
  287. /// The version of the reflection service.
  288. ///
  289. /// Depending in the version you are using, when creating the ReflectionService
  290. /// provide the corresponding `Version` variable (`v1` or `v1Alpha`).
  291. public struct Version: Sendable, Hashable {
  292. internal enum Wrapped {
  293. case v1
  294. case v1Alpha
  295. }
  296. var wrapped: Wrapped
  297. private init(_ wrapped: Wrapped) { self.wrapped = wrapped }
  298. /// The v1 version of reflection service: https://github.com/grpc/grpc/blob/master/src/proto/grpc/reflection/v1/reflection.proto.
  299. public static var v1: Self { Self(.v1) }
  300. /// The v1alpha version of reflection service: https://github.com/grpc/grpc/blob/master/src/proto/grpc/reflection/v1alpha/reflection.proto.
  301. public static var v1Alpha: Self { Self(.v1Alpha) }
  302. }
  303. private enum Provider {
  304. case v1(ReflectionServiceProviderV1)
  305. case v1Alpha(ReflectionServiceProviderV1Alpha)
  306. }
  307. }
  308. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  309. extension ReflectionService {
  310. static func readSerializedFileDescriptorProto(
  311. atPath path: String
  312. ) throws -> Google_Protobuf_FileDescriptorProto {
  313. let fileURL: URL
  314. #if os(Linux)
  315. fileURL = URL(fileURLWithPath: path)
  316. #else
  317. if #available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *) {
  318. fileURL = URL(filePath: path, directoryHint: .notDirectory)
  319. } else {
  320. fileURL = URL(fileURLWithPath: path)
  321. }
  322. #endif
  323. let binaryData = try Data(contentsOf: fileURL)
  324. guard let serializedData = Data(base64Encoded: binaryData) else {
  325. throw GRPCStatus(
  326. code: .invalidArgument,
  327. message:
  328. """
  329. The \(path) file contents could not be transformed \
  330. into serialized data representing a file descriptor proto.
  331. """
  332. )
  333. }
  334. return try Google_Protobuf_FileDescriptorProto(serializedData: serializedData)
  335. }
  336. static func readSerializedFileDescriptorProtos(
  337. atPaths paths: [String]
  338. ) throws -> [Google_Protobuf_FileDescriptorProto] {
  339. var fileDescriptorProtos = [Google_Protobuf_FileDescriptorProto]()
  340. fileDescriptorProtos.reserveCapacity(paths.count)
  341. for path in paths {
  342. try fileDescriptorProtos.append(readSerializedFileDescriptorProto(atPath: path))
  343. }
  344. return fileDescriptorProtos
  345. }
  346. }