Skip to content

Commit

Permalink
Adding support for v1alpha reflection (#1703)
Browse files Browse the repository at this point in the history
Motivation:

v1alpha reflection is also used by GRPC users, so we should provide support for it.

Modifications:

- Generated the .grpc and .pb files for the v1alpha reflection.
- Added an extension for the ReflectionService that represents the version, that is set by the users.
- Created an enum to represent the 2 possible Reflection Service Providers and changed the methods that
were calling provider's methods to switch between the possible cases.

Result:

Users will be able to set the version for the Reflection Service that they use, v1alpha becoming
a possibility.
  • Loading branch information
stefanadranca authored Nov 9, 2023
1 parent cddc12d commit 187b609
Show file tree
Hide file tree
Showing 17 changed files with 3,049 additions and 414 deletions.
56 changes: 42 additions & 14 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -131,42 +131,70 @@ ${SERIALIZATION_GRPC_REFLECTION}: ${ECHO_PROTO} ${PROTOC_GEN_GRPC_SWIFT}
.PHONY:
generate-reflection-data: ${SERIALIZATION_GRPC_REFLECTION}

REFLECTION_PROTO=Sources/GRPCReflectionService/Model/reflection.proto
REFLECTION_PB=$(REFLECTION_PROTO:.proto=.pb.swift)
REFLECTION_GRPC=$(REFLECTION_PROTO:.proto=.grpc.swift)
REFLECTION_V1_PROTO=Sources/GRPCReflectionService/v1/reflection-v1.proto
REFLECTION_V1_PB=$(REFLECTION_V1_PROTO:.proto=.pb.swift)
REFLECTION_V1_GRPC=$(REFLECTION_V1_PROTO:.proto=.grpc.swift)

REFLECTION_V1ALPHA_PROTO=Sources/GRPCReflectionService/v1Alpha/reflection-v1alpha.proto
REFLECTION_V1ALPHA_PB=$(REFLECTION_V1ALPHA_PROTO:.proto=.pb.swift)
REFLECTION_V1ALPHA_GRPC=$(REFLECTION_V1ALPHA_PROTO:.proto=.grpc.swift)

# For Reflection we'll generate only the Server code.
${REFLECTION_GRPC}: ${REFLECTION_PROTO} ${PROTOC_GEN_GRPC_SWIFT}
${REFLECTION_V1_GRPC}: ${REFLECTION_V1_PROTO} ${PROTOC_GEN_GRPC_SWIFT}
protoc $< \
--proto_path=$(dir $<) \
--plugin=${PROTOC_GEN_GRPC_SWIFT} \
--grpc-swift_opt=Client=false \
--grpc-swift_out=$(dir $<)

# For Reflection we'll generate only the Server code.
${REFLECTION_V1ALPHA_GRPC}: ${REFLECTION_V1ALPHA_PROTO} ${PROTOC_GEN_GRPC_SWIFT}
protoc $< \
--proto_path=$(dir $<) \
--plugin=${PROTOC_GEN_GRPC_SWIFT} \
--grpc-swift_opt=Client=false \
--grpc-swift_out=$(dir $<)

# Generates protobufs and gRPC server for the Reflection Service
.PHONY:
generate-reflection: ${REFLECTION_PB} ${REFLECTION_GRPC}
generate-reflection: ${REFLECTION_V1_PB} ${REFLECTION_V1_GRPC} ${REFLECTION_V1ALPHA_PB} ${REFLECTION_V1ALPHA_GRPC}

TEST_REFLECTION_GRPC=Tests/GRPCTests/GRPCReflectionServiceTests/Generated/reflection.grpc.swift
TEST_REFLECTION_PB=Tests/GRPCTests/GRPCReflectionServiceTests/Generated/reflection.pb.swift
TEST_REFLECTION_V1_GRPC=Tests/GRPCTests/GRPCReflectionServiceTests/Generated/v1/reflection-v1.grpc.swift
TEST_REFLECTION_V1_PB=Tests/GRPCTests/GRPCReflectionServiceTests/Generated/v1/reflection-v1.pb.swift
TEST_REFLECTION_V1ALPHA_GRPC=Tests/GRPCTests/GRPCReflectionServiceTests/Generated/v1Alpha/reflection-v1alpha.grpc.swift
TEST_REFLECTION_V1ALPHA_PB=Tests/GRPCTests/GRPCReflectionServiceTests/Generated/v1Alpha/reflection-v1alpha.pb.swift

# For Reflection we'll generate only the Server code.
${TEST_REFLECTION_GRPC}: ${REFLECTION_PROTO} ${PROTOC_GEN_GRPC_SWIFT}
# For Testing the Reflection we'll generate only the Client code.
${TEST_REFLECTION_V1_GRPC}: ${REFLECTION_V1_PROTO} ${PROTOC_GEN_GRPC_SWIFT}
protoc $< \
--proto_path=$(dir $<) \
--plugin=${PROTOC_GEN_GRPC_SWIFT} \
--grpc-swift_opt=Client=true,Server=false \
--grpc-swift_out=$(dir ${TEST_REFLECTION_V1_GRPC})

${TEST_REFLECTION_V1_PB}: ${REFLECTION_V1_PROTO} ${PROTOC_GEN_SWIFT}
protoc $< \
--proto_path=$(dir $<) \
--plugin=${PROTOC_GEN_SWIFT} \
--swift_out=$(dir ${TEST_REFLECTION_V1_PB})

# For Testing the Reflection we'll generate only the Client code.
${TEST_REFLECTION_V1ALPHA_GRPC}: ${REFLECTION_V1ALPHA_PROTO} ${PROTOC_GEN_GRPC_SWIFT}
protoc $< \
--proto_path=$(dir $<) \
--plugin=${PROTOC_GEN_GRPC_SWIFT} \
--grpc-swift_opt=Client=true,Server=false \
--grpc-swift_out=$(dir ${TEST_REFLECTION_GRPC})
--grpc-swift_out=$(dir ${TEST_REFLECTION_V1ALPHA_GRPC})

${TEST_REFLECTION_PB}: ${REFLECTION_PROTO} ${PROTOC_GEN_SWIFT}
${TEST_REFLECTION_V1ALPHA_PB}: ${REFLECTION_V1ALPHA_PROTO} ${PROTOC_GEN_SWIFT}
protoc $< \
--proto_path=$(dir $<) \
--plugin=${PROTOC_GEN_SWIFT} \
--swift_out=$(dir ${TEST_REFLECTION_PB})
--swift_out=$(dir ${TEST_REFLECTION_V1ALPHA_PB})

# Generates protobufs and gRPC client for the Reflection Service Tests
# Generates protobufs and gRPC clients for the Reflection Service Tests
.PHONY:
generate-reflection-client: ${TEST_REFLECTION_PB} ${TEST_REFLECTION_GRPC}
generate-reflection-test-clients: ${TEST_REFLECTION_V1_PB} ${TEST_REFLECTION_V1_GRPC} ${TEST_REFLECTION_V1ALPHA_PB} ${TEST_REFLECTION_V1ALPHA_GRPC}

### Testing ####################################################################

Expand Down
3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,8 @@ extension Target {
],
path: "Sources/GRPCReflectionService",
exclude: [
"Model/reflection.proto",
"v1/reflection-v1.proto",
"v1Alpha/reflection-v1alpha.proto"
]
)
}
Expand Down
248 changes: 57 additions & 191 deletions Sources/GRPCReflectionService/Server/ReflectionService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@ import SwiftProtobuf

