-
Notifications
You must be signed in to change notification settings - Fork 420
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
Conversation
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.
…to avoid build errors when generating the .grpc and .pb files
sd-v1alpha-temp solved the conflict between main and v1alpha.
@@ -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 |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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):
// ...
}
public static var v1: Self { Self(.v1) } | ||
public static var v1Alpha: Self { Self(.v1Alpha) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These need docs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These ones still need docs. I'd also link to the source protos:
return .success(.errorResponse(error)) | ||
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) | ||
extension ReflectionService { | ||
public struct Version: Sendable { |
There was a problem hiding this comment.
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
self.map { message in | ||
Grpc_Reflection_V1_ServerReflectionResponse(request: request, messageResponse: message) | ||
} | ||
private enum ProviderType { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Provider
is fine here
func testFileByFileName() async throws { | ||
try self.setUpServerAndChannel() | ||
let client = Grpc_Reflection_V1alpha_ServerReflectionAsyncClient(channel: self.channel!) | ||
let serviceReflectionInfo = client.makeServerReflectionInfoCall() | ||
try await serviceReflectionInfo.requestStream.send( | ||
.with { | ||
$0.host = "127.0.0.1" | ||
$0.fileByFilename = "bar1.proto" | ||
} | ||
) | ||
serviceReflectionInfo.requestStream.finish() | ||
|
||
var iterator = serviceReflectionInfo.responseStream.makeAsyncIterator() | ||
guard let message = try await iterator.next() else { | ||
return XCTFail("Could not get a response message.") | ||
} | ||
|
||
let receivedFileDescriptorProto = | ||
try Google_Protobuf_FileDescriptorProto( | ||
serializedData: (message.fileDescriptorResponse | ||
.fileDescriptorProto[0]) | ||
) | ||
|
||
XCTAssertEqual(receivedFileDescriptorProto.name, "bar1.proto") | ||
XCTAssertEqual(receivedFileDescriptorProto.service.count, 1) | ||
|
||
guard let service = receivedFileDescriptorProto.service.first else { | ||
return XCTFail("The received file descriptor proto doesn't have any services.") | ||
} | ||
guard let method = service.method.first else { | ||
return XCTFail("The service of the received file descriptor proto doesn't have any methods.") | ||
} | ||
XCTAssertEqual(method.name, "testMethod1") | ||
XCTAssertEqual(message.fileDescriptorResponse.fileDescriptorProto.count, 4) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't love that we've essentially copied-and-pasted all the tests. It's going to be difficult to maintain going forward.
I think we can be a bit smarter here by having each case iterate over the two versions and having some convenience functions to switch between v1 and v1 alpha. This obviously won't compile but hopefully gives an idea of what we can do:
func testFileByFileName() async throws { | |
try self.setUpServerAndChannel() | |
let client = Grpc_Reflection_V1alpha_ServerReflectionAsyncClient(channel: self.channel!) | |
let serviceReflectionInfo = client.makeServerReflectionInfoCall() | |
try await serviceReflectionInfo.requestStream.send( | |
.with { | |
$0.host = "127.0.0.1" | |
$0.fileByFilename = "bar1.proto" | |
} | |
) | |
serviceReflectionInfo.requestStream.finish() | |
var iterator = serviceReflectionInfo.responseStream.makeAsyncIterator() | |
guard let message = try await iterator.next() else { | |
return XCTFail("Could not get a response message.") | |
} | |
let receivedFileDescriptorProto = | |
try Google_Protobuf_FileDescriptorProto( | |
serializedData: (message.fileDescriptorResponse | |
.fileDescriptorProto[0]) | |
) | |
XCTAssertEqual(receivedFileDescriptorProto.name, "bar1.proto") | |
XCTAssertEqual(receivedFileDescriptorProto.service.count, 1) | |
guard let service = receivedFileDescriptorProto.service.first else { | |
return XCTFail("The received file descriptor proto doesn't have any services.") | |
} | |
guard let method = service.method.first else { | |
return XCTFail("The service of the received file descriptor proto doesn't have any methods.") | |
} | |
XCTAssertEqual(method.name, "testMethod1") | |
XCTAssertEqual(message.fileDescriptorResponse.fileDescriptorProto.count, 4) | |
} | |
func testFileByFileName() async throws { | |
for version in versions { | |
try self.setUpServerAndChannel(version: version) | |
let request = V1Request.with { | |
$0.host = "127.0.0.1" | |
$0.fileByFilename = "bar1.proto" | |
} | |
let response: V1Response? | |
switch version { | |
case .v1: | |
let client = V1Client(channel: self.channel!) | |
let call = client.makeServerReflectionInfoCall() | |
try await call.send(request) | |
call.finish() | |
var iterator = serviceReflectionInfo.responseStream.makeAsyncIterator() | |
response = try await iterator.next() | |
case .v2: | |
let client = V1Client(channel: self.channel!) | |
let call = client.makeServerReflectionInfoCall() | |
try await call.send(V1AlphaRequest(request)) // convert from v1 to alpha | |
call.finish() | |
response = try await iterator.next().map { V1AlphaResponse($0) } // convert from v1 to alpha | |
} | |
guard let message = response else { | |
return XCTFail("Could not get a response message.") | |
} | |
let receivedFileDescriptorProto = | |
try Google_Protobuf_FileDescriptorProto( | |
serializedData: (message.fileDescriptorResponse | |
.fileDescriptorProto[0]) | |
) | |
XCTAssertEqual(receivedFileDescriptorProto.name, "bar1.proto") | |
XCTAssertEqual(receivedFileDescriptorProto.service.count, 1) | |
guard let service = receivedFileDescriptorProto.service.first else { | |
return XCTFail("The received file descriptor proto doesn't have any services.") | |
} | |
guard let method = service.method.first else { | |
return XCTFail("The service of the received file descriptor proto doesn't have any methods.") | |
} | |
XCTAssertEqual(method.name, "testMethod1") | |
XCTAssertEqual(message.fileDescriptorResponse.fileDescriptorProto.count, 4) | |
} | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! You need to avoid leaking some resources in the tests now that we do two iterations.
@@ -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, `v1` or `v1alpha`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd recommend against documenting individual cases; the documentation can only become outdated.
/// - Parameter version: The version of the reflection, `v1` or `v1alpha`. | |
/// - Parameter version: The version of the reflection service to create. |
/// Represents the two possible `Providers`. | ||
/// | ||
/// Depending on the reflection version `v1` or `v1alpha` you provided to the initialiser, | ||
/// ``init(serializedFileDescriptorProtoFilePaths:version:)``, | ||
/// or ``init(fileDescriptorProtos:version:)``, | ||
/// the Reflection Service will select its provider accordingly. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to document private
types. General rule of thumb is to document all public
types and any internal
types which are non-trivial.
return .success(.errorResponse(error)) | ||
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) | ||
extension ReflectionService { | ||
/// Represents the version of the `reflection.proto`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Represents the version of the `reflection.proto`. | |
/// The version of the reflection service. |
public static var v1: Self { Self(.v1) } | ||
public static var v1Alpha: Self { Self(.v1Alpha) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These ones still need docs. I'd also link to the source protos:
try await serviceReflectionInfo.requestStream.send( | ||
.with { | ||
for version in self.versions { | ||
try self.setUpServerAndChannel(version: version) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that we do the test twice, in the second iteration we'll be overwriting the existing client and server but never shutting them down. We also need to do the teardown at the end of each loop.
You can probably do this with a helper:
func forEachVersion(_ body: (GRPCChannel) async throws -> Void) async throws {
for version in self.versions {
let server = ...
let channel = ...
let result: Result<Void, Error>
do {
try await body(channel)
result = .success(())
} catch {
result = .failure(error)
}
// tear down client and server
// ...
try result.get()
}
}
Then each test can just do:
self.forEachVersion { client in
// ...
}
return XCTFail( | ||
"The service of the received file descriptor proto doesn't have any methods." | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can also use let method = try XCTUnwrap(service.method.first)
to unwrap optionals in tests
Motivation:
v1alpha reflection is also used by GRPC users, so we should provide support for it.
Modifications:
Result:
Users will be able to set the version for the Reflection Service that they use, v1alpha becoming a possibility.