Reputation:
I need to simply load the contents of a few .proto
files directly as descriptor so I can manipulate them via reflection as per https://pkg.go.dev/google.golang.org/protobuf/reflect/protoregistry
From what I can gather, I need to
prototext
to Unmarshal the string into a descriptorpb.FileDescriptorProto
ProtoFileDescriptor
with thatDo I really need to jump through all of these hoops, or am I completely missing another API?
Upvotes: 2
Views: 2692
Reputation: 1109
I was digging around trying to figure out a way to register proto files with protoregistry without using the protoc executable on the host machine. I came upon this code in open policy agent that does it with bufbuild/protocompile (which describes itself as "the spiritual successor to the github.com/jhump/protoreflect/desc/protoparse package"):
In my case, I wanted to not only register messages and types with protoregistry but I also wanted to set up a generic service handler as described in gRPC GO Single Generic Service Handler so I modified the open policy agent code a bit:
func (s *server) registerServicesFromProtoFileDynamicpbNoProtoc(protoFileName string) error {
// Skip this file if we already registered it.
if _, err := protoregistry.GlobalFiles.FindFileByPath(protoFileName); err == nil {
return nil
}
fh, err := os.Open(protoFileName)
if err != nil {
return fmt.Errorf("open file: %w", err)
}
defer fh.Close()
handler := reporter.NewHandler(nil)
node, err := parser.Parse(protoFileName, fh, handler)
if err != nil {
return fmt.Errorf("parse proto: %w", err)
}
res, err := parser.ResultFromAST(node, true, handler)
if err != nil {
return fmt.Errorf("convert from AST: %w", err)
}
fd, err := protodesc.NewFile(res.FileDescriptorProto(), protoregistry.GlobalFiles)
if err != nil {
return fmt.Errorf("convert to FileDescriptor: %w", err)
}
if err := protoregistry.GlobalFiles.RegisterFile(fd); err != nil {
return fmt.Errorf("register file: %w", err)
}
for i := 0; i < fd.Messages().Len(); i++ {
msg := fd.Messages().Get(i)
if err := protoregistry.GlobalTypes.RegisterMessage(dynamicpb.NewMessageType(msg)); err != nil {
return fmt.Errorf("register message %q: %w", msg.FullName(), err)
}
}
for i := 0; i < fd.Extensions().Len(); i++ {
ext := fd.Extensions().Get(i)
if err := protoregistry.GlobalTypes.RegisterExtension(dynamicpb.NewExtensionType(ext)); err != nil {
return fmt.Errorf("register extension %q: %w", ext.FullName(), err)
}
}
for svcNum := 0; svcNum < fd.Services().Len(); svcNum++ {
svc := fd.Services().Get(svcNum)
serviceName := string(svc.FullName())
s.sdMap2[serviceName] = svc
gsd := grpc.ServiceDesc{ServiceName: serviceName, HandlerType: (*interface{})(nil)}
for methodNum := 0; methodNum < svc.Methods().Len(); methodNum++ {
m := svc.Methods().Get(methodNum)
gsd.Methods = append(gsd.Methods, grpc.MethodDesc{MethodName: string(m.Name()), Handler: s.GenericHandler})
}
s.grpcServer.RegisterService(&gsd, s)
}
return nil
}
My server struct
was also modified from the generic service handler post above:
type server struct {
tunnel.UnimplementedRequestTunnelServer
sdMap map[string]*desc.ServiceDescriptor // this uses the github.com/jhump/protoreflect/desc package that I didn't want to use
sdMap2 map[string]protoreflect.ServiceDescriptor // Instead, I use the official protoreflect package
grpcServer *grpc.Server
}
Upvotes: 1
Reputation:
In case someone else lands on this question:
func registerProtoFile(src_dir string, filename string) error {
// First, convert the .proto file to a file descriptor set
tmp_file := filename + "tmp.pb"
cmd := exec.Command("protoc",
"--descriptor_set_out=" + tmp_file,
"-I"+src_dir
path.Join(src_dir, filename))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
return err
}
defer os.Remove(tmp_file)
// Now load that temporary file as a file descriptor set protobuf
protoFile, err := ioutil.ReadFile(tmp_file)
if err != nil {
return err
}
pb_set := new(descriptorpb.FileDescriptorSet)
if err := proto.Unmarshal(protoFile, pb_set); err != nil {
return err
}
// We know protoc was invoked with a single .proto file
pb := pb_set.GetFile()[0]
// Initialize the File descriptor object
fd, err := protodesc.NewFile(pb, protoregistry.GlobalFiles)
if err != nil {
return err
}
// and finally register it.
return protoregistry.GlobalFiles.RegisterFile(fd)
}
Upvotes: 1