bufbuild/protocompile

how to get httprule using this compiler

jaredzhou opened this issue · 4 comments

for example i got and demo.proto

syntax = "proto3";

package demo;

import "google/api/annotations.proto";

service DemoService {
    rpc GetDemo(GetDemoReq) returns(GetDemoReply){
        option (google.api.http) = {
			get: "/api/demo/{id}"
		};
    }	
}

and i use code from grpc-gateway like below

package test
import (
	"fmt"
	"testing"

	"github.com/bufbuild/protocompile/protoutil"
	options "google.golang.org/genproto/googleapis/api/annotations"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/reflect/protoreflect"
)
func TestCompile(t *testing.T) {
	importPaths := compile([]string{"./proto"})

   comp := pcompile.Compiler{
		Resolver: pcompile.WithStandardImports(
			&pcompile.SourceResolver{
				ImportPaths: importPaths,
			},
		),
	}
	fs, err := comp.Compile(context.Background(), []string{"demo.proto"})
	if err != nil {
		t.Fatal(err)
	}
	for _, f := range fs {
		fmt.Println(f.FullName())
		svcs := f.Services()
		for i := 0; i < svcs.Len(); i++ {
			svc := svcs.Get(i)
			fmt.Println(svc.FullName())
			methods := svc.Methods()
			for j := 0; j < methods.Len(); j++ {
				m := methods.Get(j)
				fmt.Println(m.FullName())
				_, err := extractAPIOptions(m)
				if err != nil {
					fmt.Println(err)
					return
				}
			}
		}
	}
}

func extractAPIOptions(d protoreflect.MethodDescriptor) (*options.HttpRule, error) {
	dp := protoutil.ProtoFromMethodDescriptor(d)
	if dp.Options == nil {
		return nil, nil
	}
	if !proto.HasExtension(dp.Options, options.E_Http) {
		return nil, nil
	}
	ext := proto.GetExtension(dp.Options, options.E_Http)
	opts, ok := ext.(*options.HttpRule)
	if !ok {
		return nil, fmt.Errorf("extension is %T; want an HttpRule", ext)
	}
	return opts, nil
}

the code is exactly same with code from grpc-gateway but i got and error
invalid type: got *dynamicpb.Message, want *annotations.HttpRule,. i am not very familiar with the protobuf-go package, so it's very confusing for me, anyone know what's the problem

jhump commented

Sorry this isn't better documented. But when you compile the file, it creates dynamic representations of all extensions to match the version defined in source. So it's not using options.E_Http to populate MethodOptions but instead has a dynamic version of that custom option, create from the definition in the sources provided.

So in order to use the result with generated extensions, you need to convert the dynamic extensions to non-dynamic extensions. The easiest way to do this is to marshal the MethodOptions message to bytes and then unmarshal. When you unmarshal, you should use the proto.Unmarshal function or use a proto.UnmarshalOptions struct where the Resolver field is either left nil or set to protoregistry.GlobalTypes. That way, the serialized bytes corresponding to the custom options will be interpreted using the generated extension and its corresponding Go types -- in this case the options.E_Http extension definition, which indicates *annotations.HttpRule as the Go type of its value.

@jhump Thank you very much, it seems working. also i wonder is there any example using this compiler results to send dynamic rpc calls?

jhump commented

@jaredzhou, the package that tools like grpcurl and grpcui use for dynamic RPC is github.com/jhump/protoreflect/dynamic/grpcdynamic. This API wants a *desc.MethodDescriptor, which can be created via desc.WrapMethod, passing in a protoreflect.MethodDescriptor that is inside a file descriptor returned from protocompile.

An alternate example using the connect-go library can be found in the buf CLI's "curl" sub-command. This example uses a protoreflect.MethodDescriptor (so no wrapping needed). This example is a bit more involved, and there's no import-able library to make it easier (though you can view its implementation techniques and copy/fork the relevant parts of the code).

@jhump cool, it's very helpful, i will have a try, thank u