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

Added ReflectionService initialiser using file paths #1699

Merged
merged 15 commits into from
Nov 3, 2023

Conversation

stefanadranca
Copy link
Collaborator

@stefanadranca stefanadranca commented Nov 1, 2023

Motivation:

Initialising the Reflection Service using file descriptor protos is too laborious for users, so simply passing the file paths of the binary files containing the serialised file descriptor protos and letting the Service extract the data from those paths is more convenient.

Modifications:

Added a new initializer that takes in file paths and extracts and converts to file descriptor proto format the Data from the files. Also modified the integration tests to create temporary files containing the serialized file descriptor protos.

Result:

GRPC users can add the ReflectionService to their Server in a more convenient way.

Motivation:

Initializing the Reflection Service using file descriptor protos is too laborious for users,
so simply passing the file paths of the binary files containing the serialized file descriptor protos
and letting the Service extract the data from those paths is more convenient.

Modifications:

Added a new initializer that takes in file paths and extracts and converts to file descriptor proto format the Data
from the files. Also modified the integration tests to create temporary files containing the serialized file descriptor protos.

Result:

GRPCUsers can add the ReflectionService to their Server in a more convenient way.
@@ -26,6 +26,10 @@ public final class ReflectionService: CallHandlerProvider, Sendable {
self.reflectionService.serviceName
}

public init(protoFilePaths: [String]) throws {
Copy link
Collaborator

Choose a reason for hiding this comment

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

We aren't loading proto files (i.e. .proto) we're loading serialised file descriptor protos:

public init(serializedFileDescriptorProtoFilePaths: [String]) throws {

Copy link
Collaborator

Choose a reason for hiding this comment

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

This also needs documentation

@@ -207,6 +211,27 @@ internal struct ReflectionServiceData: Sendable {
internal final class ReflectionServiceProvider: Grpc_Reflection_V1_ServerReflectionAsyncProvider {
private let protoRegistry: ReflectionServiceData

internal init(protoFilePaths: [String]) throws {

Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: unnecessary blank line

Suggested change

Comment on lines 227 to 230
let binaryData = binaryFilesURLs.map { Data(base64Encoded: try! Data(contentsOf: $0)) }
let fileDescriptorProtos = binaryData.map {
try! Google_Protobuf_FileDescriptorProto(serializedData: $0!)
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

We can't try! these, they may legitimately fail and this would cause a crash.

We should just do the basic thing:

var fileDescriptorProtos = [Google_Protobuf_FileDescriptorProto]()
for path in paths {
  // ...  
  fileDescriptorProtos.append(...)
}

#else
let temporaryDirectory = fileManager.temporaryDirectory.path()
#endif
let filePath = temporaryDirectory + fileDescriptorProto.name
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 think this is sufficiently unique (especially on linux): if we ran these tests in parallel we could end up removing files which another test is expecting to be there. One workaround which is probably fine is to add some randomness to the name.

let reflectionServiceProvider = try ReflectionService(
fileDescriptors: self.protos + [self.independentProto]
)
var filePaths: [String] = []
Copy link
Collaborator

Choose a reason for hiding this comment

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

We actually don't need to do this in the integration tests: we can do it in the unit tests because the actual work is done by the registry rather than the service. This also means we can do this in a separate test as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

At the moment, the ReflectionServiceProvider (not the wrapper, the class that contains the registry) is doing the conversion of Data from the file paths to serialised file descriptor protos and then initialises the registry (using the file descriptor ports). This is why I implemented the testing in the integration tests. So if I'm moving it in the Unit tests I should also move the conversion in a Proto initialiser. Should I do this?

And my second question if I do this: Should I only implement unit tests for this conversion from binary data to file descriptor photos and leave all the other tests as they were before (using directly file descriptor protos)?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh, my mistake. Thinking about this some more, I think we can implement this as a couple of helpers on the ReflectionService which would be called by the init:

extension ReflectionService {
  static func readSerializedFileDescriptorProto(atPath path: String) throws -> Google_Protobuf_FileDescriptorProto { 
    // ...
  }

  static func readSerializedFileDescriptorProtos(atPaths paths: [String]) throws -> [Google_Protobuf_FileDescriptorProto] {     
    // ... 
  }
}

This way we can test the loading independently of the registry and operation of the service. I.e. we can test that these functions can load and deserialize the serialized descriptors correctly.

Comment on lines 29 to 31
/// Receives as parameter an array of strings representing the file paths to the binary files containing
/// the serialised file descriptor protos describing the services supported by the Server and
/// discovarable through Server Reflection.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Docs should follow a particular format, take a look at https://www.swift.org/documentation/api-design-guidelines/ in particular the "More detail" part of "Write a documentation comment".

The documentation should start with a brief description of what the function does:

Creates a ReflectionService by loading serialized reflection data created by protoc-gen-grpc-swift.

Then you can go into more information about what the expectations are etc. and how they can generate this data:

You can generate serialized reflection data using the protoc-gen-grpc-swift plugin for protoc by
setting the to True. The paths provided should be absolute or relative
to the current working directory.

Then you can detail the parameters:

  • Parameter filePaths: The paths to files containing serialized reflection data.

And when the function throws:

  • Throws: When a file can't be read from disk or parsed.

/// Receives as parameter an array of strings representing the file paths to the binary files containing
/// the serialised file descriptor protos describing the services supported by the Server and
/// discovarable through Server Reflection.
public init(serializedFileDescriptorProtoFilePaths: [String]) throws {
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should use a shorter label internally:

Suggested change
public init(serializedFileDescriptorProtoFilePaths: [String]) throws {
public init(serializedFileDescriptorProtoFilePaths filePaths: [String]) throws {

code: .invalidArgument,
message:
"""
The \(fileURL.lastPathComponent) file contents could not be transformed \
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should include the whole file path. The user could have two files with the same name in different directories which would make this error unclear.

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.

Doc needs fixing but looks fine otherwise

/// Creates a `ReflectionService` by loading serialized reflection data created by `protoc-gen-grpc-swift`.
///
/// You can generate serialized reflection data using the `protoc-gen-grpc-swift` plugin for `protoc` by
/// setting the to `True`. The paths provided should be absolute or relative to the current working directory.
Copy link
Collaborator

Choose a reason for hiding this comment

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

"setting the to True" ...?

@glbrntt glbrntt merged commit 5519602 into grpc:main Nov 3, 2023
12 of 14 checks passed
@glbrntt glbrntt added the semver/minor Adds new public API. label Dec 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
semver/minor Adds new public API.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants