Skip to content

Commit

Permalink
protoc-gen-oracall: remove String hack, use golang/protobuf#1260
Browse files Browse the repository at this point in the history
  • Loading branch information
tgulacsi committed Oct 31, 2024
1 parent 2a699bd commit bad610f
Showing 1 changed file with 84 additions and 38 deletions.
122 changes: 84 additions & 38 deletions protoc-gen-oracall/protoc_gen_oracall.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import (
oracall "github.com/tgulacsi/oracall/lib"
"google.golang.org/protobuf/compiler/protogen"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
// "google.golang.org/protobuf/types/descriptorpb"
"google.golang.org/protobuf/types/dynamicpb"
"google.golang.org/protobuf/types/pluginpb"
)

Expand All @@ -43,55 +47,52 @@ func Main() error {
panic(err)
}

// https://github.com/golang/protobuf/issues/1260
// The type information for all extensions is in the source files,
// so we need to extract them into a dynamically created protoregistry.Types.
extTypes := new(protoregistry.Types)
for _, file := range plugin.Files {
if err := registerAllExtensions(extTypes, file.Desc); err != nil {
return fmt.Errorf("registerAllExtensions: %w", err)
}
}

// Protoc passes a slice of File structs for us to process
for _, file := range plugin.Files {
// log.Printf("? %q", file.GoImportPath.String())
if strings.HasPrefix(file.GoImportPath.String(), `"google.golang.org/`) {
log.Println("skip", file.GoImportPath.String())
continue
}
// var messageOption, fieldOption *protogen.Message
// for _, e := range file.Extensions {
// switch e.Extendee.Desc.FullName() {
// case "google.protobuf.MessageOptions":
// messageOption = e.Extendee
// case "google.protobuf.FieldOptions":
// fieldOption = e.Extendee
// }
// }

// Time to generate code...!
var buf bytes.Buffer

// 2. Write the package name
fmt.Fprintf(&buf, "// Code generated by protoc-gen-oracall. DO NOT EDIT!\n\npackage %s\n\n", file.GoPackageName)

for _, msg := range file.Proto.MessageType {
// FIXME[tgulacs]: this is a hack, but I couldn't find out how to get the extensions' options' values
optS := msg.Options.String()
opts, err := parseOption(optS)
for _, msg := range file.Messages {
objectType, err := getCustomOption(extTypes, msg.Desc.Options(), "oracall_object_type")
if err != nil {
return fmt.Errorf("parse %q: %w", optS, err)
return err
}
objectType := opts["objects.oracall_object_type"]
msgName := msg.Desc.Name()
fmt.Fprintf(&buf, "\n// %q=%q\nfunc(%s) ObjecTypeName() string { return %q }\n",
*msg.Name, objectType,
*msg.Name, objectType,
msgName, objectType,
msgName, objectType,
)
fmt.Fprintf(&buf, "func (%s) FieldTypeName(f string) string{\n\tswitch f {\n", msg.GetName())
for _, f := range msg.Field {
optS := f.Options.String()
opts, err := parseOption(optS)
fmt.Fprintf(&buf, "func (%s) FieldTypeName(f string) string{\n\tswitch f {\n", msg.Desc.Name())
fields := msg.Desc.Fields()
for i := range fields.Len() {
f := fields.Get(i)
fieldName := string(f.Name())
fieldType, err := getCustomOption(extTypes, f.Options(), "oracall_field_type")
if err != nil {
return fmt.Errorf("parse %q: %w", optS, err)
}
fieldType := opts["objects.oracall_field_type"]
if fieldType == "" {
log.Printf("opts=%q fields=%+v", optS, opts)
return err
}
fmt.Fprintf(&buf, "\t\t// %q.%q = %q\n\t\tcase %q, %q: return %q\n",
*msg.Name, oracall.CamelCase(f.GetName()), fieldType,
f.GetName(), oracall.CamelCase(f.GetName()), fieldType,
msgName, oracall.CamelCase(fieldName), fieldType,
fieldName, oracall.CamelCase(fieldName), fieldType,
)
}
fmt.Fprintf(&buf, "}\n\treturn \"\"\n}\n")
Expand Down Expand Up @@ -121,16 +122,61 @@ func Main() error {
return err
}

// parseOption parses the [option.name]:"value" stringified option.
func parseOption(s string) (map[string]string, error) {
k, v, ok := strings.Cut(s, ":")
if !ok {
return nil, fmt.Errorf("no : in %q", s)
func getCustomOption(extTypes *protoregistry.Types, options protoreflect.ProtoMessage, name string) (protoreflect.Value, error) {
var value protoreflect.Value
err := iterCustomOptions(extTypes, options, func(fd protoreflect.FieldDescriptor, v protoreflect.Value) error {
if string(fd.Name()) == name {
value = v
}
return nil
})
return value, err
}
func iterCustomOptions(extTypes *protoregistry.Types, options protoreflect.ProtoMessage, f func(protoreflect.FieldDescriptor, protoreflect.Value) error) error {
// The MessageOptions as provided by protoc does not know about
// dynamically created extensions, so they are left as unknown fields.
// We round-trip marshal and unmarshal the options with
// a dynamically created resolver that does know about extensions at runtime.
// options := msg.Desc.Options().(*descriptorpb.MessageOptions)
b, err := proto.Marshal(options)
if err != nil {
return fmt.Errorf("Marshal(%#v): %w", options, err)
}
options.(interface{ Reset() }).Reset()
err = proto.UnmarshalOptions{Resolver: extTypes}.Unmarshal(b, options)
if err != nil {
return fmt.Errorf("Unmarshal: %w", err)
}
if w, err := strconv.Unquote(v); err != nil {
return nil, fmt.Errorf("unquote %q: %w", v, err)
} else {
v = w

// Use protobuf reflection to iterate over all the extension fields,
// looking for the ones that we are interested in.
options.ProtoReflect().Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
if !fd.IsExtension() {
return true
}
if err = f(fd, v); err != nil {
return false
}
// Make use of fd and v based on their reflective properties.
return true
})
return err
}

// https://github.com/golang/protobuf/issues/1260
func registerAllExtensions(extTypes *protoregistry.Types, descs interface {
Messages() protoreflect.MessageDescriptors
Extensions() protoreflect.ExtensionDescriptors
}) error {
mds := descs.Messages()
for i := 0; i < mds.Len(); i++ {
registerAllExtensions(extTypes, mds.Get(i))
}
xds := descs.Extensions()
for i := 0; i < xds.Len(); i++ {
if err := extTypes.RegisterExtension(dynamicpb.NewExtensionType(xds.Get(i))); err != nil {
return err
}
}
return map[string]string{strings.Trim(k, "[]"): v}, nil
return nil
}

0 comments on commit bad610f

Please sign in to comment.