Browse Source

Tool that generates serialised file descriptor protos (#1654)

Motivation:

In order to implement the reflection service we need to be able to generate serialised file descriptor protos of the
services and messages that a server offers.

Modifications:

Added an option for protoc-gen-grpc-swift that enables generating a binary file containing a base64 encoded representation of the  serialized file descriptor proto of the specified proto file.

Result:

This change enables the generation of serialised file descriptor protos that will be useful in the implementation of the reflection service.
Stefana-Ioana Dranca 2 years ago
parent
commit
09c46b0a65

+ 14 - 0
Makefile

@@ -117,6 +117,20 @@ ${NORMALIZATION_GRPC}: ${NORMALIZATION_PROTO} ${PROTOC_GEN_GRPC_SWIFT}
 .PHONY:
 generate-normalization: ${NORMALIZATION_PB} ${NORMALIZATION_GRPC}
 
+SERIALIZATION_GRPC_REFLECTION=Tests/GRPCTests/Codegen/Serialization/echo.grpc.reflection.txt
+
+# For serialization we'll set the ReflectionData option to true.
+${SERIALIZATION_GRPC_REFLECTION}: ${ECHO_PROTO} ${PROTOC_GEN_GRPC_SWIFT}
+	protoc $< \
+		--proto_path=$(dir $<) \
+		--plugin=${PROTOC_GEN_GRPC_SWIFT} \
+		--grpc-swift_opt=Client=false,Server=false,ReflectionData=true \
+		--grpc-swift_out=$(dir ${SERIALIZATION_GRPC_REFLECTION})
+
+# Generates binary file containing the serialized file descriptor proto for the Serialization test
+.PHONY:
+generate-reflection-data: ${SERIALIZATION_GRPC_REFLECTION}
+
 ### Testing ####################################################################
 
 # Normal test suite.

+ 1 - 0
Package.swift

@@ -205,6 +205,7 @@ extension Target {
     ),
     exclude: [
       "Codegen/Normalization/normalization.proto",
+      "Codegen/Serialization/echo.grpc.reflection.txt",
     ]
   )
 

+ 41 - 18
Sources/protoc-gen-grpc-swift/main.swift

