755 lines
20 KiB
Go
Raw Normal View History

// Copyright 2018 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 protoregistry provides data structures to register and lookup
// protobuf descriptor types.
//
// The Files registry contains file descriptors and provides the ability
// to iterate over the files or lookup a specific descriptor within the files.
// Files only contains protobuf descriptors and has no understanding of Go
// type information that may be associated with each descriptor.
//
// The Types registry contains descriptor types for which there is a known
// Go type associated with that descriptor. It provides the ability to iterate
// over the registered types or lookup a type by name.
package protoregistry
import (
"fmt"
"log"
"strings"
"sync"
"google.golang.org/protobuf/internal/errors"
"google.golang.org/protobuf/reflect/protoreflect"
)
// ignoreConflict reports whether to ignore a registration conflict
// given the descriptor being registered and the error.
// It is a variable so that the behavior is easily overridden in another file.
var ignoreConflict = func(d protoreflect.Descriptor, err error) bool {
log.Printf(""+
"WARNING: %v\n"+
"A future release will panic on registration conflicts. See:\n"+
"https://developers.google.com/protocol-buffers/docs/reference/go/faq#namespace-conflict\n"+
"\n", err)
return true
}
var globalMutex sync.RWMutex
// GlobalFiles is a global registry of file descriptors.
var GlobalFiles *Files = new(Files)
// GlobalTypes is the registry used by default for type lookups
// unless a local registry is provided by the user.
var GlobalTypes *Types = new(Types)
// NotFound is a sentinel error value to indicate that the type was not found.
//
// Since registry lookup can happen in the critical performance path, resolvers
// must return this exact error value, not an error wrapping it.
var NotFound = errors.New("not found")
// Files is a registry for looking up or iterating over files and the
// descriptors contained within them.
// The Find and Range methods are safe for concurrent use.
type Files struct {
reflect/protoreflect: register all desciptors in Files This change makes it such that Files now functionally registers all descriptors in a file (not just enums, messages, extensions, and services), but also including enum values, messages fields/oneofs, and service methods. The ability to look up any descriptor by full name is needed to: 1) properly detect namespace conflicts on enum values 2) properly implement the relative name lookup logic in reflect/protodesc The approach taken: 1) Assumes that a FileDescriptor has no internal name conflicts. This will (in a future CL) be guaranteed by reflect/protodesc and is guaranteed today by protoc for generated descriptors. 2) Observes that the only declarations that can possibly conflict with another file are top-level declarations (i.e., enums, enum values, messages, extensions, and services). Enum values are annoying since they live in the same scope as the parent enum, rather than being under the enum. For the internal data structure of Files, we only register the top-level declarations. This is the bare minimum needed to detect whether the file being registered has any namespace conflicts with previously registered files. We shift the effort to lookups, where we now need to peel off the end fragments of a full name until we find a match in the internal registry. If a match is found, we may need to descend into that declaration to find a nested declaration by name. For initialization, we modify internal/filedesc to initialize the enum values for all top-level enums. This performance cost is offsetted by the fact that Files.Register now avoids internally registering nested enums, messages, and extensions. For lookup, the cost has shifted from O(1) to O(N), where N is the number of segments in the full name. Top-level descriptors still have O(1) lookup times. Nested descriptors have O(M) lookup times, where M is the level of nesting within a single file. Change-Id: I950163423431f04a503b6201ddcc20a62ccba017 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/183697 Reviewed-by: Damien Neil <dneil@google.com>
2019-06-21 14:25:16 -07:00
// The map of descsByName contains:
// EnumDescriptor
reflect/protoreflect: register all desciptors in Files This change makes it such that Files now functionally registers all descriptors in a file (not just enums, messages, extensions, and services), but also including enum values, messages fields/oneofs, and service methods. The ability to look up any descriptor by full name is needed to: 1) properly detect namespace conflicts on enum values 2) properly implement the relative name lookup logic in reflect/protodesc The approach taken: 1) Assumes that a FileDescriptor has no internal name conflicts. This will (in a future CL) be guaranteed by reflect/protodesc and is guaranteed today by protoc for generated descriptors. 2) Observes that the only declarations that can possibly conflict with another file are top-level declarations (i.e., enums, enum values, messages, extensions, and services). Enum values are annoying since they live in the same scope as the parent enum, rather than being under the enum. For the internal data structure of Files, we only register the top-level declarations. This is the bare minimum needed to detect whether the file being registered has any namespace conflicts with previously registered files. We shift the effort to lookups, where we now need to peel off the end fragments of a full name until we find a match in the internal registry. If a match is found, we may need to descend into that declaration to find a nested declaration by name. For initialization, we modify internal/filedesc to initialize the enum values for all top-level enums. This performance cost is offsetted by the fact that Files.Register now avoids internally registering nested enums, messages, and extensions. For lookup, the cost has shifted from O(1) to O(N), where N is the number of segments in the full name. Top-level descriptors still have O(1) lookup times. Nested descriptors have O(M) lookup times, where M is the level of nesting within a single file. Change-Id: I950163423431f04a503b6201ddcc20a62ccba017 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/183697 Reviewed-by: Damien Neil <dneil@google.com>
2019-06-21 14:25:16 -07:00
// EnumValueDescriptor
// MessageDescriptor
// ExtensionDescriptor
// ServiceDescriptor
// *packageDescriptor
//
// Note that files are stored as a slice, since a package may contain
reflect/protoreflect: register all desciptors in Files This change makes it such that Files now functionally registers all descriptors in a file (not just enums, messages, extensions, and services), but also including enum values, messages fields/oneofs, and service methods. The ability to look up any descriptor by full name is needed to: 1) properly detect namespace conflicts on enum values 2) properly implement the relative name lookup logic in reflect/protodesc The approach taken: 1) Assumes that a FileDescriptor has no internal name conflicts. This will (in a future CL) be guaranteed by reflect/protodesc and is guaranteed today by protoc for generated descriptors. 2) Observes that the only declarations that can possibly conflict with another file are top-level declarations (i.e., enums, enum values, messages, extensions, and services). Enum values are annoying since they live in the same scope as the parent enum, rather than being under the enum. For the internal data structure of Files, we only register the top-level declarations. This is the bare minimum needed to detect whether the file being registered has any namespace conflicts with previously registered files. We shift the effort to lookups, where we now need to peel off the end fragments of a full name until we find a match in the internal registry. If a match is found, we may need to descend into that declaration to find a nested declaration by name. For initialization, we modify internal/filedesc to initialize the enum values for all top-level enums. This performance cost is offsetted by the fact that Files.Register now avoids internally registering nested enums, messages, and extensions. For lookup, the cost has shifted from O(1) to O(N), where N is the number of segments in the full name. Top-level descriptors still have O(1) lookup times. Nested descriptors have O(M) lookup times, where M is the level of nesting within a single file. Change-Id: I950163423431f04a503b6201ddcc20a62ccba017 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/183697 Reviewed-by: Damien Neil <dneil@google.com>
2019-06-21 14:25:16 -07:00
// multiple files. Only top-level declarations are registered.
// Note that enum values are in the top-level since that are in the same
// scope as the parent enum.
descsByName map[protoreflect.FullName]interface{}
filesByPath map[string]protoreflect.FileDescriptor
}
type packageDescriptor struct {
files []protoreflect.FileDescriptor
}
// RegisterFile registers the provided file descriptor.
//
// If any descriptor within the file conflicts with the descriptor of any
// previously registered file (e.g., two enums with the same full name),
// then the file is not registered and an error is returned.
//
// It is permitted for multiple files to have the same file path.
func (r *Files) RegisterFile(file protoreflect.FileDescriptor) error {
if r == GlobalFiles {
globalMutex.Lock()
defer globalMutex.Unlock()
}
reflect/protoreflect: register all desciptors in Files This change makes it such that Files now functionally registers all descriptors in a file (not just enums, messages, extensions, and services), but also including enum values, messages fields/oneofs, and service methods. The ability to look up any descriptor by full name is needed to: 1) properly detect namespace conflicts on enum values 2) properly implement the relative name lookup logic in reflect/protodesc The approach taken: 1) Assumes that a FileDescriptor has no internal name conflicts. This will (in a future CL) be guaranteed by reflect/protodesc and is guaranteed today by protoc for generated descriptors. 2) Observes that the only declarations that can possibly conflict with another file are top-level declarations (i.e., enums, enum values, messages, extensions, and services). Enum values are annoying since they live in the same scope as the parent enum, rather than being under the enum. For the internal data structure of Files, we only register the top-level declarations. This is the bare minimum needed to detect whether the file being registered has any namespace conflicts with previously registered files. We shift the effort to lookups, where we now need to peel off the end fragments of a full name until we find a match in the internal registry. If a match is found, we may need to descend into that declaration to find a nested declaration by name. For initialization, we modify internal/filedesc to initialize the enum values for all top-level enums. This performance cost is offsetted by the fact that Files.Register now avoids internally registering nested enums, messages, and extensions. For lookup, the cost has shifted from O(1) to O(N), where N is the number of segments in the full name. Top-level descriptors still have O(1) lookup times. Nested descriptors have O(M) lookup times, where M is the level of nesting within a single file. Change-Id: I950163423431f04a503b6201ddcc20a62ccba017 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/183697 Reviewed-by: Damien Neil <dneil@google.com>
2019-06-21 14:25:16 -07:00
if r.descsByName == nil {
r.descsByName = map[protoreflect.FullName]interface{}{
"": &packageDescriptor{},
}
r.filesByPath = make(map[string]protoreflect.FileDescriptor)
}
path := file.Path()
if prev := r.filesByPath[path]; prev != nil {
err := errors.New("file %q is already registered", file.Path())
err = amendErrorWithCaller(err, prev, file)
if r == GlobalFiles && ignoreConflict(file, err) {
err = nil
}
return err
}
for name := file.Package(); name != ""; name = name.Parent() {
switch prev := r.descsByName[name]; prev.(type) {
case nil, *packageDescriptor:
default:
err := errors.New("file %q has a package name conflict over %v", file.Path(), name)
err = amendErrorWithCaller(err, prev, file)
if r == GlobalFiles && ignoreConflict(file, err) {
err = nil
}
return err
}
}
var err error
var hasConflict bool
rangeTopLevelDescriptors(file, func(d protoreflect.Descriptor) {
if prev := r.descsByName[d.FullName()]; prev != nil {
hasConflict = true
err = errors.New("file %q has a name conflict over %v", file.Path(), d.FullName())
err = amendErrorWithCaller(err, prev, file)
if r == GlobalFiles && ignoreConflict(d, err) {
err = nil
}
}
})
if hasConflict {
return err
}
for name := file.Package(); name != ""; name = name.Parent() {
reflect/protoreflect: register all desciptors in Files This change makes it such that Files now functionally registers all descriptors in a file (not just enums, messages, extensions, and services), but also including enum values, messages fields/oneofs, and service methods. The ability to look up any descriptor by full name is needed to: 1) properly detect namespace conflicts on enum values 2) properly implement the relative name lookup logic in reflect/protodesc The approach taken: 1) Assumes that a FileDescriptor has no internal name conflicts. This will (in a future CL) be guaranteed by reflect/protodesc and is guaranteed today by protoc for generated descriptors. 2) Observes that the only declarations that can possibly conflict with another file are top-level declarations (i.e., enums, enum values, messages, extensions, and services). Enum values are annoying since they live in the same scope as the parent enum, rather than being under the enum. For the internal data structure of Files, we only register the top-level declarations. This is the bare minimum needed to detect whether the file being registered has any namespace conflicts with previously registered files. We shift the effort to lookups, where we now need to peel off the end fragments of a full name until we find a match in the internal registry. If a match is found, we may need to descend into that declaration to find a nested declaration by name. For initialization, we modify internal/filedesc to initialize the enum values for all top-level enums. This performance cost is offsetted by the fact that Files.Register now avoids internally registering nested enums, messages, and extensions. For lookup, the cost has shifted from O(1) to O(N), where N is the number of segments in the full name. Top-level descriptors still have O(1) lookup times. Nested descriptors have O(M) lookup times, where M is the level of nesting within a single file. Change-Id: I950163423431f04a503b6201ddcc20a62ccba017 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/183697 Reviewed-by: Damien Neil <dneil@google.com>
2019-06-21 14:25:16 -07:00
if r.descsByName[name] == nil {
r.descsByName[name] = &packageDescriptor{}
}
}
p := r.descsByName[file.Package()].(*packageDescriptor)
p.files = append(p.files, file)
rangeTopLevelDescriptors(file, func(d protoreflect.Descriptor) {
reflect/protoreflect: register all desciptors in Files This change makes it such that Files now functionally registers all descriptors in a file (not just enums, messages, extensions, and services), but also including enum values, messages fields/oneofs, and service methods. The ability to look up any descriptor by full name is needed to: 1) properly detect namespace conflicts on enum values 2) properly implement the relative name lookup logic in reflect/protodesc The approach taken: 1) Assumes that a FileDescriptor has no internal name conflicts. This will (in a future CL) be guaranteed by reflect/protodesc and is guaranteed today by protoc for generated descriptors. 2) Observes that the only declarations that can possibly conflict with another file are top-level declarations (i.e., enums, enum values, messages, extensions, and services). Enum values are annoying since they live in the same scope as the parent enum, rather than being under the enum. For the internal data structure of Files, we only register the top-level declarations. This is the bare minimum needed to detect whether the file being registered has any namespace conflicts with previously registered files. We shift the effort to lookups, where we now need to peel off the end fragments of a full name until we find a match in the internal registry. If a match is found, we may need to descend into that declaration to find a nested declaration by name. For initialization, we modify internal/filedesc to initialize the enum values for all top-level enums. This performance cost is offsetted by the fact that Files.Register now avoids internally registering nested enums, messages, and extensions. For lookup, the cost has shifted from O(1) to O(N), where N is the number of segments in the full name. Top-level descriptors still have O(1) lookup times. Nested descriptors have O(M) lookup times, where M is the level of nesting within a single file. Change-Id: I950163423431f04a503b6201ddcc20a62ccba017 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/183697 Reviewed-by: Damien Neil <dneil@google.com>
2019-06-21 14:25:16 -07:00
r.descsByName[d.FullName()] = d
})
r.filesByPath[path] = file
return nil
}
reflect/protoreflect: register all desciptors in Files This change makes it such that Files now functionally registers all descriptors in a file (not just enums, messages, extensions, and services), but also including enum values, messages fields/oneofs, and service methods. The ability to look up any descriptor by full name is needed to: 1) properly detect namespace conflicts on enum values 2) properly implement the relative name lookup logic in reflect/protodesc The approach taken: 1) Assumes that a FileDescriptor has no internal name conflicts. This will (in a future CL) be guaranteed by reflect/protodesc and is guaranteed today by protoc for generated descriptors. 2) Observes that the only declarations that can possibly conflict with another file are top-level declarations (i.e., enums, enum values, messages, extensions, and services). Enum values are annoying since they live in the same scope as the parent enum, rather than being under the enum. For the internal data structure of Files, we only register the top-level declarations. This is the bare minimum needed to detect whether the file being registered has any namespace conflicts with previously registered files. We shift the effort to lookups, where we now need to peel off the end fragments of a full name until we find a match in the internal registry. If a match is found, we may need to descend into that declaration to find a nested declaration by name. For initialization, we modify internal/filedesc to initialize the enum values for all top-level enums. This performance cost is offsetted by the fact that Files.Register now avoids internally registering nested enums, messages, and extensions. For lookup, the cost has shifted from O(1) to O(N), where N is the number of segments in the full name. Top-level descriptors still have O(1) lookup times. Nested descriptors have O(M) lookup times, where M is the level of nesting within a single file. Change-Id: I950163423431f04a503b6201ddcc20a62ccba017 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/183697 Reviewed-by: Damien Neil <dneil@google.com>
2019-06-21 14:25:16 -07:00
// FindDescriptorByName looks up a descriptor by the full name.
//
// This returns (nil, NotFound) if not found.
reflect/protoreflect: register all desciptors in Files This change makes it such that Files now functionally registers all descriptors in a file (not just enums, messages, extensions, and services), but also including enum values, messages fields/oneofs, and service methods. The ability to look up any descriptor by full name is needed to: 1) properly detect namespace conflicts on enum values 2) properly implement the relative name lookup logic in reflect/protodesc The approach taken: 1) Assumes that a FileDescriptor has no internal name conflicts. This will (in a future CL) be guaranteed by reflect/protodesc and is guaranteed today by protoc for generated descriptors. 2) Observes that the only declarations that can possibly conflict with another file are top-level declarations (i.e., enums, enum values, messages, extensions, and services). Enum values are annoying since they live in the same scope as the parent enum, rather than being under the enum. For the internal data structure of Files, we only register the top-level declarations. This is the bare minimum needed to detect whether the file being registered has any namespace conflicts with previously registered files. We shift the effort to lookups, where we now need to peel off the end fragments of a full name until we find a match in the internal registry. If a match is found, we may need to descend into that declaration to find a nested declaration by name. For initialization, we modify internal/filedesc to initialize the enum values for all top-level enums. This performance cost is offsetted by the fact that Files.Register now avoids internally registering nested enums, messages, and extensions. For lookup, the cost has shifted from O(1) to O(N), where N is the number of segments in the full name. Top-level descriptors still have O(1) lookup times. Nested descriptors have O(M) lookup times, where M is the level of nesting within a single file. Change-Id: I950163423431f04a503b6201ddcc20a62ccba017 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/183697 Reviewed-by: Damien Neil <dneil@google.com>
2019-06-21 14:25:16 -07:00
func (r *Files) FindDescriptorByName(name protoreflect.FullName) (protoreflect.Descriptor, error) {
if r == nil {
return nil, NotFound
}
if r == GlobalFiles {
globalMutex.RLock()
defer globalMutex.RUnlock()
}
reflect/protoreflect: register all desciptors in Files This change makes it such that Files now functionally registers all descriptors in a file (not just enums, messages, extensions, and services), but also including enum values, messages fields/oneofs, and service methods. The ability to look up any descriptor by full name is needed to: 1) properly detect namespace conflicts on enum values 2) properly implement the relative name lookup logic in reflect/protodesc The approach taken: 1) Assumes that a FileDescriptor has no internal name conflicts. This will (in a future CL) be guaranteed by reflect/protodesc and is guaranteed today by protoc for generated descriptors. 2) Observes that the only declarations that can possibly conflict with another file are top-level declarations (i.e., enums, enum values, messages, extensions, and services). Enum values are annoying since they live in the same scope as the parent enum, rather than being under the enum. For the internal data structure of Files, we only register the top-level declarations. This is the bare minimum needed to detect whether the file being registered has any namespace conflicts with previously registered files. We shift the effort to lookups, where we now need to peel off the end fragments of a full name until we find a match in the internal registry. If a match is found, we may need to descend into that declaration to find a nested declaration by name. For initialization, we modify internal/filedesc to initialize the enum values for all top-level enums. This performance cost is offsetted by the fact that Files.Register now avoids internally registering nested enums, messages, and extensions. For lookup, the cost has shifted from O(1) to O(N), where N is the number of segments in the full name. Top-level descriptors still have O(1) lookup times. Nested descriptors have O(M) lookup times, where M is the level of nesting within a single file. Change-Id: I950163423431f04a503b6201ddcc20a62ccba017 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/183697 Reviewed-by: Damien Neil <dneil@google.com>
2019-06-21 14:25:16 -07:00
prefix := name
suffix := nameSuffix("")
for prefix != "" {
if d, ok := r.descsByName[prefix]; ok {
switch d := d.(type) {
case protoreflect.EnumDescriptor:
if d.FullName() == name {
return d, nil
}
case protoreflect.EnumValueDescriptor:
if d.FullName() == name {
return d, nil
}
case protoreflect.MessageDescriptor:
if d.FullName() == name {
return d, nil
}
if d := findDescriptorInMessage(d, suffix); d != nil && d.FullName() == name {
return d, nil
}
case protoreflect.ExtensionDescriptor:
if d.FullName() == name {
return d, nil
}
case protoreflect.ServiceDescriptor:
if d.FullName() == name {
return d, nil
}
if d := d.Methods().ByName(suffix.Pop()); d != nil && d.FullName() == name {
return d, nil
}
}
return nil, NotFound
}
prefix = prefix.Parent()
suffix = nameSuffix(name[len(prefix)+len("."):])
}
return nil, NotFound
}
func findDescriptorInMessage(md protoreflect.MessageDescriptor, suffix nameSuffix) protoreflect.Descriptor {
name := suffix.Pop()
if suffix == "" {
if ed := md.Enums().ByName(name); ed != nil {
return ed
}
for i := md.Enums().Len() - 1; i >= 0; i-- {
if vd := md.Enums().Get(i).Values().ByName(name); vd != nil {
return vd
}
}
if xd := md.Extensions().ByName(name); xd != nil {
return xd
}
if fd := md.Fields().ByName(name); fd != nil {
return fd
}
if od := md.Oneofs().ByName(name); od != nil {
return od
}
}
if md := md.Messages().ByName(name); md != nil {
if suffix == "" {
return md
}
return findDescriptorInMessage(md, suffix)
}
return nil
}
type nameSuffix string
func (s *nameSuffix) Pop() (name protoreflect.Name) {
if i := strings.IndexByte(string(*s), '.'); i >= 0 {
name, *s = protoreflect.Name((*s)[:i]), (*s)[i+1:]
} else {
name, *s = protoreflect.Name((*s)), ""
}
return name
}
// FindFileByPath looks up a file by the path.
//
// This returns (nil, NotFound) if not found.
func (r *Files) FindFileByPath(path string) (protoreflect.FileDescriptor, error) {
if r == nil {
return nil, NotFound
}
if r == GlobalFiles {
globalMutex.RLock()
defer globalMutex.RUnlock()
}
if fd, ok := r.filesByPath[path]; ok {
return fd, nil
}
return nil, NotFound
}
// NumFiles reports the number of registered files.
func (r *Files) NumFiles() int {
if r == nil {
return 0
}
if r == GlobalFiles {
globalMutex.RLock()
defer globalMutex.RUnlock()
}
return len(r.filesByPath)
}
// RangeFiles iterates over all registered files while f returns true.
// The iteration order is undefined.
func (r *Files) RangeFiles(f func(protoreflect.FileDescriptor) bool) {
if r == nil {
return
}
if r == GlobalFiles {
globalMutex.RLock()
defer globalMutex.RUnlock()
}
for _, file := range r.filesByPath {
if !f(file) {
return
}
}
}
// NumFilesByPackage reports the number of registered files in a proto package.
func (r *Files) NumFilesByPackage(name protoreflect.FullName) int {
if r == nil {
return 0
}
if r == GlobalFiles {
globalMutex.RLock()
defer globalMutex.RUnlock()
}
p, ok := r.descsByName[name].(*packageDescriptor)
if !ok {
return 0
}
return len(p.files)
}
// RangeFilesByPackage iterates over all registered files in a given proto package
// while f returns true. The iteration order is undefined.
func (r *Files) RangeFilesByPackage(name protoreflect.FullName, f func(protoreflect.FileDescriptor) bool) {
if r == nil {
return
}
if r == GlobalFiles {
globalMutex.RLock()
defer globalMutex.RUnlock()
}
reflect/protoreflect: register all desciptors in Files This change makes it such that Files now functionally registers all descriptors in a file (not just enums, messages, extensions, and services), but also including enum values, messages fields/oneofs, and service methods. The ability to look up any descriptor by full name is needed to: 1) properly detect namespace conflicts on enum values 2) properly implement the relative name lookup logic in reflect/protodesc The approach taken: 1) Assumes that a FileDescriptor has no internal name conflicts. This will (in a future CL) be guaranteed by reflect/protodesc and is guaranteed today by protoc for generated descriptors. 2) Observes that the only declarations that can possibly conflict with another file are top-level declarations (i.e., enums, enum values, messages, extensions, and services). Enum values are annoying since they live in the same scope as the parent enum, rather than being under the enum. For the internal data structure of Files, we only register the top-level declarations. This is the bare minimum needed to detect whether the file being registered has any namespace conflicts with previously registered files. We shift the effort to lookups, where we now need to peel off the end fragments of a full name until we find a match in the internal registry. If a match is found, we may need to descend into that declaration to find a nested declaration by name. For initialization, we modify internal/filedesc to initialize the enum values for all top-level enums. This performance cost is offsetted by the fact that Files.Register now avoids internally registering nested enums, messages, and extensions. For lookup, the cost has shifted from O(1) to O(N), where N is the number of segments in the full name. Top-level descriptors still have O(1) lookup times. Nested descriptors have O(M) lookup times, where M is the level of nesting within a single file. Change-Id: I950163423431f04a503b6201ddcc20a62ccba017 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/183697 Reviewed-by: Damien Neil <dneil@google.com>
2019-06-21 14:25:16 -07:00
p, ok := r.descsByName[name].(*packageDescriptor)
if !ok {
return
}
for _, file := range p.files {
if !f(file) {
return
}
}
}
reflect/protoreflect: register all desciptors in Files This change makes it such that Files now functionally registers all descriptors in a file (not just enums, messages, extensions, and services), but also including enum values, messages fields/oneofs, and service methods. The ability to look up any descriptor by full name is needed to: 1) properly detect namespace conflicts on enum values 2) properly implement the relative name lookup logic in reflect/protodesc The approach taken: 1) Assumes that a FileDescriptor has no internal name conflicts. This will (in a future CL) be guaranteed by reflect/protodesc and is guaranteed today by protoc for generated descriptors. 2) Observes that the only declarations that can possibly conflict with another file are top-level declarations (i.e., enums, enum values, messages, extensions, and services). Enum values are annoying since they live in the same scope as the parent enum, rather than being under the enum. For the internal data structure of Files, we only register the top-level declarations. This is the bare minimum needed to detect whether the file being registered has any namespace conflicts with previously registered files. We shift the effort to lookups, where we now need to peel off the end fragments of a full name until we find a match in the internal registry. If a match is found, we may need to descend into that declaration to find a nested declaration by name. For initialization, we modify internal/filedesc to initialize the enum values for all top-level enums. This performance cost is offsetted by the fact that Files.Register now avoids internally registering nested enums, messages, and extensions. For lookup, the cost has shifted from O(1) to O(N), where N is the number of segments in the full name. Top-level descriptors still have O(1) lookup times. Nested descriptors have O(M) lookup times, where M is the level of nesting within a single file. Change-Id: I950163423431f04a503b6201ddcc20a62ccba017 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/183697 Reviewed-by: Damien Neil <dneil@google.com>
2019-06-21 14:25:16 -07:00
// rangeTopLevelDescriptors iterates over all top-level descriptors in a file
// which will be directly entered into the registry.
func rangeTopLevelDescriptors(fd protoreflect.FileDescriptor, f func(protoreflect.Descriptor)) {
eds := fd.Enums()
for i := eds.Len() - 1; i >= 0; i-- {
f(eds.Get(i))
vds := eds.Get(i).Values()
for i := vds.Len() - 1; i >= 0; i-- {
f(vds.Get(i))
}
}
reflect/protoreflect: register all desciptors in Files This change makes it such that Files now functionally registers all descriptors in a file (not just enums, messages, extensions, and services), but also including enum values, messages fields/oneofs, and service methods. The ability to look up any descriptor by full name is needed to: 1) properly detect namespace conflicts on enum values 2) properly implement the relative name lookup logic in reflect/protodesc The approach taken: 1) Assumes that a FileDescriptor has no internal name conflicts. This will (in a future CL) be guaranteed by reflect/protodesc and is guaranteed today by protoc for generated descriptors. 2) Observes that the only declarations that can possibly conflict with another file are top-level declarations (i.e., enums, enum values, messages, extensions, and services). Enum values are annoying since they live in the same scope as the parent enum, rather than being under the enum. For the internal data structure of Files, we only register the top-level declarations. This is the bare minimum needed to detect whether the file being registered has any namespace conflicts with previously registered files. We shift the effort to lookups, where we now need to peel off the end fragments of a full name until we find a match in the internal registry. If a match is found, we may need to descend into that declaration to find a nested declaration by name. For initialization, we modify internal/filedesc to initialize the enum values for all top-level enums. This performance cost is offsetted by the fact that Files.Register now avoids internally registering nested enums, messages, and extensions. For lookup, the cost has shifted from O(1) to O(N), where N is the number of segments in the full name. Top-level descriptors still have O(1) lookup times. Nested descriptors have O(M) lookup times, where M is the level of nesting within a single file. Change-Id: I950163423431f04a503b6201ddcc20a62ccba017 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/183697 Reviewed-by: Damien Neil <dneil@google.com>
2019-06-21 14:25:16 -07:00
mds := fd.Messages()
for i := mds.Len() - 1; i >= 0; i-- {
f(mds.Get(i))
}
reflect/protoreflect: register all desciptors in Files This change makes it such that Files now functionally registers all descriptors in a file (not just enums, messages, extensions, and services), but also including enum values, messages fields/oneofs, and service methods. The ability to look up any descriptor by full name is needed to: 1) properly detect namespace conflicts on enum values 2) properly implement the relative name lookup logic in reflect/protodesc The approach taken: 1) Assumes that a FileDescriptor has no internal name conflicts. This will (in a future CL) be guaranteed by reflect/protodesc and is guaranteed today by protoc for generated descriptors. 2) Observes that the only declarations that can possibly conflict with another file are top-level declarations (i.e., enums, enum values, messages, extensions, and services). Enum values are annoying since they live in the same scope as the parent enum, rather than being under the enum. For the internal data structure of Files, we only register the top-level declarations. This is the bare minimum needed to detect whether the file being registered has any namespace conflicts with previously registered files. We shift the effort to lookups, where we now need to peel off the end fragments of a full name until we find a match in the internal registry. If a match is found, we may need to descend into that declaration to find a nested declaration by name. For initialization, we modify internal/filedesc to initialize the enum values for all top-level enums. This performance cost is offsetted by the fact that Files.Register now avoids internally registering nested enums, messages, and extensions. For lookup, the cost has shifted from O(1) to O(N), where N is the number of segments in the full name. Top-level descriptors still have O(1) lookup times. Nested descriptors have O(M) lookup times, where M is the level of nesting within a single file. Change-Id: I950163423431f04a503b6201ddcc20a62ccba017 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/183697 Reviewed-by: Damien Neil <dneil@google.com>
2019-06-21 14:25:16 -07:00
xds := fd.Extensions()
for i := xds.Len() - 1; i >= 0; i-- {
f(xds.Get(i))
}
reflect/protoreflect: register all desciptors in Files This change makes it such that Files now functionally registers all descriptors in a file (not just enums, messages, extensions, and services), but also including enum values, messages fields/oneofs, and service methods. The ability to look up any descriptor by full name is needed to: 1) properly detect namespace conflicts on enum values 2) properly implement the relative name lookup logic in reflect/protodesc The approach taken: 1) Assumes that a FileDescriptor has no internal name conflicts. This will (in a future CL) be guaranteed by reflect/protodesc and is guaranteed today by protoc for generated descriptors. 2) Observes that the only declarations that can possibly conflict with another file are top-level declarations (i.e., enums, enum values, messages, extensions, and services). Enum values are annoying since they live in the same scope as the parent enum, rather than being under the enum. For the internal data structure of Files, we only register the top-level declarations. This is the bare minimum needed to detect whether the file being registered has any namespace conflicts with previously registered files. We shift the effort to lookups, where we now need to peel off the end fragments of a full name until we find a match in the internal registry. If a match is found, we may need to descend into that declaration to find a nested declaration by name. For initialization, we modify internal/filedesc to initialize the enum values for all top-level enums. This performance cost is offsetted by the fact that Files.Register now avoids internally registering nested enums, messages, and extensions. For lookup, the cost has shifted from O(1) to O(N), where N is the number of segments in the full name. Top-level descriptors still have O(1) lookup times. Nested descriptors have O(M) lookup times, where M is the level of nesting within a single file. Change-Id: I950163423431f04a503b6201ddcc20a62ccba017 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/183697 Reviewed-by: Damien Neil <dneil@google.com>
2019-06-21 14:25:16 -07:00
sds := fd.Services()
for i := sds.Len() - 1; i >= 0; i-- {
f(sds.Get(i))
}
}
proto, encoding/protojson, encoding/prototext: use Resolver interface Instead of accepting a concrete protoregistry.Types type, accept an interface that provides the necessary functionality to perform the serialization. The advantages of this approach: * There is no need for complex logic to allow a Parent or custom Resolver on the protoregistry.Types type. * Users can pass their own custom resolver implementations directly to the serialization functions. * This is a more principled approach to plumbing custom resolvers than the previous approach of overloading behavior on the concrete Types type. The disadvantages of this approach: * A pointer to a concrete type is 8B, while an interface is 16B. However, the expansion of the {Marshal,Unmarshal}Options structs should be a concern solved separately from how to plumb custom resolvers. * The resolver interfaces as defined today may be insufficient to provide functionality needed in the future if protobuf expands its feature set. For example, let's suppose the Any message permits directly representing a enum by name. This would require the ability to lookup an enum by name. To support that hypothetical need, we can document that the serializers type-assert the provided Resolver to a EnumTypeResolver and use that if possible. There is some loss of type safety with this approach, but provides a clear path forward. Change-Id: I81ca80e59335d36be6b43d57ec8e17abfdfa3bad Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/177044 Reviewed-by: Damien Neil <dneil@google.com>
2019-05-14 14:28:19 -07:00
// MessageTypeResolver is an interface for looking up messages.
//
// A compliant implementation must deterministically return the same type
// if no error is encountered.
//
// The Types type implements this interface.
type MessageTypeResolver interface {
// FindMessageByName looks up a message by its full name.
// E.g., "google.protobuf.Any"
//
proto, encoding/protojson, encoding/prototext: use Resolver interface Instead of accepting a concrete protoregistry.Types type, accept an interface that provides the necessary functionality to perform the serialization. The advantages of this approach: * There is no need for complex logic to allow a Parent or custom Resolver on the protoregistry.Types type. * Users can pass their own custom resolver implementations directly to the serialization functions. * This is a more principled approach to plumbing custom resolvers than the previous approach of overloading behavior on the concrete Types type. The disadvantages of this approach: * A pointer to a concrete type is 8B, while an interface is 16B. However, the expansion of the {Marshal,Unmarshal}Options structs should be a concern solved separately from how to plumb custom resolvers. * The resolver interfaces as defined today may be insufficient to provide functionality needed in the future if protobuf expands its feature set. For example, let's suppose the Any message permits directly representing a enum by name. This would require the ability to lookup an enum by name. To support that hypothetical need, we can document that the serializers type-assert the provided Resolver to a EnumTypeResolver and use that if possible. There is some loss of type safety with this approach, but provides a clear path forward. Change-Id: I81ca80e59335d36be6b43d57ec8e17abfdfa3bad Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/177044 Reviewed-by: Damien Neil <dneil@google.com>
2019-05-14 14:28:19 -07:00
// This return (nil, NotFound) if not found.
FindMessageByName(message protoreflect.FullName) (protoreflect.MessageType, error)
// FindMessageByURL looks up a message by a URL identifier.
// See documentation on google.protobuf.Any.type_url for the URL format.
//
proto, encoding/protojson, encoding/prototext: use Resolver interface Instead of accepting a concrete protoregistry.Types type, accept an interface that provides the necessary functionality to perform the serialization. The advantages of this approach: * There is no need for complex logic to allow a Parent or custom Resolver on the protoregistry.Types type. * Users can pass their own custom resolver implementations directly to the serialization functions. * This is a more principled approach to plumbing custom resolvers than the previous approach of overloading behavior on the concrete Types type. The disadvantages of this approach: * A pointer to a concrete type is 8B, while an interface is 16B. However, the expansion of the {Marshal,Unmarshal}Options structs should be a concern solved separately from how to plumb custom resolvers. * The resolver interfaces as defined today may be insufficient to provide functionality needed in the future if protobuf expands its feature set. For example, let's suppose the Any message permits directly representing a enum by name. This would require the ability to lookup an enum by name. To support that hypothetical need, we can document that the serializers type-assert the provided Resolver to a EnumTypeResolver and use that if possible. There is some loss of type safety with this approach, but provides a clear path forward. Change-Id: I81ca80e59335d36be6b43d57ec8e17abfdfa3bad Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/177044 Reviewed-by: Damien Neil <dneil@google.com>
2019-05-14 14:28:19 -07:00
// This returns (nil, NotFound) if not found.
FindMessageByURL(url string) (protoreflect.MessageType, error)
}
// ExtensionTypeResolver is an interface for looking up extensions.
//
// A compliant implementation must deterministically return the same type
// if no error is encountered.
//
// The Types type implements this interface.
type ExtensionTypeResolver interface {
// FindExtensionByName looks up a 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.
//
proto, encoding/protojson, encoding/prototext: use Resolver interface Instead of accepting a concrete protoregistry.Types type, accept an interface that provides the necessary functionality to perform the serialization. The advantages of this approach: * There is no need for complex logic to allow a Parent or custom Resolver on the protoregistry.Types type. * Users can pass their own custom resolver implementations directly to the serialization functions. * This is a more principled approach to plumbing custom resolvers than the previous approach of overloading behavior on the concrete Types type. The disadvantages of this approach: * A pointer to a concrete type is 8B, while an interface is 16B. However, the expansion of the {Marshal,Unmarshal}Options structs should be a concern solved separately from how to plumb custom resolvers. * The resolver interfaces as defined today may be insufficient to provide functionality needed in the future if protobuf expands its feature set. For example, let's suppose the Any message permits directly representing a enum by name. This would require the ability to lookup an enum by name. To support that hypothetical need, we can document that the serializers type-assert the provided Resolver to a EnumTypeResolver and use that if possible. There is some loss of type safety with this approach, but provides a clear path forward. Change-Id: I81ca80e59335d36be6b43d57ec8e17abfdfa3bad Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/177044 Reviewed-by: Damien Neil <dneil@google.com>
2019-05-14 14:28:19 -07:00
// This returns (nil, NotFound) if not found.
FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error)
// FindExtensionByNumber looks up a extension field by the field number
// within some parent message, identified by full name.
//
proto, encoding/protojson, encoding/prototext: use Resolver interface Instead of accepting a concrete protoregistry.Types type, accept an interface that provides the necessary functionality to perform the serialization. The advantages of this approach: * There is no need for complex logic to allow a Parent or custom Resolver on the protoregistry.Types type. * Users can pass their own custom resolver implementations directly to the serialization functions. * This is a more principled approach to plumbing custom resolvers than the previous approach of overloading behavior on the concrete Types type. The disadvantages of this approach: * A pointer to a concrete type is 8B, while an interface is 16B. However, the expansion of the {Marshal,Unmarshal}Options structs should be a concern solved separately from how to plumb custom resolvers. * The resolver interfaces as defined today may be insufficient to provide functionality needed in the future if protobuf expands its feature set. For example, let's suppose the Any message permits directly representing a enum by name. This would require the ability to lookup an enum by name. To support that hypothetical need, we can document that the serializers type-assert the provided Resolver to a EnumTypeResolver and use that if possible. There is some loss of type safety with this approach, but provides a clear path forward. Change-Id: I81ca80e59335d36be6b43d57ec8e17abfdfa3bad Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/177044 Reviewed-by: Damien Neil <dneil@google.com>
2019-05-14 14:28:19 -07:00
// This returns (nil, NotFound) if not found.
FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error)
}
var (
_ MessageTypeResolver = (*Types)(nil)
_ ExtensionTypeResolver = (*Types)(nil)
)
proto, encoding/protojson, encoding/prototext: use Resolver interface Instead of accepting a concrete protoregistry.Types type, accept an interface that provides the necessary functionality to perform the serialization. The advantages of this approach: * There is no need for complex logic to allow a Parent or custom Resolver on the protoregistry.Types type. * Users can pass their own custom resolver implementations directly to the serialization functions. * This is a more principled approach to plumbing custom resolvers than the previous approach of overloading behavior on the concrete Types type. The disadvantages of this approach: * A pointer to a concrete type is 8B, while an interface is 16B. However, the expansion of the {Marshal,Unmarshal}Options structs should be a concern solved separately from how to plumb custom resolvers. * The resolver interfaces as defined today may be insufficient to provide functionality needed in the future if protobuf expands its feature set. For example, let's suppose the Any message permits directly representing a enum by name. This would require the ability to lookup an enum by name. To support that hypothetical need, we can document that the serializers type-assert the provided Resolver to a EnumTypeResolver and use that if possible. There is some loss of type safety with this approach, but provides a clear path forward. Change-Id: I81ca80e59335d36be6b43d57ec8e17abfdfa3bad Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/177044 Reviewed-by: Damien Neil <dneil@google.com>
2019-05-14 14:28:19 -07:00
// Types is a registry for looking up or iterating over descriptor types.
// The Find and Range methods are safe for concurrent use.
type Types struct {
typesByName typesByName
extensionsByMessage extensionsByMessage
numEnums int
numMessages int
numExtensions int
}
type (
typesByName map[protoreflect.FullName]interface{}
extensionsByMessage map[protoreflect.FullName]extensionsByNumber
extensionsByNumber map[protoreflect.FieldNumber]protoreflect.ExtensionType
)
// RegisterMessage registers the provided message type.
//
// If a naming conflict occurs, the type is not registered and an error is returned.
func (r *Types) RegisterMessage(mt protoreflect.MessageType) error {
if r == GlobalTypes {
globalMutex.Lock()
defer globalMutex.Unlock()
}
if err := r.register("message", mt.Descriptor(), mt); err != nil {
return err
}
r.numMessages++
return nil
}
// RegisterEnum registers the provided enum type.
//
// If a naming conflict occurs, the type is not registered and an error is returned.
func (r *Types) RegisterEnum(et protoreflect.EnumType) error {
if r == GlobalTypes {
globalMutex.Lock()
defer globalMutex.Unlock()
}
if err := r.register("enum", et.Descriptor(), et); err != nil {
return err
}
r.numEnums++
return nil
}
// RegisterExtension registers the provided extension type.
//
// If a naming conflict occurs, the type is not registered and an error is returned.
func (r *Types) RegisterExtension(xt protoreflect.ExtensionType) error {
if r == GlobalTypes {
globalMutex.Lock()
defer globalMutex.Unlock()
}
xd := xt.TypeDescriptor()
field := xd.Number()
message := xd.ContainingMessage().FullName()
if prev := r.extensionsByMessage[message][field]; prev != nil {
err := errors.New("extension number %d is already registered on message %v", field, message)
err = amendErrorWithCaller(err, prev, xt)
if !(r == GlobalTypes && ignoreConflict(xd, err)) {
return err
}
}
if err := r.register("extension", xt.TypeDescriptor(), xt); err != nil {
return err
}
if r.extensionsByMessage == nil {
r.extensionsByMessage = make(extensionsByMessage)
}
if r.extensionsByMessage[message] == nil {
r.extensionsByMessage[message] = make(extensionsByNumber)
}
r.extensionsByMessage[message][field] = xt
r.numExtensions++
return nil
}
func (r *Types) register(kind string, desc protoreflect.Descriptor, typ interface{}) error {
name := desc.FullName()
prev := r.typesByName[name]
if prev != nil {
err := errors.New("%v %v is already registered", kind, name)
err = amendErrorWithCaller(err, prev, typ)
if !(r == GlobalTypes && ignoreConflict(desc, err)) {
return err
}
}
if r.typesByName == nil {
r.typesByName = make(typesByName)
}
r.typesByName[name] = typ
return nil
}
// FindEnumByName looks up an enum by its full name.
// E.g., "google.protobuf.Field.Kind".
//
// This returns (nil, NotFound) if not found.
func (r *Types) FindEnumByName(enum protoreflect.FullName) (protoreflect.EnumType, error) {
if r == nil {
return nil, NotFound
}
if r == GlobalTypes {
globalMutex.RLock()
defer globalMutex.RUnlock()
}
if v := r.typesByName[enum]; v != nil {
if et, _ := v.(protoreflect.EnumType); et != nil {
return et, nil
}
return nil, errors.New("found wrong type: got %v, want enum", typeName(v))
}
proto, encoding/protojson, encoding/prototext: use Resolver interface Instead of accepting a concrete protoregistry.Types type, accept an interface that provides the necessary functionality to perform the serialization. The advantages of this approach: * There is no need for complex logic to allow a Parent or custom Resolver on the protoregistry.Types type. * Users can pass their own custom resolver implementations directly to the serialization functions. * This is a more principled approach to plumbing custom resolvers than the previous approach of overloading behavior on the concrete Types type. The disadvantages of this approach: * A pointer to a concrete type is 8B, while an interface is 16B. However, the expansion of the {Marshal,Unmarshal}Options structs should be a concern solved separately from how to plumb custom resolvers. * The resolver interfaces as defined today may be insufficient to provide functionality needed in the future if protobuf expands its feature set. For example, let's suppose the Any message permits directly representing a enum by name. This would require the ability to lookup an enum by name. To support that hypothetical need, we can document that the serializers type-assert the provided Resolver to a EnumTypeResolver and use that if possible. There is some loss of type safety with this approach, but provides a clear path forward. Change-Id: I81ca80e59335d36be6b43d57ec8e17abfdfa3bad Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/177044 Reviewed-by: Damien Neil <dneil@google.com>
2019-05-14 14:28:19 -07:00
return nil, NotFound
}
// FindMessageByName looks up a message by its full name.
// E.g., "google.protobuf.Any"
//
// This return (nil, NotFound) if not found.
func (r *Types) FindMessageByName(message protoreflect.FullName) (protoreflect.MessageType, error) {
// The full name by itself is a valid URL.
return r.FindMessageByURL(string(message))
}
// FindMessageByURL looks up a message by a URL identifier.
proto, encoding/protojson, encoding/prototext: use Resolver interface Instead of accepting a concrete protoregistry.Types type, accept an interface that provides the necessary functionality to perform the serialization. The advantages of this approach: * There is no need for complex logic to allow a Parent or custom Resolver on the protoregistry.Types type. * Users can pass their own custom resolver implementations directly to the serialization functions. * This is a more principled approach to plumbing custom resolvers than the previous approach of overloading behavior on the concrete Types type. The disadvantages of this approach: * A pointer to a concrete type is 8B, while an interface is 16B. However, the expansion of the {Marshal,Unmarshal}Options structs should be a concern solved separately from how to plumb custom resolvers. * The resolver interfaces as defined today may be insufficient to provide functionality needed in the future if protobuf expands its feature set. For example, let's suppose the Any message permits directly representing a enum by name. This would require the ability to lookup an enum by name. To support that hypothetical need, we can document that the serializers type-assert the provided Resolver to a EnumTypeResolver and use that if possible. There is some loss of type safety with this approach, but provides a clear path forward. Change-Id: I81ca80e59335d36be6b43d57ec8e17abfdfa3bad Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/177044 Reviewed-by: Damien Neil <dneil@google.com>
2019-05-14 14:28:19 -07:00
// See documentation on google.protobuf.Any.type_url for the URL format.
//
// This returns (nil, NotFound) if not found.
func (r *Types) FindMessageByURL(url string) (protoreflect.MessageType, error) {
if r == nil {
return nil, NotFound
}
if r == GlobalTypes {
globalMutex.RLock()
defer globalMutex.RUnlock()
}
message := protoreflect.FullName(url)
if i := strings.LastIndexByte(url, '/'); i >= 0 {
message = message[i+len("/"):]
}
if v := r.typesByName[message]; v != nil {
if mt, _ := v.(protoreflect.MessageType); mt != nil {
return mt, nil
}
return nil, errors.New("found wrong type: got %v, want message", typeName(v))
}
proto, encoding/protojson, encoding/prototext: use Resolver interface Instead of accepting a concrete protoregistry.Types type, accept an interface that provides the necessary functionality to perform the serialization. The advantages of this approach: * There is no need for complex logic to allow a Parent or custom Resolver on the protoregistry.Types type. * Users can pass their own custom resolver implementations directly to the serialization functions. * This is a more principled approach to plumbing custom resolvers than the previous approach of overloading behavior on the concrete Types type. The disadvantages of this approach: * A pointer to a concrete type is 8B, while an interface is 16B. However, the expansion of the {Marshal,Unmarshal}Options structs should be a concern solved separately from how to plumb custom resolvers. * The resolver interfaces as defined today may be insufficient to provide functionality needed in the future if protobuf expands its feature set. For example, let's suppose the Any message permits directly representing a enum by name. This would require the ability to lookup an enum by name. To support that hypothetical need, we can document that the serializers type-assert the provided Resolver to a EnumTypeResolver and use that if possible. There is some loss of type safety with this approach, but provides a clear path forward. Change-Id: I81ca80e59335d36be6b43d57ec8e17abfdfa3bad Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/177044 Reviewed-by: Damien Neil <dneil@google.com>
2019-05-14 14:28:19 -07:00
return nil, NotFound
}
// FindExtensionByName looks up a 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, NotFound) if not found.
func (r *Types) FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error) {
if r == nil {
return nil, NotFound
}
if r == GlobalTypes {
globalMutex.RLock()
defer globalMutex.RUnlock()
}
if v := r.typesByName[field]; v != nil {
if xt, _ := v.(protoreflect.ExtensionType); xt != nil {
return xt, nil
}
return nil, errors.New("found wrong type: got %v, want extension", typeName(v))
}
proto, encoding/protojson, encoding/prototext: use Resolver interface Instead of accepting a concrete protoregistry.Types type, accept an interface that provides the necessary functionality to perform the serialization. The advantages of this approach: * There is no need for complex logic to allow a Parent or custom Resolver on the protoregistry.Types type. * Users can pass their own custom resolver implementations directly to the serialization functions. * This is a more principled approach to plumbing custom resolvers than the previous approach of overloading behavior on the concrete Types type. The disadvantages of this approach: * A pointer to a concrete type is 8B, while an interface is 16B. However, the expansion of the {Marshal,Unmarshal}Options structs should be a concern solved separately from how to plumb custom resolvers. * The resolver interfaces as defined today may be insufficient to provide functionality needed in the future if protobuf expands its feature set. For example, let's suppose the Any message permits directly representing a enum by name. This would require the ability to lookup an enum by name. To support that hypothetical need, we can document that the serializers type-assert the provided Resolver to a EnumTypeResolver and use that if possible. There is some loss of type safety with this approach, but provides a clear path forward. Change-Id: I81ca80e59335d36be6b43d57ec8e17abfdfa3bad Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/177044 Reviewed-by: Damien Neil <dneil@google.com>
2019-05-14 14:28:19 -07:00
return nil, NotFound
}
// FindExtensionByNumber looks up a extension field by the field number
// within some parent message, identified by full name.
//
// This returns (nil, NotFound) if not found.
func (r *Types) FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error) {
if r == nil {
return nil, NotFound
}
if r == GlobalTypes {
globalMutex.RLock()
defer globalMutex.RUnlock()
}
if xt, ok := r.extensionsByMessage[message][field]; ok {
return xt, nil
}
proto, encoding/protojson, encoding/prototext: use Resolver interface Instead of accepting a concrete protoregistry.Types type, accept an interface that provides the necessary functionality to perform the serialization. The advantages of this approach: * There is no need for complex logic to allow a Parent or custom Resolver on the protoregistry.Types type. * Users can pass their own custom resolver implementations directly to the serialization functions. * This is a more principled approach to plumbing custom resolvers than the previous approach of overloading behavior on the concrete Types type. The disadvantages of this approach: * A pointer to a concrete type is 8B, while an interface is 16B. However, the expansion of the {Marshal,Unmarshal}Options structs should be a concern solved separately from how to plumb custom resolvers. * The resolver interfaces as defined today may be insufficient to provide functionality needed in the future if protobuf expands its feature set. For example, let's suppose the Any message permits directly representing a enum by name. This would require the ability to lookup an enum by name. To support that hypothetical need, we can document that the serializers type-assert the provided Resolver to a EnumTypeResolver and use that if possible. There is some loss of type safety with this approach, but provides a clear path forward. Change-Id: I81ca80e59335d36be6b43d57ec8e17abfdfa3bad Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/177044 Reviewed-by: Damien Neil <dneil@google.com>
2019-05-14 14:28:19 -07:00
return nil, NotFound
}
// NumEnums reports the number of registered enums.
func (r *Types) NumEnums() int {
if r == nil {
return 0
}
if r == GlobalTypes {
globalMutex.RLock()
defer globalMutex.RUnlock()
}
return r.numEnums
}
// RangeEnums iterates over all registered enums while f returns true.
// Iteration order is undefined.
func (r *Types) RangeEnums(f func(protoreflect.EnumType) bool) {
if r == nil {
return
}
if r == GlobalTypes {
globalMutex.RLock()
defer globalMutex.RUnlock()
}
for _, typ := range r.typesByName {
if et, ok := typ.(protoreflect.EnumType); ok {
if !f(et) {
return
}
}
}
}
// NumMessages reports the number of registered messages.
func (r *Types) NumMessages() int {
if r == nil {
return 0
}
if r == GlobalTypes {
globalMutex.RLock()
defer globalMutex.RUnlock()
}
return r.numMessages
}
// RangeMessages iterates over all registered messages while f returns true.
// Iteration order is undefined.
func (r *Types) RangeMessages(f func(protoreflect.MessageType) bool) {
if r == nil {
return
}
if r == GlobalTypes {
globalMutex.RLock()
defer globalMutex.RUnlock()
}
for _, typ := range r.typesByName {
if mt, ok := typ.(protoreflect.MessageType); ok {
if !f(mt) {
return
}
}
}
}
// NumExtensions reports the number of registered extensions.
func (r *Types) NumExtensions() int {
if r == nil {
return 0
}
if r == GlobalTypes {
globalMutex.RLock()
defer globalMutex.RUnlock()
}
return r.numExtensions
}
// RangeExtensions iterates over all registered extensions while f returns true.
// Iteration order is undefined.
func (r *Types) RangeExtensions(f func(protoreflect.ExtensionType) bool) {
if r == nil {
return
}
if r == GlobalTypes {
globalMutex.RLock()
defer globalMutex.RUnlock()
}
for _, typ := range r.typesByName {
if xt, ok := typ.(protoreflect.ExtensionType); ok {
if !f(xt) {
return
}
}
}
}
// NumExtensionsByMessage reports the number of registered extensions for
// a given message type.
func (r *Types) NumExtensionsByMessage(message protoreflect.FullName) int {
if r == nil {
return 0
}
if r == GlobalTypes {
globalMutex.RLock()
defer globalMutex.RUnlock()
}
return len(r.extensionsByMessage[message])
}
// RangeExtensionsByMessage iterates over all registered extensions filtered
// by a given message type while f returns true. Iteration order is undefined.
func (r *Types) RangeExtensionsByMessage(message protoreflect.FullName, f func(protoreflect.ExtensionType) bool) {
if r == nil {
return
}
if r == GlobalTypes {
globalMutex.RLock()
defer globalMutex.RUnlock()
}
for _, xt := range r.extensionsByMessage[message] {
if !f(xt) {
return
}
}
}
func typeName(t interface{}) string {
switch t.(type) {
case protoreflect.EnumType:
return "enum"
case protoreflect.MessageType:
return "message"
case protoreflect.ExtensionType:
return "extension"
default:
return fmt.Sprintf("%T", t)
}
}
func amendErrorWithCaller(err error, prev, curr interface{}) error {
prevPkg := goPackage(prev)
currPkg := goPackage(curr)
if prevPkg == "" || currPkg == "" || prevPkg == currPkg {
return err
}
return errors.New("%s\n\tpreviously from: %q\n\tcurrently from: %q", err, prevPkg, currPkg)
}
func goPackage(v interface{}) string {
switch d := v.(type) {
case protoreflect.EnumType:
v = d.Descriptor()
case protoreflect.MessageType:
v = d.Descriptor()
case protoreflect.ExtensionType:
v = d.TypeDescriptor()
}
if d, ok := v.(protoreflect.Descriptor); ok {
v = d.ParentFile()
}
if d, ok := v.(interface{ GoPackagePath() string }); ok {
return d.GoPackagePath()
}
return ""
}