ReflectionService.swift 13 KB

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