Skip to content

Commit

Permalink
Tool that generates serialised file descriptor protos (#1654)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
stefanadranca authored Oct 2, 2023
1 parent d576a74 commit 09c46b0
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 18 deletions.
14 changes: 14 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ extension Target {
),
exclude: [
"Codegen/Normalization/normalization.proto",
"Codegen/Serialization/echo.grpc.reflection.txt",
]
)

Expand Down
59 changes: 41 additions & 18 deletions Sources/protoc-gen-grpc-swift/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
}
}

Expand Down
8 changes: 8 additions & 0 deletions Sources/protoc-gen-grpc-swift/options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
}
Expand Down
85 changes: 85 additions & 0 deletions Tests/GRPCTests/Codegen/Serialization/SerializationTests.swift
Original file line number Diff line number Diff line change
@@ -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.")
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CgplY2hvLnByb3RvEgRlY2hvIiEKC0VjaG9SZXF1ZXN0EhIKBHRleHQYASABKAlSBHRleHQiIgoMRWNob1Jlc3BvbnNlEhIKBHRleHQYASABKAlSBHRleHQy2AEKBEVjaG8SLgoDR2V0EhEuZWNoby5FY2hvUmVxdWVzdBoSLmVjaG8uRWNob1Jlc3BvbnNlIgASMwoGRXhwYW5kEhEuZWNoby5FY2hvUmVxdWVzdBoSLmVjaG8uRWNob1Jlc3BvbnNlIgAwARI0CgdDb2xsZWN0EhEuZWNoby5FY2hvUmVxdWVzdBoSLmVjaG8uRWNob1Jlc3BvbnNlIgAoARI1CgZVcGRhdGUSES5lY2hvLkVjaG9SZXF1ZXN0GhIuZWNoby5FY2hvUmVzcG9uc2UiACgBMAFK/QoKBhIEDgAoAQrCBAoBDBIDDgASMrcEIENvcHlyaWdodCAoYykgMjAxNSwgR29vZ2xlIEluYy4KCiBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIHlvdSBtYXkgbm90IHVzZSB0aGlzIGZpbGUgZXhjZXB0IGluIGNvbXBsaWFuY2Ugd2l0aCB0aGUgTGljZW5zZS4KIFlvdSBtYXkgb2J0YWluIGEgY29weSBvZiB0aGUgTGljZW5zZSBhdAoKICAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjAKCiBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiAiQVMgSVMiIEJBU0lTLAogV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiBsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS4KCggKAQISAxAADQoKCgIGABIEEgAeAQoKCgMGAAESAxIIDAo4CgQGAAIAEgMUAjAaKyBJbW1lZGlhdGVseSByZXR1cm5zIGFuIGVjaG8gb2YgYSByZXF1ZXN0LgoKDAoFBgACAAESAxQGCQoMCgUGAAIAAhIDFAoVCgwKBQYAAgADEgMUICwKWQoEBgACARIDFwI6GkwgU3BsaXRzIGEgcmVxdWVzdCBpbnRvIHdvcmRzIGFuZCByZXR1cm5zIGVhY2ggd29yZCBpbiBhIHN0cmVhbSBvZiBtZXNzYWdlcy4KCgwKBQYAAgEBEgMXBgwKDAoFBgACAQISAxcNGAoMCgUGAAIBBhIDFyMpCgwKBQYAAgEDEgMXKjYKYgoEBgACAhIDGgI7GlUgQ29sbGVjdHMgYSBzdHJlYW0gb2YgbWVzc2FnZXMgYW5kIHJldHVybnMgdGhlbSBjb25jYXRlbmF0ZWQgd2hlbiB0aGUgY2FsbGVyIGNsb3Nlcy4KCgwKBQYAAgIBEgMaBg0KDAoFBgACAgUSAxoOFAoMCgUGAAICAhIDGhUgCgwKBQYAAgIDEgMaKzcKTQoEBgACAxIDHQJBGkAgU3RyZWFtcyBiYWNrIG1lc3NhZ2VzIGFzIHRoZXkgYXJlIHJlY2VpdmVkIGluIGFuIGlucHV0IHN0cmVhbS4KCgwKBQYAAgMBEgMdBgwKDAoFBgACAwUSAx0NEwoMCgUGAAIDAhIDHRQfCgwKBQYAAgMGEgMdKjAKDAoFBgACAwMSAx0xPQoKCgIEABIEIAAjAQoKCgMEAAESAyAIEwoyCgQEAAIAEgMiAhIaJSBUaGUgdGV4dCBvZiBhIG1lc3NhZ2UgdG8gYmUgZWNob2VkLgoKDAoFBAACAAUSAyICCAoMCgUEAAIAARIDIgkNCgwKBQQAAgADEgMiEBEKCgoCBAESBCUAKAEKCgoDBAEBEgMlCBQKLAoEBAECABIDJwISGh8gVGhlIHRleHQgb2YgYW4gZWNobyByZXNwb25zZS4KCgwKBQQBAgAFEgMnAggKDAoFBAECAAESAycJDQoMCgUEAQIAAxIDJxARYgZwcm90bzM=

0 comments on commit 09c46b0

Please sign in to comment.