Implement service & method descriptor lookup in Ruby#15817
Implement service & method descriptor lookup in Ruby#15817KJTsanaktsidis wants to merge 2 commits intoprotocolbuffers:mainfrom
Conversation
ruby/tests/service_test.rb
Outdated
| # one doesn't do this). | ||
| omit('JRuby implementation cannot display option extensions') if defined?(::JRUBY_VERSION) | ||
|
|
||
| options_hash = JSON.parse(@test_service.options.to_json) |
There was a problem hiding this comment.
So the actual utility of this for the original use-case in #14891 (getting RPC options) might be somewhat dubious without support for being able to get message extensions (without resorting to #to_json). I guess that can be tackled in a subsequent PR
There was a problem hiding this comment.
You can get extensions now, though the API is slightly awkward:
protobuf/ruby/tests/basic_proto2.rb
Lines 260 to 351 in eb70b34
|
This sounds great; I haven't taken a look at the code yet, but from the description this sounds like just the right approach.
The upb code is now in this repo, and the old repo is defunct. We should probably make the banner at the top of the upb readme bigger and more emphatic about this point. |
ruby/src/main/java/com/google/protobuf/jruby/RubyServiceDescriptor.java
Outdated
Show resolved
Hide resolved
ruby/src/main/java/com/google/protobuf/jruby/RubyMethodDescriptor.java
Outdated
Show resolved
Hide resolved
|
The FFI tests are currently failing with errors like:
Similar changes as you've already made in |
I think they should be made in the |
553117e to
7fb3f10
Compare
ruby/ext/google/protobuf_c/defs.c
Outdated
| rb_define_method(klass, "each_method", ServiceDescriptor_each_method, 0); | ||
| rb_define_method(klass, "file_descriptor", ServiceDescriptor_file_descriptor, 0); | ||
| // n.b. this shadows Object#methods, which seems less than ideal? | ||
| rb_define_method(klass, "methods", ServiceDescriptor_methods, 0); |
There was a problem hiding this comment.
I do want to point out that this shadows Object#methods, which is a bit icky. Perhaps we should rename this #rpc_methods or #service_methods or #method_descriptors or some such?
How does this happen? I made the changes in the For now I regenerated the amalgamation, then copied it into |
ruby/ext/google/protobuf_c/defs.c
Outdated
| * call-seq: | ||
| * ServiceDescriptor.methods => Enumerator | ||
| * | ||
| * Returns an enumerator over all the methods in this service |
There was a problem hiding this comment.
It appears that none of our other descriptor types have methods that return an enumerator. For consistency, I'd prefer to start with just each or each_method. If we want to add methods returning enumerators later, let's do it consistently across all the descriptor types.
There was a problem hiding this comment.
If we want to add methods returning enumerators later, let's do it consistently across all the descriptor types.
So some of our other descriptor types (including plain message Descriptor) are enumerables though (they include the Enumerable mixin), which is more or less the same thing. So you can do
descriptor.each { |field| puts "have field #{field}" }
but also things like
descriptor.to_a
# or
descriptor.select { |field| field.type == 'int32' }
I didn't implement ServiceDescriptor#each, however, because I don't think a service is-a collection of RPC methods in the same way that a message type is-a collection of fields. So instead, I implemented #each_method, and thus implemented #methods to return an enumerable in the same way that self is an enumerator for a message descriptor.
If you like I could just implement #each_method as ServiecDescriptor#each instead, if you think service descriptor is-a list of RPC methods. (as a bonus that would solve the #methods method shadowing Object#methods too, since we wouldn't need that since self is the enumerable then.
There was a problem hiding this comment.
I see your point, that "service is-a collection of methods" is a bit of a stretch. But methods are the only thing you can list inside a service {} block in a .proto file, other than options, which have their own accessor. The fact that this conveniently resolves the #methods shadowing is another argument in its favor.
There was a problem hiding this comment.
alright 👍 the current version of my PR just implements #each/Enumerable directly on ServiceDescriptor. Thanks for talking through that with me.
ruby/ext/google/protobuf_c/defs.c
Outdated
| rb_define_method(klass, "name", ServiceDescriptor_name, 0); | ||
| rb_define_method(klass, "each_method", ServiceDescriptor_each_method, 0); | ||
| rb_define_method(klass, "file_descriptor", ServiceDescriptor_file_descriptor, 0); | ||
| // n.b. this shadows Object#methods, which seems less than ideal? |
There was a problem hiding this comment.
Agreed, shadowing an existing method would be better to avoid if possible.
There was a problem hiding this comment.
I'll rename #each_method to #each and include Enumerable then, which is how the other descriptors list their children. Then I don't need this method at all.
| int32_t fieldnum); | ||
|
|
||
| const upb_ServiceDef* upb_DefPool_FindServiceByName(const upb_DefPool* s, | ||
| UPB_API const upb_ServiceDef* upb_DefPool_FindServiceByName(const upb_DefPool* s, |
There was a problem hiding this comment.
We'll want to update these in
protobuf/upb/reflection/def_pool.h
Line 69 in 3ab1276
There was a problem hiding this comment.
I did that as well; i'll drop the commit which updates these vendored copies.
The Ruby service descriptor & method descriptor implementation requires these methods; export them so that they're callable from the Ruby FFI implementation.
7fb3f10 to
90a955a
Compare
|
@haberman OK, I dropped the each_method/to_enum stuff in favour of just implementing |
|
@haberman anything i can do to move this along? |
|
This is exactly what I need :) I'm looking at generating Rails routes from proto options mirroring what grpc-gateway does (so basically allowing us to have two different processes, one running grpc, one running REST, both funneling to the same handler). I'm trying to write a protoc plugin in Ruby (I have one working in Go, but it means that whoever uses it would need a cumbersome "download binary and put in your path" workflow - I'd rather do it in pure Ruby). Right now this is impossible because a) I can't get the original method protos since I can't call |
|
Sorry for the delay. I think there is still an unresolved comment from me in the code. In the tests you have a comment saying that there is no API for getting an extension, but that isn't true -- see the other tests that get and set extensions. There's no generated code for it yet, but you can lookup an extension in the descriptor pool and then call |
This commit implements suppot for looking up Service descriptors in the global descriptor pool, and from there looking up the associated methods. This is implemented in each of the C extension, JRuby extension, and the FFI implementation. The descriptors can be used to look up the options on the service/methods, and also the request/response types and streaming flags of methods.
90a955a to
54d7218
Compare
|
Oh, apologies, I didn't see that. Thanks for the tip about how to use extensions from Ruby. It should be good to go now. |
…#15817) This PR implements lookup of service descriptor and method descriptor objects in Ruby as described in issue protocolbuffers#14891. It contains three implementations - one for the CRuby extension API, one for JRuby, and one for FFI. With this patch, * `DescriptorPool#lookup('fully.qualified.service.name')` works and returns a `Google::Protobuf::ServiceDescriptor` object * You can call `#options` on that to get the service options * You can call `#methods` on that to get the services' methods as `Google::Protobuf::MethodDescriptor` objects, * You can call `MethodDescriptor#options` to get method options * You can also get the streaming flags & input/output types of the method with `#input_type`, `#output_type`, `#client_streaming`, and `#server_streaming`. In order to make the FFI implementation work, I had to mark some more methods in the UPB header as exported - I guess that's something which will have to be done on the UPB side, like this protocolbuffers/upb@01fed1c CC @dazuma & @haberman from the original issue, and @JasonLunn (since you work on protobuf it seems - small world!) I apologies for the large volume of copy-pasta'd code from the existing descriptor class implementations into the new ones - I felt this was probably better than designing new abstractions to reduce it off the bat though; this feels like it "fits in" with the existing implementation. Closes protocolbuffers#15817 COPYBARA_INTEGRATE_REVIEW=protocolbuffers#15817 from KJTsanaktsidis:ktsanaktsidis/add_service_method_descriptors 54d7218 PiperOrigin-RevId: 618221016
`DescriptorPool.lookup` also returns `ServiceDescriptor` since protocolbuffers#15817.
`DescriptorPool#lookup` also returns `ServiceDescriptor` since protocolbuffers#15817.
`DescriptorPool#lookup` returns `ServiceDescriptor` too since protocolbuffers#15817.
|
@KJTsanaktsidis @haberman wondering if you can help me here. I'm trying to implement a protoc plugin in Ruby where I want to inspect the methods in the input services. However, the fix in this PR doesn't seem to work. The code generation request gives me a If I inspect the Any idea for options here? I'm stumped. |
Can you open a new issue for this use case? |
|
Done! #21091 |
This PR implements lookup of service descriptor and method descriptor objects in Ruby as described in issue #14891.
It contains three implementations - one for the CRuby extension API, one for JRuby, and one for FFI.
With this patch,
DescriptorPool#lookup('fully.qualified.service.name')works and returns aGoogle::Protobuf::ServiceDescriptorobject#optionson that to get the service options#methodson that to get the services' methods asGoogle::Protobuf::MethodDescriptorobjects,MethodDescriptor#optionsto get method options#input_type,#output_type,#client_streaming, and#server_streaming.In order to make the FFI implementation work, I had to mark some more methods in the UPB header as exported - I guess that's something which will have to be done on the UPB side, like this protocolbuffers/upb@01fed1c
CC @dazuma & @haberman from the original issue, and @JasonLunn (since you work on protobuf it seems - small world!)
I apologies for the large volume of copy-pasta'd code from the existing descriptor class implementations into the new ones - I felt this was probably better than designing new abstractions to reduce it off the bat though; this feels like it "fits in" with the existing implementation.