瀏覽代碼

Added ReflectionService initialiser using file paths (#1699)

Motivation:

Initializing the Reflection Service using file descriptor protos is too laborious for users,
so simply passing the file paths of the binary files containing the serialized file descriptor protos
and letting the Service extract the data from those paths is more convenient.

Modifications:

Added a new initializer that takes in file paths and extracts and converts to file descriptor proto format the Data
from the files. Also modified the integration tests to create temporary files containing the serialized file descriptor protos.

Result:

GRPCUsers can add the ReflectionService to their Server in a more convenient way.
Stefana-Ioana Dranca 2 年之前
父節點
當前提交
55196026c0

+ 59 - 0
Sources/GRPCReflectionService/Server/ReflectionService.swift

@@ -26,6 +26,24 @@ public final class ReflectionService: CallHandlerProvider, Sendable {
     self.reflectionService.serviceName
   }
 
+  /// Creates a `ReflectionService` by loading serialized reflection data created by `protoc-gen-grpc-swift`.
+  ///
+  /// You can generate serialized reflection data using the `protoc-gen-grpc-swift` plugin for `protoc` by
+  /// setting the `ReflectionData` option  to `True`. The paths provided should be absolute or relative to the
+  /// current working directory.
+  ///
+  /// - Parameter filePaths: The paths to files containing serialized reflection data.
+  ///
+  /// - Throws: When a file can't be read from disk or parsed.
+  public init(serializedFileDescriptorProtoFilePaths filePaths: [String]) throws {
+    let fileDescriptorProtos = try ReflectionService.readSerializedFileDescriptorProtos(
+      atPaths: filePaths
+    )
+    self.reflectionService = try ReflectionServiceProvider(
+      fileDescriptorProtos: fileDescriptorProtos
+    )
+  }
+
   public init(fileDescriptors: [Google_Protobuf_FileDescriptorProto]) throws {
     self.reflectionService = try ReflectionServiceProvider(fileDescriptorProtos: fileDescriptors)
   }
@@ -425,3 +443,44 @@ where Success == Grpc_Reflection_V1_ServerReflectionResponse.OneOf_MessageRespon
     }
   }
 }
+
+@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
+extension ReflectionService {
+  static func readSerializedFileDescriptorProto(
+    atPath path: String
+  ) throws -> Google_Protobuf_FileDescriptorProto {
+    let fileURL: URL
+    #if os(Linux)
+    fileURL = URL(fileURLWithPath: path)
+    #else
+    if #available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *) {
+      fileURL = URL(filePath: path, directoryHint: .notDirectory)
+    } else {
+      fileURL = URL(fileURLWithPath: path)
+    }
+    #endif
+    let binaryData = try Data(contentsOf: fileURL)
+    guard let serializedData = Data(base64Encoded: binaryData) else {
+      throw GRPCStatus(
+        code: .invalidArgument,
+        message:
+          """
+          The \(path) file contents could not be transformed \
+          into serialized data representing a file descriptor proto.
+          """
+      )
+    }
+    return try Google_Protobuf_FileDescriptorProto(serializedData: serializedData)
+  }
+
+  static func readSerializedFileDescriptorProtos(
+    atPaths paths: [String]
+  ) throws -> [Google_Protobuf_FileDescriptorProto] {
+    var fileDescriptorProtos = [Google_Protobuf_FileDescriptorProto]()
+    fileDescriptorProtos.reserveCapacity(paths.count)
+    for path in paths {
+      try fileDescriptorProtos.append(readSerializedFileDescriptorProto(atPath: path))
+    }
+    return fileDescriptorProtos
+  }
+}

+ 76 - 0
Tests/GRPCTests/GRPCReflectionServiceTests/ReflectionServiceUnitTests.swift

@@ -519,4 +519,80 @@ final class ReflectionServiceUnitTests: GRPCTestCase {
 
     XCTAssertEqual(try extensionsFieldNumbersOfTypeResult.get(), [1, 2, 3, 4, 5, 130])
   }
+
+  func testReadSerializedFileDescriptorProto() throws {
+    let initialFileDescriptorProto = generateFileDescriptorProto(fileName: "test", suffix: "1")
+    let data = try initialFileDescriptorProto.serializedData().base64EncodedData()
+    #if os(Linux)
+    let temporaryDirectory = "/tmp/"
+    #else
+    let temporaryDirectory = FileManager.default.temporaryDirectory.path()
+    #endif
+    let filePath = "\(temporaryDirectory)test\(UUID()).grpc.reflection"
+    FileManager.default.createFile(atPath: filePath, contents: data)
+    defer {
+      XCTAssertNoThrow(try FileManager.default.removeItem(atPath: filePath))
+    }
+    let reflectionServiceFileDescriptorProto =
+      try ReflectionService.readSerializedFileDescriptorProto(atPath: filePath)
+    XCTAssertEqual(reflectionServiceFileDescriptorProto, initialFileDescriptorProto)
+  }
+
+  func testReadSerializedFileDescriptorProtoInvalidFileContents() throws {
+    let invalidData = "%%%%%££££".data(using: .utf8)
+    #if os(Linux)
+    let temporaryDirectory = "/tmp/"
+    #else
+    let temporaryDirectory = FileManager.default.temporaryDirectory.path()
+    #endif
+    let filePath = "\(temporaryDirectory)test\(UUID()).grpc.reflection"
+    FileManager.default.createFile(atPath: filePath, contents: invalidData)
+    defer {
+      XCTAssertNoThrow(try FileManager.default.removeItem(atPath: filePath))
+    }
+
+    XCTAssertThrowsGRPCStatus(
+      try ReflectionService.readSerializedFileDescriptorProto(atPath: filePath)
+    ) {
+      status in
+      XCTAssertEqual(
+        status,
+        GRPCStatus(
+          code: .invalidArgument,
+          message:
+            """
+            The \(filePath) file contents could not be transformed \
+            into serialized data representing a file descriptor proto.
+            """
+        )
+      )
+    }
+  }
+
+  func testReadSerializedFileDescriptorProtos() throws {
+    let initialFileDescriptorProtos = makeProtosWithDependencies()
+    var filePaths: [String] = []
+
+    for initialFileDescriptorProto in initialFileDescriptorProtos {
+      let data = try initialFileDescriptorProto.serializedData()
+        .base64EncodedData()
+      #if os(Linux)
+      let temporaryDirectory = "/tmp/"
+      #else
+      let temporaryDirectory = FileManager.default.temporaryDirectory.path()
+      #endif
+      let filePath = "\(temporaryDirectory)test\(UUID()).grpc.reflection"
+      FileManager.default.createFile(atPath: filePath, contents: data)
+      filePaths.append(filePath)
+    }
+    defer {
+      for filePath in filePaths {
+        XCTAssertNoThrow(try FileManager.default.removeItem(atPath: filePath))
+      }
+    }
+
+    let reflectionServiceFileDescriptorProtos =
+      try ReflectionService.readSerializedFileDescriptorProtos(atPaths: filePaths)
+    XCTAssertEqual(reflectionServiceFileDescriptorProtos, initialFileDescriptorProtos)
+  }
 }