Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for v1alpha reflection #1703

Merged
merged 17 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
glbrntt marked this conversation as resolved.
Show resolved Hide resolved
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
Loading