mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-01-01 11:58:21 +00:00
01ab29648e
This change was created by running: git ls-files | xargs sed -i "s|google.golang.org/proto|github.com/golang/protobuf/v2|g" This change is *not* an endorsement of "github.com/golang/protobuf/v2" as the final import path when the v2 API is eventually released as stable. We continue to reserve the right to make breaking changes as we see fit. This change enables us to host the v2 API on a repository that is go-gettable (since go.googlesource.com is not a known host by the "go get" tool; and google.golang.org/proto was just a stub URL that is not currently served). Thus, we can start work on a forked version of the v1 API that explores what it would take to implement v1 in terms of v2 in a backwards compatible way. Change-Id: Ia3ebc41ac4238af62ee140200d3158b53ac9ec48 Reviewed-on: https://go-review.googlesource.com/136736 Reviewed-by: Damien Neil <dneil@google.com>
301 lines
10 KiB
Go
301 lines
10 KiB
Go
// 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"
|
|
|
|
"github.com/golang/protobuf/v2/internal/errors"
|
|
"github.com/golang/protobuf/v2/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) {
|
|
if r == nil {
|
|
return nil, NotFound
|
|
}
|
|
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 r == nil {
|
|
return
|
|
}
|
|
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) {
|
|
if r == nil {
|
|
return
|
|
}
|
|
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())
|
|
}
|
|
}
|