protoreflect
protoreflect copied to clipboard
desc/builder: feature request: auto-de-duplicate builders and already-built descriptors in transitive graph
Hey, jhump:
First let me say this is a incredible complement to standard Go API v2, and I enjoyed a lot and thank you!
I want to construct a compact transitive closure from a "start" message, "compact" meaning removing all non-relevant symbols, even they appears in the original .proto file, which is always good for storage / transfer over network.
With the following code snippet, it complained
Received unexpected error:
descriptors have cyclic dependency:...
I suspect when I tried to AddMessage (to the FileBuilder), as wrapped from protoreflect.MessageDescriptor, it drags the import path, and for some reason, some mis-registration happens in your package, but I don't know too much details to tell / fix.
Can you please help? Thanks.
--- Code snippet ---
// TransitiveClosure returns a compact set of related protobuf message,
// as the transitive closure of the desired protobuf message.
// All the symbols are packaged into a single FileDescriptorSet proto,
// to be consistent with common usage of delivering such dependency over network.
func TransitiveClosure[T proto.Message]() (*descriptorpb.FileDescriptorSet, error) {
var zero T
md := zero.ProtoReflect().Descriptor()
// A map from the file name to the index (within fbs) for each FileBuilder instance.
builderIdx := make(map[string]int)
var fbs []*builder.FileBuilder
knownSymbols := make(map[protoreflect.FullName]bool)
queue := []protoreflect.Descriptor{md}
for len(queue) > 0 {
head := queue[0]
queue = queue[1:]
if knownSymbols[head.FullName()] {
continue
}
if head.IsPlaceholder() { // Skip any missing files in the dependency graph.
continue
}
pf := head.ParentFile()
idx, found := builderIdx[pf.Path()]
if !found { // We need another FileBuilder for a new .proto file in this transitive closure.
idx = len(fbs)
fbs = append(fbs, prepareFileBuilder(pf.Path(), string(pf.Package())))
builderIdx[pf.Path()] = idx
}
knownSymbols[head.FullName()] = true
switch descriptor := head.(type) {
case protoreflect.MessageDescriptor:
jmd, err := desc.WrapMessage(descriptor)
if err != nil {
return nil, err
}
mb, err := builder.FromMessage(jmd)
if err != nil {
return nil, err
}
fbs[idx].AddMessage(mb)
fields := descriptor.Fields()
for idx, len := 0, fields.Len(); idx < len; idx++ {
switch f := fields.Get(idx); f.Kind() {
case protoreflect.MessageKind:
if f.IsMap() {
if mv := f.MapValue(); mv.Kind() == protoreflect.MessageKind {
queue = append(queue, mv.Message())
}
continue
}
queue = append(queue, f.Message())
case protoreflect.EnumKind:
queue = append(queue, f.Enum())
}
}
case protoreflect.EnumDescriptor:
jed, err := desc.WrapEnum(descriptor)
if err != nil {
return nil, err
}
eb, err := builder.FromEnum(jed)
if err != nil {
return nil, err
}
fbs[idx].AddEnum(eb)
default:
panic("unhandled?")
}
}
var fds []*desc.FileDescriptor
for idx := len(fbs) - 1; idx >= 0; idx-- {
built, err := fbs[idx].Build()
if err != nil {
return nil, err
}
fds = append(fds, built)
}
return desc.ToFileDescriptorSet(fds...), nil
}
func prepareFileBuilder(path, packageName string) *builder.FileBuilder {
fb := builder.NewFile(path)
fb.SetProto3(true)
fb.SetPackageName(packageName)
return fb
}