Feature Request: call protoregistry.GlobalTypes.RegisterExtension so extensions can be handled without codegen
Is your feature request related to a problem? Please describe.
Currently extensions will be stored as unknown when unmarshaled by protogen.Options.Run, unless you codegen all proto files containing extensions and link them into your plugin.
https://github.com/protocolbuffers/protobuf-go/blob/master/compiler/protogen/protogen.go#L67
Describe the solution you'd like
I would like extensions to be dynamically registered (protoregistry.GlobalTypes.RegisterExtension) prior to unmarshal of FileDescriptors using those extensions so plugins can operate on extensions without requiring a codegen step for them. For example, one way to hack around this now is:
// hack: grab the file registry used by the Plugin
fileReg := (*protoregistry.Files)(reflect.ValueOf(gen).Elem().FieldByName("fileReg").UnsafePointer())
registerExtensions(fileReg)
// now we can call unmarshal again and all extensions will be recognized
err := proto.Unmarshal(in, req)
....
// registerExtensions visits all files looking for extensions
// and registers them with protoregistry.GlobalTypes
func registerExtensions(f *protoregistry.Files) error {
var err error
f.RangeFiles(func(fd protoreflect.FileDescriptor) bool {
extensions := fd.Extensions()
for i := 0; i < extensions.Len(); i++ {
ext := extensions.Get(i)
err = protoregistry.GlobalTypes.RegisterExtension(dynamicpb.NewExtensionType(ext))
if err != nil {
err = fmt.Errorf("error registering extension (%s): %w", ext.FullName(), err)
return false
}
}
return true
})
if err != nil {
return err
}
return nil
}
Describe alternatives you've considered
- Code generation of all proto files containing extensions and linking them in the plugin.
- Hacking around this by forking
protogen.Options.Run/runto inject my own solution.
Additional context I would like this for a plugin that is dynamically programmable and might not know about all extensions at compile time because it loads code and templates at runtime.
package main
import (
"fmt"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/known/dynamicpb"
"reflect"
)
// registerExtensions registers all extensions found in FileDescriptors with GlobalTypes
func registerExtensions(f *protoregistry.Files) error {
var err error
f.RangeFiles(func(fd protoreflect.FileDescriptor) bool {
extensions := fd.Extensions()
for i := 0; i < extensions.Len(); i++ {
ext := extensions.Get(i)
// Register the extension dynamically
err = protoregistry.GlobalTypes.RegisterExtension(dynamicpb.NewExtensionType(ext))
if err != nil {
// Return error if registration fails
err = fmt.Errorf("error registering extension (%s): %w", ext.FullName(), err)
return false
}
}
return true
})
if err != nil {
return err
}
return nil
}
// Example function where you would call this before unmarshaling
func unmarshalWithExtensions(gen interface{}, in []byte, req proto.Message) error {
// Hack: Grab the file registry used by the Plugin (you can use reflection)
fileReg := (*protoregistry.Files)(reflect.ValueOf(gen).Elem().FieldByName("fileReg").UnsafePointer())
// Register all extensions dynamically
if err := registerExtensions(fileReg); err != nil {
return err
}
// Now we can unmarshal the proto message with recognized extensions
return proto.Unmarshal(in, req)
}
func main() {
// Example usage: replace with actual inputs and types
var gen interface{} // Your Plugin's generator
var in []byte // The byte data to unmarshal
var req proto.Message // Your protobuf message
// Call the unmarshal function
if err := unmarshalWithExtensions(gen, in, req); err != nil {
fmt.Printf("Error unmarshaling: %v", err)
}
}