1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
|
// 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)
}
}
|