protobuf icon indicating copy to clipboard operation
protobuf copied to clipboard

Feature Request: call protoregistry.GlobalTypes.RegisterExtension so extensions can be handled without codegen

Open toffaletti opened this issue 3 years ago • 1 comments

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

  1. Code generation of all proto files containing extensions and linking them in the plugin.
  2. Hacking around this by forking protogen.Options.Run/run to 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.

toffaletti avatar May 02 '22 23:05 toffaletti

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)
	}
}

ljluestc avatar Dec 23 '24 20:12 ljluestc