mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-03-08 19:14:05 +00:00
reflect/protoregistry: initial commit
Package protoregistry provides data structures to register and lookup protobuf descriptor types. High-level API: var GlobalFiles = new(Files) var NotFound = errors.New("not found") type Files struct{ ... } func NewFiles(...pref.FileDescriptor) *Files func (*Files) Register(...pref.FileDescriptor) error func (*Files) FindDescriptorByName(pref.FullName) (pref.Descriptor, error) func (*Files) RangeFiles(func(pref.FileDescriptor) bool) func (*Files) RangeFilesByPackage(pref.FullName, func(pref.FileDescriptor) bool) func (*Files) RangeFilesByPath(string, func(pref.FileDescriptor) bool) To support the FindDescriptorByName method, we add a DescriptorByName to protoreflect.FileDescriptor and associated logic to prototype. Change-Id: I14d65f74d2bd9f4f48641da9dfa70190310e5878 Reviewed-on: https://go-review.googlesource.com/129499 Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
parent
c046d8696c
commit
095e462e1c
@ -178,6 +178,9 @@ type FileDescriptor interface {
|
||||
Extensions() ExtensionDescriptors
|
||||
// Services is a list of the top-level service declarations.
|
||||
Services() ServiceDescriptors
|
||||
// DescriptorByName looks up any descriptor declared within this file
|
||||
// by full name. It returns nil if not found.
|
||||
DescriptorByName(FullName) Descriptor
|
||||
|
||||
isFileDescriptor
|
||||
}
|
||||
|
291
reflect/protoregistry/registry.go
Normal file
291
reflect/protoregistry/registry.go
Normal file
@ -0,0 +1,291 @@
|
||||
// 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.
|
||||
package protoregistry
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/proto/internal/errors"
|
||||
"google.golang.org/proto/reflect/protoreflect"
|
||||
)
|
||||
|
||||
// TODO: Perhaps Register should record the frame of where the function was
|
||||
// called and surface that in the error? That would help users debug duplicate
|
||||
// registration issues. This presumes that we provide a way to disable automatic
|
||||
// registration in generated code.
|
||||
|
||||
// TODO: Add a type registry:
|
||||
/*
|
||||
var GlobalTypes = new(Types)
|
||||
|
||||
type Type interface {
|
||||
protoreflect.Descriptor
|
||||
GoType() reflect.Type
|
||||
}
|
||||
type Types struct {
|
||||
Parent *Types
|
||||
Resolver func(url string) (Type, error)
|
||||
}
|
||||
func NewTypes(typs ...Type) *Types
|
||||
func (*Types) Register(typs ...Type) error
|
||||
func (*Types) FindEnumByName(enum protoreflect.FullName) (protoreflect.EnumType, error)
|
||||
func (*Types) FindMessageByName(message protoreflect.FullName) (protoreflect.MessageType, error)
|
||||
func (*Types) FindMessageByURL(url string) (protoreflect.MessageType, error)
|
||||
func (*Types) FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error)
|
||||
func (*Types) FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error)
|
||||
func (*Types) RangeEnums(f func(protoreflect.EnumType) bool)
|
||||
func (*Types) RangeMessages(f func(protoreflect.MessageType) bool)
|
||||
func (*Types) RangeExtensions(f func(protoreflect.ExtensionType) bool)
|
||||
func (*Types) RangeExtensionsByMessage(message protoreflect.FullName, f func(protoreflect.ExtensionType) bool)
|
||||
*/
|
||||
|
||||
// GlobalFiles is a global registry of file descriptors.
|
||||
var GlobalFiles = new(Files)
|
||||
|
||||
// NotFound is a sentinel error value to indicate that the type was not found.
|
||||
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 {
|
||||
filesByPackage filesByPackage
|
||||
filesByPath filesByPath
|
||||
}
|
||||
|
||||
type (
|
||||
filesByPackage struct {
|
||||
// files is a list of files all in the same package.
|
||||
files []protoreflect.FileDescriptor
|
||||
// subs is a tree of files all in a sub-package scope.
|
||||
// It also maps all top-level identifiers declared in files
|
||||
// as the notProtoPackage sentinel value.
|
||||
subs map[protoreflect.Name]*filesByPackage // invariant: len(Name) > 0
|
||||
}
|
||||
filesByPath map[string][]protoreflect.FileDescriptor
|
||||
)
|
||||
|
||||
// notProtoPackage is a sentinel value to indicate that some identifier maps
|
||||
// to an actual protobuf declaration and is not a sub-package.
|
||||
var notProtoPackage = new(filesByPackage)
|
||||
|
||||
// NewFiles returns a registry initialized with the provided set of files.
|
||||
// If there are duplicates, the first one takes precedence.
|
||||
func NewFiles(files ...protoreflect.FileDescriptor) *Files {
|
||||
// TODO: Should last take precedence? This allows a user to intentionally
|
||||
// overwrite an existing registration.
|
||||
//
|
||||
// The use case is for implementing the existing v1 proto.RegisterFile
|
||||
// function where the behavior is last wins. However, it could be argued
|
||||
// that the v1 behavior is broken, and we can switch to first wins
|
||||
// without violating compatibility.
|
||||
r := new(Files)
|
||||
r.Register(files...) // ignore errors; first takes precedence
|
||||
return r
|
||||
}
|
||||
|
||||
// Register registers the provided list of file descriptors.
|
||||
// Placeholder files are ignored.
|
||||
//
|
||||
// If any descriptor within a file conflicts with the descriptor of any
|
||||
// previously registered file (e.g., two enums with the same full name),
|
||||
// then that file is not registered and an error is returned.
|
||||
//
|
||||
// It is permitted for multiple files to have the same file path.
|
||||
func (r *Files) Register(files ...protoreflect.FileDescriptor) error {
|
||||
var firstErr error
|
||||
fileLoop:
|
||||
for _, file := range files {
|
||||
if file.IsPlaceholder() {
|
||||
continue // TODO: Should this be an error instead?
|
||||
}
|
||||
|
||||
// Register the file into the filesByPackage tree.
|
||||
//
|
||||
// The prototype package validates that a FileDescriptor is internally
|
||||
// consistent such it does not have conflicts within itself.
|
||||
// However, we need to ensure that the inserted file does not conflict
|
||||
// with other previously inserted files.
|
||||
pkg := file.Package()
|
||||
root := &r.filesByPackage
|
||||
for len(pkg) > 0 {
|
||||
var prefix protoreflect.Name
|
||||
prefix, pkg = splitPrefix(pkg)
|
||||
|
||||
// Add a new sub-package segment.
|
||||
switch nextRoot := root.subs[prefix]; nextRoot {
|
||||
case nil:
|
||||
nextRoot = new(filesByPackage)
|
||||
if root.subs == nil {
|
||||
root.subs = make(map[protoreflect.Name]*filesByPackage)
|
||||
}
|
||||
root.subs[prefix] = nextRoot
|
||||
root = nextRoot
|
||||
case notProtoPackage:
|
||||
if firstErr == nil {
|
||||
name := strings.TrimSuffix(strings.TrimSuffix(string(file.Package()), string(pkg)), ".")
|
||||
firstErr = errors.New("file %q has a name conflict over %v", file.Path(), name)
|
||||
}
|
||||
continue fileLoop
|
||||
default:
|
||||
root = nextRoot
|
||||
}
|
||||
}
|
||||
// Check for top-level conflicts within the same package.
|
||||
// The current file cannot add any top-level declaration that conflict
|
||||
// with another top-level declaration or sub-package name.
|
||||
var conflicts []protoreflect.Name
|
||||
rangeTopLevelDeclarations(file, func(s protoreflect.Name) {
|
||||
if root.subs[s] == nil {
|
||||
if root.subs == nil {
|
||||
root.subs = make(map[protoreflect.Name]*filesByPackage)
|
||||
}
|
||||
root.subs[s] = notProtoPackage
|
||||
} else {
|
||||
conflicts = append(conflicts, s)
|
||||
}
|
||||
})
|
||||
if len(conflicts) > 0 {
|
||||
// Remove inserted identifiers to make registration failure atomic.
|
||||
sort.Slice(conflicts, func(i, j int) bool { return conflicts[i] < conflicts[j] })
|
||||
rangeTopLevelDeclarations(file, func(s protoreflect.Name) {
|
||||
i := sort.Search(len(conflicts), func(i int) bool { return conflicts[i] >= s })
|
||||
if has := i < len(conflicts) && conflicts[i] == s; !has {
|
||||
delete(root.subs, s) // remove everything not in conflicts
|
||||
}
|
||||
})
|
||||
|
||||
if firstErr == nil {
|
||||
name := file.Package().Append(conflicts[0])
|
||||
firstErr = errors.New("file %q has a name conflict over %v", file.Path(), name)
|
||||
}
|
||||
continue fileLoop
|
||||
}
|
||||
root.files = append(root.files, file)
|
||||
|
||||
// Register the file into the filesByPath map.
|
||||
//
|
||||
// There is no check for conflicts in file path since the path is
|
||||
// heavily dependent on how protoc is invoked. When protoc is being
|
||||
// invoked by different parties in a distributed manner, it is
|
||||
// unreasonable to assume nor ensure that the path is unique.
|
||||
if r.filesByPath == nil {
|
||||
r.filesByPath = make(filesByPath)
|
||||
}
|
||||
r.filesByPath[file.Path()] = append(r.filesByPath[file.Path()], file)
|
||||
}
|
||||
return firstErr
|
||||
}
|
||||
|
||||
// FindDescriptorByName looks up any descriptor (except files) by its full name.
|
||||
// Files are not handled since multiple file descriptors may belong in
|
||||
// the same package and have the same full name (see RangeFilesByPackage).
|
||||
//
|
||||
// This return (nil, NotFound) if not found.
|
||||
func (r *Files) FindDescriptorByName(name protoreflect.FullName) (protoreflect.Descriptor, error) {
|
||||
pkg := name
|
||||
root := &r.filesByPackage
|
||||
for len(pkg) > 0 {
|
||||
var prefix protoreflect.Name
|
||||
prefix, pkg = splitPrefix(pkg)
|
||||
switch nextRoot := root.subs[prefix]; nextRoot {
|
||||
case nil:
|
||||
return nil, NotFound
|
||||
case notProtoPackage:
|
||||
// Search current root's package for the descriptor.
|
||||
for _, fd := range root.files {
|
||||
if d := fd.DescriptorByName(name); d != nil {
|
||||
return d, nil
|
||||
}
|
||||
}
|
||||
return nil, NotFound
|
||||
default:
|
||||
root = nextRoot
|
||||
}
|
||||
}
|
||||
return nil, NotFound
|
||||
}
|
||||
|
||||
// RangeFiles iterates over all registered files.
|
||||
// The iteration order is undefined.
|
||||
func (r *Files) RangeFiles(f func(protoreflect.FileDescriptor) bool) {
|
||||
r.RangeFilesByPackage("", f) // empty package is a prefix for all packages
|
||||
}
|
||||
|
||||
// RangeFilesByPackage iterates over all registered files filtered by
|
||||
// the given proto package prefix. It iterates over files with an exact package
|
||||
// match before iterating over files with general prefix match.
|
||||
// The iteration order is undefined within exact matches or prefix matches.
|
||||
func (r *Files) RangeFilesByPackage(pkg protoreflect.FullName, f func(protoreflect.FileDescriptor) bool) {
|
||||
if strings.HasSuffix(string(pkg), ".") {
|
||||
return // avoid edge case where splitPrefix allows trailing dot
|
||||
}
|
||||
root := &r.filesByPackage
|
||||
for len(pkg) > 0 && root != nil {
|
||||
var prefix protoreflect.Name
|
||||
prefix, pkg = splitPrefix(pkg)
|
||||
root = root.subs[prefix]
|
||||
}
|
||||
rangeFiles(root, f)
|
||||
}
|
||||
func rangeFiles(fs *filesByPackage, f func(protoreflect.FileDescriptor) bool) bool {
|
||||
if fs == nil {
|
||||
return true
|
||||
}
|
||||
// Iterate over exact matches.
|
||||
for _, fd := range fs.files { // TODO: iterate non-deterministically
|
||||
if !f(fd) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// Iterate over prefix matches.
|
||||
for _, fs := range fs.subs {
|
||||
if !rangeFiles(fs, f) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// RangeFilesByPath iterates over all registered files filtered by
|
||||
// the given proto path. The iteration order is undefined.
|
||||
func (r *Files) RangeFilesByPath(path string, f func(protoreflect.FileDescriptor) bool) {
|
||||
for _, fd := range r.filesByPath[path] { // TODO: iterate non-deterministically
|
||||
if !f(fd) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func splitPrefix(name protoreflect.FullName) (protoreflect.Name, protoreflect.FullName) {
|
||||
if i := strings.IndexByte(string(name), '.'); i >= 0 {
|
||||
return protoreflect.Name(name[:i]), name[i+len("."):]
|
||||
}
|
||||
return protoreflect.Name(name), ""
|
||||
}
|
||||
|
||||
// rangeTopLevelDeclarations iterates over the name of all top-level
|
||||
// declarations in the proto file.
|
||||
func rangeTopLevelDeclarations(fd protoreflect.FileDescriptor, f func(protoreflect.Name)) {
|
||||
for i := 0; i < fd.Enums().Len(); i++ {
|
||||
e := fd.Enums().Get(i)
|
||||
f(e.Name())
|
||||
for i := 0; i < e.Values().Len(); i++ {
|
||||
f(e.Values().Get(i).Name())
|
||||
}
|
||||
}
|
||||
for i := 0; i < fd.Messages().Len(); i++ {
|
||||
f(fd.Messages().Get(i).Name())
|
||||
}
|
||||
for i := 0; i < fd.Extensions().Len(); i++ {
|
||||
f(fd.Extensions().Get(i).Name())
|
||||
}
|
||||
for i := 0; i < fd.Services().Len(); i++ {
|
||||
f(fd.Services().Get(i).Name())
|
||||
}
|
||||
}
|
313
reflect/protoregistry/registry_test.go
Normal file
313
reflect/protoregistry/registry_test.go
Normal file
@ -0,0 +1,313 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
|
||||
pref "google.golang.org/proto/reflect/protoreflect"
|
||||
ptype "google.golang.org/proto/reflect/prototype"
|
||||
)
|
||||
|
||||
func TestFiles(t *testing.T) {
|
||||
type (
|
||||
file struct {
|
||||
Path string
|
||||
Pkg pref.FullName
|
||||
}
|
||||
testFile struct {
|
||||
inFile *ptype.File
|
||||
wantErr string
|
||||
}
|
||||
testFindDesc struct {
|
||||
inName pref.FullName
|
||||
wantOk bool
|
||||
}
|
||||
testRangePkg struct {
|
||||
inPkg pref.FullName
|
||||
wantFiles []file
|
||||
}
|
||||
testRangePath struct {
|
||||
inPath string
|
||||
wantFiles []file
|
||||
}
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
files []testFile
|
||||
findDescs []testFindDesc
|
||||
rangePkgs []testRangePkg
|
||||
rangePaths []testRangePath
|
||||
}{{
|
||||
// Test that overlapping packages and files are permitted.
|
||||
files: []testFile{
|
||||
{inFile: &ptype.File{Syntax: pref.Proto2, Package: "foo.bar"}},
|
||||
{inFile: &ptype.File{Syntax: pref.Proto2, Path: "foo/bar/test.proto", Package: "my.test"}},
|
||||
{inFile: &ptype.File{Syntax: pref.Proto2, Path: "foo/bar/test.proto", Package: "foo.bar.baz"}},
|
||||
{inFile: &ptype.File{Syntax: pref.Proto2, Package: "my.test.package"}},
|
||||
{inFile: &ptype.File{Syntax: pref.Proto2, Package: "foo.bar"}},
|
||||
{inFile: &ptype.File{Syntax: pref.Proto2, Path: "foo/bar/baz/../test.proto", Package: "my.test"}},
|
||||
},
|
||||
|
||||
rangePkgs: []testRangePkg{{
|
||||
inPkg: "nothing",
|
||||
}, {
|
||||
inPkg: "",
|
||||
wantFiles: []file{
|
||||
{"", "foo.bar"},
|
||||
{"", "foo.bar"},
|
||||
{"foo/bar/test.proto", "foo.bar.baz"},
|
||||
{"foo/bar/test.proto", "my.test"},
|
||||
{"", "my.test.package"},
|
||||
{"foo/bar/baz/../test.proto", "my.test"},
|
||||
},
|
||||
}, {
|
||||
inPkg: ".",
|
||||
}, {
|
||||
inPkg: "foo",
|
||||
wantFiles: []file{
|
||||
{"", "foo.bar"},
|
||||
{"", "foo.bar"},
|
||||
{"foo/bar/test.proto", "foo.bar.baz"},
|
||||
},
|
||||
}, {
|
||||
inPkg: "foo.",
|
||||
}, {
|
||||
inPkg: "foo..",
|
||||
}, {
|
||||
inPkg: "foo.bar.baz",
|
||||
wantFiles: []file{
|
||||
{"foo/bar/test.proto", "foo.bar.baz"},
|
||||
},
|
||||
}, {
|
||||
inPkg: "fo",
|
||||
}},
|
||||
|
||||
rangePaths: []testRangePath{{
|
||||
inPath: "nothing",
|
||||
}, {
|
||||
inPath: "",
|
||||
wantFiles: []file{
|
||||
{"", "foo.bar"},
|
||||
{"", "foo.bar"},
|
||||
{"", "my.test.package"},
|
||||
},
|
||||
}, {
|
||||
inPath: "foo/bar/test.proto",
|
||||
wantFiles: []file{
|
||||
{"foo/bar/test.proto", "foo.bar.baz"},
|
||||
{"foo/bar/test.proto", "my.test"},
|
||||
},
|
||||
}},
|
||||
}, {
|
||||
// Test when new enum conflicts with existing package.
|
||||
files: []testFile{{
|
||||
inFile: &ptype.File{Syntax: pref.Proto2, Path: "test1a.proto", Package: "foo.bar.baz"},
|
||||
}, {
|
||||
inFile: &ptype.File{Syntax: pref.Proto2, Path: "test1b.proto", Enums: []ptype.Enum{{Name: "foo"}}},
|
||||
wantErr: `file "test1b.proto" has a name conflict over foo`,
|
||||
}},
|
||||
}, {
|
||||
// Test when new package conflicts with existing enum.
|
||||
files: []testFile{{
|
||||
inFile: &ptype.File{Syntax: pref.Proto2, Path: "test2a.proto", Enums: []ptype.Enum{{Name: "foo"}}},
|
||||
}, {
|
||||
inFile: &ptype.File{Syntax: pref.Proto2, Path: "test2b.proto", Package: "foo.bar.baz"},
|
||||
wantErr: `file "test2b.proto" has a name conflict over foo`,
|
||||
}},
|
||||
}, {
|
||||
// Test when new enum conflicts with existing enum in same package.
|
||||
files: []testFile{{
|
||||
inFile: &ptype.File{Syntax: pref.Proto2, Path: "test3a.proto", Package: "foo", Enums: []ptype.Enum{{Name: "BAR"}}},
|
||||
}, {
|
||||
inFile: &ptype.File{Syntax: pref.Proto2, Path: "test3b.proto", Package: "foo", Enums: []ptype.Enum{{Name: "BAR"}}},
|
||||
wantErr: `file "test3b.proto" has a name conflict over foo.BAR`,
|
||||
}},
|
||||
}, {
|
||||
files: []testFile{{
|
||||
inFile: &ptype.File{
|
||||
Syntax: pref.Proto2,
|
||||
Package: "fizz.buzz",
|
||||
Messages: []ptype.Message{{
|
||||
Name: "Message",
|
||||
Fields: []ptype.Field{{
|
||||
Name: "Field",
|
||||
Number: 1,
|
||||
Cardinality: pref.Optional,
|
||||
Kind: pref.StringKind,
|
||||
OneofName: "Oneof",
|
||||
}},
|
||||
Oneofs: []ptype.Oneof{{Name: "Oneof"}},
|
||||
ExtensionRanges: [][2]pref.FieldNumber{{1000, 2000}},
|
||||
}},
|
||||
Enums: []ptype.Enum{{
|
||||
Name: "Enum",
|
||||
Values: []ptype.EnumValue{{Name: "EnumValue", Number: 0}},
|
||||
}},
|
||||
Extensions: []ptype.Extension{{
|
||||
Name: "Extension",
|
||||
Number: 1000,
|
||||
Cardinality: pref.Optional,
|
||||
Kind: pref.StringKind,
|
||||
ExtendedType: ptype.PlaceholderMessage("fizz.buzz.Message"),
|
||||
}},
|
||||
Services: []ptype.Service{{
|
||||
Name: "Service",
|
||||
Methods: []ptype.Method{{
|
||||
Name: "Method",
|
||||
InputType: ptype.PlaceholderMessage("fizz.buzz.Message"),
|
||||
OutputType: ptype.PlaceholderMessage("fizz.buzz.Message"),
|
||||
IsStreamingClient: true,
|
||||
IsStreamingServer: true,
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}, {
|
||||
inFile: &ptype.File{
|
||||
Syntax: pref.Proto2,
|
||||
Package: "fizz.buzz.gazz",
|
||||
Enums: []ptype.Enum{{
|
||||
Name: "Enum",
|
||||
Values: []ptype.EnumValue{{Name: "EnumValue", Number: 0}},
|
||||
}},
|
||||
},
|
||||
}, {
|
||||
// Conflict over a single declaration.
|
||||
inFile: &ptype.File{
|
||||
Syntax: pref.Proto2,
|
||||
Package: "fizz.buzz",
|
||||
Enums: []ptype.Enum{{
|
||||
Name: "Enum1",
|
||||
Values: []ptype.EnumValue{{Name: "EnumValue1", Number: 0}},
|
||||
}, {
|
||||
Name: "Enum2",
|
||||
Values: []ptype.EnumValue{{Name: "EnumValue2", Number: 0}},
|
||||
}, {
|
||||
Name: "Enum3",
|
||||
Values: []ptype.EnumValue{{Name: "Enum", Number: 0}}, // conflict
|
||||
}},
|
||||
},
|
||||
wantErr: "name conflict over fizz.buzz.Enum",
|
||||
}, {
|
||||
// Previously failed registration should not pollute the namespace.
|
||||
inFile: &ptype.File{
|
||||
Syntax: pref.Proto2,
|
||||
Package: "fizz.buzz",
|
||||
Enums: []ptype.Enum{{
|
||||
Name: "Enum1",
|
||||
Values: []ptype.EnumValue{{Name: "EnumValue1", Number: 0}},
|
||||
}, {
|
||||
Name: "Enum2",
|
||||
Values: []ptype.EnumValue{{Name: "EnumValue2", Number: 0}},
|
||||
}},
|
||||
},
|
||||
}, {
|
||||
// Make sure we can register without package name.
|
||||
inFile: &ptype.File{
|
||||
Syntax: pref.Proto2,
|
||||
Messages: []ptype.Message{{
|
||||
Name: "Message",
|
||||
Messages: []ptype.Message{{
|
||||
Name: "Message",
|
||||
Messages: []ptype.Message{{
|
||||
Name: "Message",
|
||||
}},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
|
||||
findDescs: []testFindDesc{
|
||||
{"", false},
|
||||
{"Enum", false},
|
||||
{"Message", true},
|
||||
{"Message.", false},
|
||||
{"Message.Message", true},
|
||||
{"Message.Message.Message", true},
|
||||
{"Message.Message.Message.Message", false},
|
||||
{"fizz.buzz", false},
|
||||
{"fizz.buzz.Enum", true},
|
||||
{"fizz.buzz.Enum1", true},
|
||||
{"fizz.buzz.Enum1.EnumValue", false},
|
||||
{"fizz.buzz.EnumValue", true},
|
||||
{"fizz.buzz.Message", true},
|
||||
{"fizz.buzz.Message.Field", true},
|
||||
{"fizz.buzz.Message.Oneof", true},
|
||||
{"fizz.buzz.Extension", true},
|
||||
{"fizz.buzz.Service", true},
|
||||
{"fizz.buzz.Service.Method", true},
|
||||
{"fizz.buzz.Method", false},
|
||||
},
|
||||
}}
|
||||
|
||||
sortFiles := cmpopts.SortSlices(func(x, y file) bool {
|
||||
return x.Path < y.Path || (x.Path == y.Path && x.Pkg < y.Pkg)
|
||||
})
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
var files Files
|
||||
for i, tc := range tt.files {
|
||||
fd, err := ptype.NewFile(tc.inFile)
|
||||
if err != nil {
|
||||
t.Fatalf("file %d, prototype.NewFile() error: %v", i, err)
|
||||
}
|
||||
gotErr := files.Register(fd)
|
||||
if (gotErr == nil && tc.wantErr != "") || !strings.Contains(fmt.Sprint(gotErr), tc.wantErr) {
|
||||
t.Errorf("file %d, Register() = %v, want %v", i, gotErr, tc.wantErr)
|
||||
}
|
||||
}
|
||||
|
||||
for _, tc := range tt.findDescs {
|
||||
got, err := files.FindDescriptorByName(tc.inName)
|
||||
if (got == nil) == (err == nil) {
|
||||
if tc.wantOk {
|
||||
t.Errorf("FindDescriptorByName(%v) = (%v, %v), want (non-nil, nil)", tc.inName, got, err)
|
||||
} else {
|
||||
t.Errorf("FindDescriptorByName(%v) = (%v, %v), want (nil, NotFound)", tc.inName, got, err)
|
||||
}
|
||||
}
|
||||
|
||||
gotName := pref.FullName("<nil>")
|
||||
if got != nil {
|
||||
gotName = got.FullName()
|
||||
}
|
||||
wantName := pref.FullName("<nil>")
|
||||
if tc.wantOk {
|
||||
wantName = tc.inName
|
||||
}
|
||||
if gotName != wantName {
|
||||
t.Errorf("FindDescriptorByName(%v) = %v, want %v", tc.inName, gotName, wantName)
|
||||
}
|
||||
}
|
||||
|
||||
for _, tc := range tt.rangePkgs {
|
||||
var gotFiles []file
|
||||
files.RangeFilesByPackage(tc.inPkg, func(fd pref.FileDescriptor) bool {
|
||||
gotFiles = append(gotFiles, file{fd.Path(), fd.Package()})
|
||||
return true
|
||||
})
|
||||
if diff := cmp.Diff(tc.wantFiles, gotFiles, sortFiles); diff != "" {
|
||||
t.Errorf("RangeFilesByPackage(%v) mismatch (-want +got):\n%v", tc.inPkg, diff)
|
||||
}
|
||||
}
|
||||
|
||||
for _, tc := range tt.rangePaths {
|
||||
var gotFiles []file
|
||||
files.RangeFilesByPath(tc.inPath, func(fd pref.FileDescriptor) bool {
|
||||
gotFiles = append(gotFiles, file{fd.Path(), fd.Package()})
|
||||
return true
|
||||
})
|
||||
if diff := cmp.Diff(tc.wantFiles, gotFiles, sortFiles); diff != "" {
|
||||
t.Errorf("RangeFilesByPath(%v) mismatch (-want +got):\n%v", tc.inPath, diff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -40,15 +40,16 @@ type placeholderFile struct {
|
||||
placeholderName
|
||||
}
|
||||
|
||||
func (t placeholderFile) Path() string { return t.path }
|
||||
func (t placeholderFile) Package() pref.FullName { return t.FullName() }
|
||||
func (t placeholderFile) Imports() pref.FileImports { return &emptyFiles }
|
||||
func (t placeholderFile) Messages() pref.MessageDescriptors { return &emptyMessages }
|
||||
func (t placeholderFile) Enums() pref.EnumDescriptors { return &emptyEnums }
|
||||
func (t placeholderFile) Extensions() pref.ExtensionDescriptors { return &emptyExtensions }
|
||||
func (t placeholderFile) Services() pref.ServiceDescriptors { return &emptyServices }
|
||||
func (t placeholderFile) Format(s fmt.State, r rune) { formatDesc(s, r, t) }
|
||||
func (t placeholderFile) ProtoType(pref.FileDescriptor) {}
|
||||
func (t placeholderFile) Path() string { return t.path }
|
||||
func (t placeholderFile) Package() pref.FullName { return t.FullName() }
|
||||
func (t placeholderFile) Imports() pref.FileImports { return &emptyFiles }
|
||||
func (t placeholderFile) Messages() pref.MessageDescriptors { return &emptyMessages }
|
||||
func (t placeholderFile) Enums() pref.EnumDescriptors { return &emptyEnums }
|
||||
func (t placeholderFile) Extensions() pref.ExtensionDescriptors { return &emptyExtensions }
|
||||
func (t placeholderFile) Services() pref.ServiceDescriptors { return &emptyServices }
|
||||
func (t placeholderFile) DescriptorByName(pref.FullName) pref.Descriptor { return nil }
|
||||
func (t placeholderFile) Format(s fmt.State, r rune) { formatDesc(s, r, t) }
|
||||
func (t placeholderFile) ProtoType(pref.FileDescriptor) {}
|
||||
|
||||
type placeholderMessage struct {
|
||||
placeholderName
|
||||
|
@ -46,6 +46,7 @@ type fileMeta struct {
|
||||
es enumsMeta
|
||||
xs extensionsMeta
|
||||
ss servicesMeta
|
||||
ds descriptorsMeta
|
||||
}
|
||||
type fileDesc struct{ f *File }
|
||||
|
||||
@ -70,10 +71,86 @@ func (t fileDesc) Messages() pref.MessageDescriptors { return t.
|
||||
func (t fileDesc) Enums() pref.EnumDescriptors { return t.f.es.lazyInit(t, t.f.Enums) }
|
||||
func (t fileDesc) Extensions() pref.ExtensionDescriptors { return t.f.xs.lazyInit(t, t.f.Extensions) }
|
||||
func (t fileDesc) Services() pref.ServiceDescriptors { return t.f.ss.lazyInit(t, t.f.Services) }
|
||||
func (t fileDesc) DescriptorByName(s pref.FullName) pref.Descriptor { return t.f.ds.lookup(t, s) }
|
||||
func (t fileDesc) Format(s fmt.State, r rune) { formatDesc(s, r, t) }
|
||||
func (t fileDesc) ProtoType(pref.FileDescriptor) {}
|
||||
func (t fileDesc) ProtoInternal(pragma.DoNotImplement) {}
|
||||
|
||||
// descriptorsMeta is a lazily initialized map of all descriptors declared in
|
||||
// the file by full name.
|
||||
type descriptorsMeta struct {
|
||||
once sync.Once
|
||||
m map[pref.FullName]pref.Descriptor
|
||||
}
|
||||
|
||||
func (m *descriptorsMeta) lookup(fd pref.FileDescriptor, s pref.FullName) pref.Descriptor {
|
||||
m.once.Do(func() {
|
||||
m.m = make(map[pref.FullName]pref.Descriptor)
|
||||
m.initMap(fd)
|
||||
delete(m.m, fd.Package()) // avoid registering the file descriptor itself
|
||||
})
|
||||
return m.m[s]
|
||||
}
|
||||
func (m *descriptorsMeta) initMap(d pref.Descriptor) {
|
||||
m.m[d.FullName()] = d
|
||||
if ds, ok := d.(interface {
|
||||
Enums() pref.EnumDescriptors
|
||||
}); ok {
|
||||
for i := 0; i < ds.Enums().Len(); i++ {
|
||||
m.initMap(ds.Enums().Get(i))
|
||||
}
|
||||
}
|
||||
if ds, ok := d.(interface {
|
||||
Values() pref.EnumValueDescriptors
|
||||
}); ok {
|
||||
for i := 0; i < ds.Values().Len(); i++ {
|
||||
m.initMap(ds.Values().Get(i))
|
||||
}
|
||||
}
|
||||
if ds, ok := d.(interface {
|
||||
Messages() pref.MessageDescriptors
|
||||
}); ok {
|
||||
for i := 0; i < ds.Messages().Len(); i++ {
|
||||
m.initMap(ds.Messages().Get(i))
|
||||
}
|
||||
}
|
||||
if ds, ok := d.(interface {
|
||||
Fields() pref.FieldDescriptors
|
||||
}); ok {
|
||||
for i := 0; i < ds.Fields().Len(); i++ {
|
||||
m.initMap(ds.Fields().Get(i))
|
||||
}
|
||||
}
|
||||
if ds, ok := d.(interface {
|
||||
Oneofs() pref.OneofDescriptors
|
||||
}); ok {
|
||||
for i := 0; i < ds.Oneofs().Len(); i++ {
|
||||
m.initMap(ds.Oneofs().Get(i))
|
||||
}
|
||||
}
|
||||
if ds, ok := d.(interface {
|
||||
Extensions() pref.ExtensionDescriptors
|
||||
}); ok {
|
||||
for i := 0; i < ds.Extensions().Len(); i++ {
|
||||
m.initMap(ds.Extensions().Get(i))
|
||||
}
|
||||
}
|
||||
if ds, ok := d.(interface {
|
||||
Services() pref.ServiceDescriptors
|
||||
}); ok {
|
||||
for i := 0; i < ds.Services().Len(); i++ {
|
||||
m.initMap(ds.Services().Get(i))
|
||||
}
|
||||
}
|
||||
if ds, ok := d.(interface {
|
||||
Methods() pref.MethodDescriptors
|
||||
}); ok {
|
||||
for i := 0; i < ds.Methods().Len(); i++ {
|
||||
m.initMap(ds.Methods().Get(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type messageMeta struct {
|
||||
inheritedMeta
|
||||
|
||||
|
@ -84,14 +84,14 @@ func TestFile(t *testing.T) {
|
||||
}, {
|
||||
Name: "B", // "test.B"
|
||||
Fields: []Field{{
|
||||
Name: "field_one",
|
||||
Name: "field_one", // "test.B.field_one"
|
||||
Number: 1,
|
||||
Cardinality: pref.Optional,
|
||||
Kind: pref.StringKind,
|
||||
Default: pref.ValueOf("hello"),
|
||||
OneofName: "O1",
|
||||
}, {
|
||||
Name: "field_two",
|
||||
Name: "field_two", // "test.B.field_two"
|
||||
JSONName: "Field2",
|
||||
Number: 2,
|
||||
Cardinality: pref.Optional,
|
||||
@ -100,32 +100,35 @@ func TestFile(t *testing.T) {
|
||||
EnumType: PlaceholderEnum("test.E1"),
|
||||
OneofName: "O2",
|
||||
}, {
|
||||
Name: "field_three",
|
||||
Name: "field_three", // "test.B.field_three"
|
||||
Number: 3,
|
||||
Cardinality: pref.Optional,
|
||||
Kind: pref.MessageKind,
|
||||
MessageType: PlaceholderMessage("test.C"),
|
||||
OneofName: "O2",
|
||||
}, {
|
||||
Name: "field_four",
|
||||
Name: "field_four", // "test.B.field_four"
|
||||
JSONName: "Field4",
|
||||
Number: 4,
|
||||
Cardinality: pref.Repeated,
|
||||
Kind: pref.MessageKind,
|
||||
MessageType: PlaceholderMessage("test.A"),
|
||||
}, {
|
||||
Name: "field_five",
|
||||
Name: "field_five", // "test.B.field_five"
|
||||
Number: 5,
|
||||
Cardinality: pref.Repeated,
|
||||
Kind: pref.Int32Kind,
|
||||
IsPacked: true,
|
||||
}, {
|
||||
Name: "field_six",
|
||||
Name: "field_six", // "test.B.field_six"
|
||||
Number: 6,
|
||||
Cardinality: pref.Required,
|
||||
Kind: pref.StringKind,
|
||||
}},
|
||||
Oneofs: []Oneof{{Name: "O1"}, {Name: "O2"}},
|
||||
Oneofs: []Oneof{
|
||||
{Name: "O1"}, // "test.B.O1"
|
||||
{Name: "O2"}, // "test.B.O2"
|
||||
},
|
||||
ExtensionRanges: [][2]pref.FieldNumber{{1000, 2000}},
|
||||
}, {
|
||||
Name: "C", // "test.C"
|
||||
@ -382,6 +385,33 @@ func TestFile(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
"DescriptorByName:": nil,
|
||||
"DescriptorByName:A": nil,
|
||||
"DescriptorByName:test": nil,
|
||||
"DescriptorByName:test.": nil,
|
||||
"DescriptorByName:test.A": M{"FullName": pref.FullName("test.A")},
|
||||
"DescriptorByName:test.A.key": M{"FullName": pref.FullName("test.A.key")},
|
||||
"DescriptorByName:test.A.A": nil,
|
||||
"DescriptorByName:test.A.field_one": nil,
|
||||
"DescriptorByName:test.B.field_one": M{"FullName": pref.FullName("test.B.field_one")},
|
||||
"DescriptorByName:test.B.O1": M{"FullName": pref.FullName("test.B.O1")},
|
||||
"DescriptorByName:test.B.O3": nil,
|
||||
"DescriptorByName:test.C.E1": M{"FullName": pref.FullName("test.C.E1")},
|
||||
"DescriptorByName:test.C.E1.FOO": nil,
|
||||
"DescriptorByName:test.C.FOO": M{"FullName": pref.FullName("test.C.FOO")},
|
||||
"DescriptorByName:test.C.Foo": nil,
|
||||
"DescriptorByName:test.C.BAZ": nil,
|
||||
"DescriptorByName:test.E1": M{"FullName": pref.FullName("test.E1")},
|
||||
"DescriptorByName:test.E1.FOO": nil,
|
||||
"DescriptorByName:test.FOO": M{"FullName": pref.FullName("test.FOO")},
|
||||
"DescriptorByName:test.Foo": nil,
|
||||
"DescriptorByName:test.BAZ": nil,
|
||||
"DescriptorByName:test.C.X": M{"FullName": pref.FullName("test.C.X")},
|
||||
"DescriptorByName:test.X": M{"FullName": pref.FullName("test.X")},
|
||||
"DescriptorByName:test.X.": nil,
|
||||
"DescriptorByName:test.S": M{"FullName": pref.FullName("test.S")},
|
||||
"DescriptorByName:test.S.M": M{"FullName": pref.FullName("test.S.M")},
|
||||
"DescriptorByName:test.M": nil,
|
||||
}
|
||||
|
||||
// Concurrently explore the file tree to induce races.
|
||||
|
Loading…
x
Reference in New Issue
Block a user