| // Copyright 2023 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package dynamicpb | |
| import ( | |
| "fmt" | |
| "strings" | |
| "sync" | |
| "sync/atomic" | |
| "google.golang.org/protobuf/internal/errors" | |
| "google.golang.org/protobuf/reflect/protoreflect" | |
| "google.golang.org/protobuf/reflect/protoregistry" | |
| ) | |
| type extField struct { | |
| name protoreflect.FullName | |
| number protoreflect.FieldNumber | |
| } | |
| // A Types is a collection of dynamically constructed descriptors. | |
| // Its methods are safe for concurrent use. | |
| // | |
| // Types implements [protoregistry.MessageTypeResolver] and [protoregistry.ExtensionTypeResolver]. | |
| // A Types may be used as a [google.golang.org/protobuf/proto.UnmarshalOptions.Resolver]. | |
| type Types struct { | |
| // atomicExtFiles is used with sync/atomic and hence must be the first word | |
| // of the struct to guarantee 64-bit alignment. | |
| // | |
| // TODO(stapelberg): once we only support Go 1.19 and newer, switch this | |
| // field to be of type atomic.Uint64 to guarantee alignment on | |
| // stack-allocated values, too. | |
| atomicExtFiles uint64 | |
| extMu sync.Mutex | |
| files *protoregistry.Files | |
| extensionsByMessage map[extField]protoreflect.ExtensionDescriptor | |
| } | |
| // NewTypes creates a new Types registry with the provided files. | |
| // The Files registry is retained, and changes to Files will be reflected in Types. | |
| // It is not safe to concurrently change the Files while calling Types methods. | |
| func NewTypes(f *protoregistry.Files) *Types { | |
| return &Types{ | |
| files: f, | |
| } | |
| } | |
| // FindEnumByName looks up an enum by its full name; | |
| // e.g., "google.protobuf.Field.Kind". | |
| // | |
| // This returns (nil, [protoregistry.NotFound]) if not found. | |
| func (t *Types) FindEnumByName(name protoreflect.FullName) (protoreflect.EnumType, error) { | |
| d, err := t.files.FindDescriptorByName(name) | |
| if err != nil { | |
| return nil, err | |
| } | |
| ed, ok := d.(protoreflect.EnumDescriptor) | |
| if !ok { | |
| return nil, errors.New("found wrong type: got %v, want enum", descName(d)) | |
| } | |
| return NewEnumType(ed), nil | |
| } | |
| // FindExtensionByName looks up an extension field by the field's full name. | |
| // Note that this is the full name of the field as determined by | |
| // where the extension is declared and is unrelated to the full name of the | |
| // message being extended. | |
| // | |
| // This returns (nil, [protoregistry.NotFound]) if not found. | |
| func (t *Types) FindExtensionByName(name protoreflect.FullName) (protoreflect.ExtensionType, error) { | |
| d, err := t.files.FindDescriptorByName(name) | |
| if err != nil { | |
| return nil, err | |
| } | |
| xd, ok := d.(protoreflect.ExtensionDescriptor) | |
| if !ok { | |
| return nil, errors.New("found wrong type: got %v, want extension", descName(d)) | |
| } | |
| return NewExtensionType(xd), nil | |
| } | |
| // FindExtensionByNumber looks up an extension field by the field number | |
| // within some parent message, identified by full name. | |
| // | |
| // This returns (nil, [protoregistry.NotFound]) if not found. | |
| func (t *Types) FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error) { | |
| // Construct the extension number map lazily, since not every user will need it. | |
| // Update the map if new files are added to the registry. | |
| if atomic.LoadUint64(&t.atomicExtFiles) != uint64(t.files.NumFiles()) { | |
| t.updateExtensions() | |
| } | |
| xd := t.extensionsByMessage[extField{message, field}] | |
| if xd == nil { | |
| return nil, protoregistry.NotFound | |
| } | |
| return NewExtensionType(xd), nil | |
| } | |
| // FindMessageByName looks up a message by its full name; | |
| // e.g. "google.protobuf.Any". | |
| // | |
| // This returns (nil, [protoregistry.NotFound]) if not found. | |
| func (t *Types) FindMessageByName(name protoreflect.FullName) (protoreflect.MessageType, error) { | |
| d, err := t.files.FindDescriptorByName(name) | |
| if err != nil { | |
| return nil, err | |
| } | |
| md, ok := d.(protoreflect.MessageDescriptor) | |
| if !ok { | |
| return nil, errors.New("found wrong type: got %v, want message", descName(d)) | |
| } | |
| return NewMessageType(md), nil | |
| } | |
| // FindMessageByURL looks up a message by a URL identifier. | |
| // See documentation on google.protobuf.Any.type_url for the URL format. | |
| // | |
| // This returns (nil, [protoregistry.NotFound]) if not found. | |
| func (t *Types) FindMessageByURL(url string) (protoreflect.MessageType, error) { | |
| // This function is similar to FindMessageByName but | |
| // truncates anything before and including '/' in the URL. | |
| message := protoreflect.FullName(url) | |
| if i := strings.LastIndexByte(url, '/'); i >= 0 { | |
| message = message[i+len("/"):] | |
| } | |
| return t.FindMessageByName(message) | |
| } | |
| func (t *Types) updateExtensions() { | |
| t.extMu.Lock() | |
| defer t.extMu.Unlock() | |
| if atomic.LoadUint64(&t.atomicExtFiles) == uint64(t.files.NumFiles()) { | |
| return | |
| } | |
| defer atomic.StoreUint64(&t.atomicExtFiles, uint64(t.files.NumFiles())) | |
| t.files.RangeFiles(func(fd protoreflect.FileDescriptor) bool { | |
| t.registerExtensions(fd.Extensions()) | |
| t.registerExtensionsInMessages(fd.Messages()) | |
| return true | |
| }) | |
| } | |
| func (t *Types) registerExtensionsInMessages(mds protoreflect.MessageDescriptors) { | |
| count := mds.Len() | |
| for i := 0; i < count; i++ { | |
| md := mds.Get(i) | |
| t.registerExtensions(md.Extensions()) | |
| t.registerExtensionsInMessages(md.Messages()) | |
| } | |
| } | |
| func (t *Types) registerExtensions(xds protoreflect.ExtensionDescriptors) { | |
| count := xds.Len() | |
| for i := 0; i < count; i++ { | |
| xd := xds.Get(i) | |
| field := xd.Number() | |
| message := xd.ContainingMessage().FullName() | |
| if t.extensionsByMessage == nil { | |
| t.extensionsByMessage = make(map[extField]protoreflect.ExtensionDescriptor) | |
| } | |
| t.extensionsByMessage[extField{message, field}] = xd | |
| } | |
| } | |
| func descName(d protoreflect.Descriptor) string { | |
| switch d.(type) { | |
| case protoreflect.EnumDescriptor: | |
| return "enum" | |
| case protoreflect.EnumValueDescriptor: | |
| return "enum value" | |
| case protoreflect.MessageDescriptor: | |
| return "message" | |
| case protoreflect.ExtensionDescriptor: | |
| return "extension" | |
| case protoreflect.ServiceDescriptor: | |
| return "service" | |
| default: | |
| return fmt.Sprintf("%T", d) | |
| } | |
| } |