@@ -64,9 +64,10 @@ enum FileNaming: String {
 func outputFileName(
   component: String,
   fileDescriptor: FileDescriptor,
-  fileNamingOption: FileNaming
+  fileNamingOption: FileNaming,
+  extension: String
 ) -> String {
-  let ext = "." + component + ".swift"
+  let ext = "." + component + "." + `extension`
   let pathParts = splitPath(pathname: fileDescriptor.name)
   switch fileNamingOption {
   case .FullPath:
@@ -84,19 +85,22 @@ func uniqueOutputFileName(
   component: String,
   fileDescriptor: FileDescriptor,
   fileNamingOption: FileNaming,
-  generatedFiles: inout [String: Int]
+  generatedFiles: inout [String: Int],
+  extension: String = "swift"
 ) -> String {
   let defaultName = outputFileName(
     component: component,
     fileDescriptor: fileDescriptor,
-    fileNamingOption: fileNamingOption
+    fileNamingOption: fileNamingOption,
+    extension: `extension`
   )
   if let count = generatedFiles[defaultName] {
     generatedFiles[defaultName] = count + 1
     return outputFileName(
       component: "\(count)." + component,
       fileDescriptor: fileDescriptor,
-      fileNamingOption: fileNamingOption
+      fileNamingOption: fileNamingOption,
+      extension: `extension`
     )
   } else {
     generatedFiles[defaultName] = 1
@@ -136,19 +140,38 @@ func main(args: [String]) throws {
 
   // Only generate output for services.
   for name in request.fileToGenerate {
-    if let fileDescriptor = descriptorSet.fileDescriptor(named: name),
-       !fileDescriptor.services.isEmpty {
-      let grpcFileName = uniqueOutputFileName(
-        component: "grpc",
-        fileDescriptor: fileDescriptor,
-        fileNamingOption: options.fileNaming,
-        generatedFiles: &generatedFiles
-      )
-      let grpcGenerator = Generator(fileDescriptor, options: options)
-      var grpcFile = Google_Protobuf_Compiler_CodeGeneratorResponse.File()
-      grpcFile.name = grpcFileName
-      grpcFile.content = grpcGenerator.code
-      response.file.append(grpcFile)
+    if let fileDescriptor = descriptorSet.fileDescriptor(named: name) {
+      if (options.generateReflectionData) {
+        var binaryFile = Google_Protobuf_Compiler_CodeGeneratorResponse.File()
+        let binaryFileName = uniqueOutputFileName(
+          component: "grpc.reflection",
+          fileDescriptor: fileDescriptor,
+          fileNamingOption: options.fileNaming,
+          generatedFiles: &generatedFiles,
+          extension: "txt"
+        )
+        let serializedFileDescriptorProto = try fileDescriptor.proto.serializedData()
+          .base64EncodedString()
+        binaryFile.name = binaryFileName
+        binaryFile.content = serializedFileDescriptorProto
+        response.file.append(binaryFile)
+      }
+      if (
+        !fileDescriptor.services
+          .isEmpty && (options.generateClient || options.generateServer)
+      ) {
+        var grpcFile = Google_Protobuf_Compiler_CodeGeneratorResponse.File()
+        let grpcFileName = uniqueOutputFileName(
+          component: "grpc",
+          fileDescriptor: fileDescriptor,
+          fileNamingOption: options.fileNaming,
+          generatedFiles: &generatedFiles
+        )
+        let grpcGenerator = Generator(fileDescriptor, options: options)
+        grpcFile.name = grpcFileName
+        grpcFile.content = grpcGenerator.code
+        response.file.append(grpcFile)
+      }
     }
   }
 

+ 8 - 0
Sources/protoc-gen-grpc-swift/options.swift

@@ -64,6 +64,7 @@ final class GeneratorOptions {
   private(set) var extraModuleImports: [String] = []
   private(set) var gRPCModuleName = "GRPC"
   private(set) var swiftProtobufModuleName = "SwiftProtobuf"
+  private(set) var generateReflectionData = false
 
   init(parameter: String?) throws {
     for pair in GeneratorOptions.parseParameter(string: parameter) {
@@ -143,6 +144,13 @@ final class GeneratorOptions {
           throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
         }
 
+      case "ReflectionData":
+        if let value = Bool(pair.value) {
+          self.generateReflectionData = value
+        } else {
+          throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
+        }
+
       default:
         throw GenerationError.unknownParameter(name: pair.key)
       }

+ 85 - 0
Tests/GRPCTests/Codegen/Serialization/SerializationTests.swift

@@ -0,0 +1,85 @@
+/*
+ * Copyright 2023, gRPC Authors All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Foundation
+import GRPC
+import SwiftProtobuf
+import XCTest
+
+final class SerializationTests: GRPCTestCase {
+  var fileDescriptorProto: Google_Protobuf_FileDescriptorProto!
+
+  override func setUp() {
+    super.setUp()
+    let binaryFileURL = URL(fileURLWithPath: #filePath)
+      .deletingLastPathComponent().appendingPathComponent("echo.grpc.reflection.txt")
+    let base64EncodedData = try! Data(contentsOf: binaryFileURL)
+    let binaryData = Data(base64Encoded: base64EncodedData)!
+    self
+      .fileDescriptorProto =
+      try! Google_Protobuf_FileDescriptorProto(serializedData: binaryData)
+  }
+
+  func testFileDescriptorMetadata() throws {
+    let name = self.fileDescriptorProto.name
+    XCTAssertEqual(name, "echo.proto")
+
+    let syntax = self.fileDescriptorProto.syntax
+    XCTAssertEqual(syntax, "proto3")
+
+    let package = self.fileDescriptorProto.package
+    XCTAssertEqual(package, "echo")
+  }
+
+  func testFileDescriptorMessages() {
+    let messages = self.fileDescriptorProto.messageType
+    XCTAssertEqual(messages.count, 2)
+    for message in messages {
+      XCTAssert((message.name == "EchoRequest") || (message.name == "EchoResponse"))
+      XCTAssertEqual(message.field.count, 1)
+      XCTAssertEqual(message.field.first!.name, "text")
+      XCTAssert(message.field.first!.hasNumber)
+    }
+  }
+
+  func testFileDescriptorServices() {
+    let services = self.fileDescriptorProto.service
+    XCTAssertEqual(services.count, 1)
+    XCTAssertEqual(self.fileDescriptorProto.service.first!.method.count, 4)
+    for method in self.fileDescriptorProto.service.first!.method {
+      switch method.name {
+      case "Get":
+        XCTAssertEqual(method.inputType, ".echo.EchoRequest")
+        XCTAssertEqual(method.outputType, ".echo.EchoResponse")
+      case "Expand":
+        XCTAssertEqual(method.inputType, ".echo.EchoRequest")
+        XCTAssertEqual(method.outputType, ".echo.EchoResponse")
+        XCTAssert(method.serverStreaming)
+      case "Collect":
+        XCTAssertEqual(method.inputType, ".echo.EchoRequest")
+        XCTAssertEqual(method.outputType, ".echo.EchoResponse")
+        XCTAssert(method.clientStreaming)
+      case "Update":
+        XCTAssertEqual(method.inputType, ".echo.EchoRequest")
+        XCTAssertEqual(method.outputType, ".echo.EchoResponse")
+        XCTAssert(method.clientStreaming)
+        XCTAssert(method.serverStreaming)
+      default:
+        XCTFail("The method name is incorrect.")
+      }
+    }
+  }
+}

File diff suppressed because it is too large
+ 0 - 0
Tests/GRPCTests/Codegen/Serialization/echo.grpc.reflection.txt


Some files were not shown because too many files changed in this diff