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 9 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
241 changes: 50 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 reflectionServiceProvider: ProviderType
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This naming doesn't need to be so verbose: we're within the ReflectionService so we can just use "provider".


public var serviceName: Substring {
self.reflectionService.serviceName
switch self.reflectionServiceProvider {
case .v1Provider(let reflectionV1Provider):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with this naming: we're switching over providers so each case doesn't need to have "provider" in the name, "v1" and "v1Alpha" are fine. Same goes for the associated value labels, provider is fine there too.

switch self.provider {
case .v1(let provider):
  // ...
case .v1Alpha(let provider):
  // ...
}

return reflectionV1Provider.serviceName
case .v1AlphaProvider(let reflectionV1AlphaProvider):
return reflectionV1AlphaProvider.serviceName
}
}

/// Creates a `ReflectionService` by loading serialized reflection data created by `protoc-gen-grpc-swift`.
Expand All @@ -35,24 +41,46 @@ public final class ReflectionService: CallHandlerProvider, Sendable {
/// - 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 {
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.reflectionServiceProvider = .v1Provider(
try ReflectionServiceProviderV1(fileDescriptorProtos: fileDescriptorProtos)
)
case .v1Alpha:
self.reflectionServiceProvider = .v1AlphaProvider(
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.reflectionServiceProvider = .v1Provider(
try ReflectionServiceProviderV1(fileDescriptorProtos: fileDescriptorProtos)
)
case .v1Alpha:
self.reflectionServiceProvider = .v1AlphaProvider(
try ReflectionServiceProviderV1Alpha(fileDescriptorProtos: fileDescriptorProtos)
)
}
}

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

Expand Down Expand Up @@ -221,163 +249,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 @@ -412,35 +283,23 @@ 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 {
public struct Version: Sendable {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be Hashable as well

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()
public static var v1: Self { Self(.v1) }
public static var v1Alpha: Self { Self(.v1Alpha) }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These need docs

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
}

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 ProviderType {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provider is fine here

case v1Provider(ReflectionServiceProviderV1)
case v1AlphaProvider(ReflectionServiceProviderV1Alpha)
}
}

Expand Down
Loading
Loading