@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
public final class ReflectionService: CallHandlerProvider, Sendable {
private let reflectionService: ReflectionServiceProvider
private let provider: Provider

public var serviceName: Substring {
self.reflectionService.serviceName
switch self.provider {
case .v1(let provider):
return provider.serviceName
case .v1Alpha(let provider):
return provider.serviceName
}
}

/// Creates a `ReflectionService` by loading serialized reflection data created by `protoc-gen-grpc-swift`.
Expand All @@ -33,26 +39,49 @@ public final class ReflectionService: CallHandlerProvider, Sendable {
/// current working directory.
///
/// - Parameter filePaths: The paths to files containing serialized reflection data.
/// - Parameter version: The version of the reflection service to create.
///
/// - Throws: When a file can't be read from disk or parsed.
public init(serializedFileDescriptorProtoFilePaths filePaths: [String]) throws {
public init(serializedFileDescriptorProtoFilePaths filePaths: [String], version: Version) throws {
let fileDescriptorProtos = try ReflectionService.readSerializedFileDescriptorProtos(
atPaths: filePaths
)
self.reflectionService = try ReflectionServiceProvider(
fileDescriptorProtos: fileDescriptorProtos
)
switch version.wrapped {
case .v1:
self.provider = .v1(
try ReflectionServiceProviderV1(fileDescriptorProtos: fileDescriptorProtos)
)
case .v1Alpha:
self.provider = .v1Alpha(
try ReflectionServiceProviderV1Alpha(fileDescriptorProtos: fileDescriptorProtos)
)
}
}

public init(fileDescriptors: [Google_Protobuf_FileDescriptorProto]) throws {
self.reflectionService = try ReflectionServiceProvider(fileDescriptorProtos: fileDescriptors)
public init(fileDescriptorProtos: [Google_Protobuf_FileDescriptorProto], version: Version) throws
{
switch version.wrapped {
case .v1:
self.provider = .v1(
try ReflectionServiceProviderV1(fileDescriptorProtos: fileDescriptorProtos)
)
case .v1Alpha:
self.provider = .v1Alpha(
try ReflectionServiceProviderV1Alpha(fileDescriptorProtos: fileDescriptorProtos)
)
}
}

public func handle(
method name: Substring,
context: GRPC.CallHandlerContext
) -> GRPC.GRPCServerHandlerProtocol? {
self.reflectionService.handle(method: name, context: context)
switch self.provider {
case .v1(let reflectionV1Provider):
return reflectionV1Provider.handle(method: name, context: context)
case .v1Alpha(let reflectionV1AlphaProvider):
return reflectionV1AlphaProvider.handle(method: name, context: context)
}
}
}

Expand Down Expand Up @@ -222,163 +251,6 @@ internal struct ReflectionServiceData: Sendable {
}
}

@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
internal final class ReflectionServiceProvider: Grpc_Reflection_V1_ServerReflectionAsyncProvider {
private let protoRegistry: ReflectionServiceData

internal init(fileDescriptorProtos: [Google_Protobuf_FileDescriptorProto]) throws {
self.protoRegistry = try ReflectionServiceData(
fileDescriptors: fileDescriptorProtos
)
}

internal func _findFileByFileName(
_ fileName: String
) -> Result<Grpc_Reflection_V1_ServerReflectionResponse.OneOf_MessageResponse, GRPCStatus> {
return self.protoRegistry
.serialisedFileDescriptorProtosForDependenciesOfFile(named: fileName)
.map { fileDescriptorProtos in
Grpc_Reflection_V1_ServerReflectionResponse.OneOf_MessageResponse.fileDescriptorResponse(
.with {
$0.fileDescriptorProto = fileDescriptorProtos
}
)
}
}

internal func findFileByFileName(
_ fileName: String,
request: Grpc_Reflection_V1_ServerReflectionRequest
) -> Grpc_Reflection_V1_ServerReflectionResponse {
let result = self._findFileByFileName(fileName)
return result.makeResponse(request: request)
}

internal func getServicesNames(
request: Grpc_Reflection_V1_ServerReflectionRequest
) throws -> Grpc_Reflection_V1_ServerReflectionResponse {
var listServicesResponse = Grpc_Reflection_V1_ListServiceResponse()
listServicesResponse.service = self.protoRegistry.serviceNames.map { serviceName in
Grpc_Reflection_V1_ServiceResponse.with {
$0.name = serviceName
}
}
return Grpc_Reflection_V1_ServerReflectionResponse(
request: request,
messageResponse: .listServicesResponse(listServicesResponse)
)
}

internal func findFileBySymbol(
_ symbolName: String,
request: Grpc_Reflection_V1_ServerReflectionRequest
) -> Grpc_Reflection_V1_ServerReflectionResponse {
let result = self.protoRegistry.nameOfFileContainingSymbol(
named: symbolName
).flatMap {
self._findFileByFileName($0)
}
return result.makeResponse(request: request)
}

internal func findFileByExtension(
extensionRequest: Grpc_Reflection_V1_ExtensionRequest,
request: Grpc_Reflection_V1_ServerReflectionRequest
) -> Grpc_Reflection_V1_ServerReflectionResponse {
let result = self.protoRegistry.nameOfFileContainingExtension(
extendeeName: extensionRequest.containingType,
fieldNumber: extensionRequest.extensionNumber
).flatMap {
self._findFileByFileName($0)
}
return result.makeResponse(request: request)
}

internal func findExtensionsFieldNumbersOfType(
named typeName: String,
request: Grpc_Reflection_V1_ServerReflectionRequest
) -> Grpc_Reflection_V1_ServerReflectionResponse {
let result = self.protoRegistry.extensionsFieldNumbersOfType(
named: typeName
).map { fieldNumbers in
Grpc_Reflection_V1_ServerReflectionResponse.OneOf_MessageResponse.allExtensionNumbersResponse(
Grpc_Reflection_V1_ExtensionNumberResponse.with {
$0.baseTypeName = typeName
$0.extensionNumber = fieldNumbers
}
)
}
return result.makeResponse(request: request)
}

internal func serverReflectionInfo(
requestStream: GRPCAsyncRequestStream<Grpc_Reflection_V1_ServerReflectionRequest>,
responseStream: GRPCAsyncResponseStreamWriter<Grpc_Reflection_V1_ServerReflectionResponse>,
context: GRPCAsyncServerCallContext
) async throws {
for try await request in requestStream {
switch request.messageRequest {
case let .fileByFilename(fileName):
let response = self.findFileByFileName(
fileName,
request: request
)
try await responseStream.send(response)

case .listServices:
let response = try self.getServicesNames(request: request)
try await responseStream.send(response)

case let .fileContainingSymbol(symbolName):
let response = self.findFileBySymbol(
symbolName,
request: request
)
try await responseStream.send(response)

case let .fileContainingExtension(extensionRequest):
let response = self.findFileByExtension(
extensionRequest: extensionRequest,
request: request
)
try await responseStream.send(response)

case let .allExtensionNumbersOfType(typeName):
let response = self.findExtensionsFieldNumbersOfType(
named: typeName,
request: request
)
try await responseStream.send(response)

default:
let response = Grpc_Reflection_V1_ServerReflectionResponse(
request: request,
messageResponse: .errorResponse(
Grpc_Reflection_V1_ErrorResponse.with {
$0.errorCode = Int32(GRPCStatus.Code.unimplemented.rawValue)
$0.errorMessage = "The request is not implemented."
}
)
)
try await responseStream.send(response)
}
}
}
}

extension Grpc_Reflection_V1_ServerReflectionResponse {
init(
request: Grpc_Reflection_V1_ServerReflectionRequest,
messageResponse: Grpc_Reflection_V1_ServerReflectionResponse.OneOf_MessageResponse
) {
self = .with {
$0.validHost = request.host
$0.originalRequest = request
$0.messageResponse = messageResponse
}
}
}

extension Google_Protobuf_FileDescriptorProto {
var qualifiedServiceAndMethodNames: [String] {
var names: [String] = []
Expand Down Expand Up @@ -413,35 +285,29 @@ extension Google_Protobuf_FileDescriptorProto {
}
}

extension Result<Grpc_Reflection_V1_ServerReflectionResponse.OneOf_MessageResponse, GRPCStatus> {
func recover() -> Result<Grpc_Reflection_V1_ServerReflectionResponse.OneOf_MessageResponse, Never>
{
self.flatMapError { status in
let error = Grpc_Reflection_V1_ErrorResponse.with {
$0.errorCode = Int32(status.code.rawValue)
$0.errorMessage = status.message ?? ""
}
return .success(.errorResponse(error))
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
extension ReflectionService {
/// The version of the reflection service.
///
/// Depending in the version you are using, when creating the ReflectionService
/// provide the corresponding `Version` variable (`v1` or `v1Alpha`).
public struct Version: Sendable, Hashable {
internal enum Wrapped {
case v1
case v1Alpha
}
}
var wrapped: Wrapped
private init(_ wrapped: Wrapped) { self.wrapped = wrapped }

func makeResponse(
request: Grpc_Reflection_V1_ServerReflectionRequest
) -> Grpc_Reflection_V1_ServerReflectionResponse {
let result = self.recover().attachRequest(request)
// Safe to '!' as the failure type is 'Never'.
return try! result.get()
/// The v1 version of reflection service: https://github.com/grpc/grpc/blob/master/src/proto/grpc/reflection/v1/reflection.proto.
public static var v1: Self { Self(.v1) }
/// The v1alpha version of reflection service: https://github.com/grpc/grpc/blob/master/src/proto/grpc/reflection/v1alpha/reflection.proto.
public static var v1Alpha: Self { Self(.v1Alpha) }
}
}

extension Result
where Success == Grpc_Reflection_V1_ServerReflectionResponse.OneOf_MessageResponse {
func attachRequest(
_ request: Grpc_Reflection_V1_ServerReflectionRequest
) -> Result<Grpc_Reflection_V1_ServerReflectionResponse, Failure> {
self.map { message in
Grpc_Reflection_V1_ServerReflectionResponse(request: request, messageResponse: message)
}
private enum Provider {
case v1(ReflectionServiceProviderV1)
case v1Alpha(ReflectionServiceProviderV1Alpha)
}
}

Expand Down
Loading

0 comments on commit 187b609

Please sign in to comment.