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

Conversation

stefanadranca
Copy link
Collaborator

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.

stefanadranca and others added 8 commits November 1, 2023 16:40
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.
@stefanadranca stefanadranca marked this pull request as ready for review November 3, 2023 14:29
@@ -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):
  // ...
}

Comment on lines 296 to 297
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.

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

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

Comment on lines 70 to 104
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)
}
Copy link
Collaborator

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:

Suggested change
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)
}

Copy link
Collaborator

@glbrntt glbrntt left a 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`.
Copy link
Collaborator

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.

Suggested change
/// - Parameter version: The version of the reflection, `v1` or `v1alpha`.
/// - Parameter version: The version of the reflection service to create.

Comment on lines 305 to 310
/// 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.
Copy link
Collaborator

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`.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/// Represents the version of the `reflection.proto`.
/// The version of the reflection service.

Comment on lines 296 to 297
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.

try await serviceReflectionInfo.requestStream.send(
.with {
for version in self.versions {
try self.setUpServerAndChannel(version: version)
Copy link
Collaborator

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 
  // ...
}

Comment on lines 127 to 129
return XCTFail(
"The service of the received file descriptor proto doesn't have any methods."
)
Copy link
Collaborator

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

@glbrntt glbrntt enabled auto-merge (squash) November 9, 2023 14:20
@glbrntt glbrntt added the semver/patch No public API change. label Nov 9, 2023
@glbrntt glbrntt merged commit 187b609 into grpc:main Nov 9, 2023
12 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
semver/patch No public API change.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants