Damien Neil 2300c18725 reflect/protoreflect, reflect/protoregistry: move descriptor lookup to registry
Drop the protoreflect.FileDescriptor.DescriptorByName method.
Descriptor lookup will always happen through a protoregistry.Files, which
is more generally useful (it's rare that you want to find a descriptor in a
specific file, as opposed to a package which may be composed of multiple files).

Split protoregistry.Files descriptor lookup into individual per-type functions
(enum, message, extension, service), matching the preg.Types API.

Drop the ability to look up enum values, message fields, and service methods
for now. This can be easily added later if needed, and is trivial to implement
in user code. (e.g., look up the service and then consult sd.Methods.ByName().)

Change-Id: I2b3d8ef888921a8464ba1434eddab20c7d3a458e
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/172118
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
2019-04-16 06:03:04 +00:00

343 lines
11 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 protodesc provides for converting descriptorpb.FileDescriptorProto
// to/from the reflective protoreflect.FileDescriptor.
package protodesc
import (
"fmt"
"strings"
"github.com/golang/protobuf/v2/internal/encoding/defval"
"github.com/golang/protobuf/v2/internal/errors"
"github.com/golang/protobuf/v2/internal/prototype"
"github.com/golang/protobuf/v2/reflect/protoreflect"
"github.com/golang/protobuf/v2/reflect/protoregistry"
descriptorpb "github.com/golang/protobuf/v2/types/descriptor"
)
// TODO: Should we be responsible for validating other parts of the descriptor
// that we don't directly use?
//
// For example:
// * That field numbers don't overlap with reserved numbers.
// * That field names don't overlap with reserved names.
// * That enum numbers don't overlap with reserved numbers.
// * That enum names don't overlap with reserved names.
// * That "extendee" is not set for a message field.
// * That "oneof_index" is not set for an extension field.
// * That "json_name" is not set for an extension field. Maybe, maybe not.
// * That "type_name" is not set on a field for non-enums and non-messages.
// * That "weak" is not set for an extension field (double check this).
// TODO: Store the input file descriptor to implement:
// * protoreflect.Descriptor.DescriptorProto
// * protoreflect.Descriptor.DescriptorOptions
// TODO: Should we return a File instead of protoreflect.FileDescriptor?
// This would allow users to mutate the File before converting it.
// However, this will complicate future work for validation since File may now
// diverge from the stored descriptor proto (see above TODO).
// NewFile creates a new protoreflect.FileDescriptor from the provided
// file descriptor message. The file must represent a valid proto file according
// to protobuf semantics.
//
// Any import files, enum types, or message types referenced in the file are
// resolved using the provided registry. When looking up an import file path,
// the path must be unique. The newly created file descriptor is not registered
// back into the provided file registry.
//
// The caller must relinquish full ownership of the input fd and must not
// access or mutate any fields.
func NewFile(fd *descriptorpb.FileDescriptorProto, r *protoregistry.Files) (protoreflect.FileDescriptor, error) {
var f prototype.File
switch fd.GetSyntax() {
case "proto2", "":
f.Syntax = protoreflect.Proto2
case "proto3":
f.Syntax = protoreflect.Proto3
default:
return nil, errors.New("invalid syntax: %v", fd.GetSyntax())
}
f.Path = fd.GetName()
f.Package = protoreflect.FullName(fd.GetPackage())
f.Options = fd.GetOptions()
f.Imports = make([]protoreflect.FileImport, len(fd.GetDependency()))
for _, i := range fd.GetPublicDependency() {
if int(i) >= len(f.Imports) || f.Imports[i].IsPublic {
return nil, errors.New("invalid or duplicate public import index: %d", i)
}
f.Imports[i].IsPublic = true
}
for _, i := range fd.GetWeakDependency() {
if int(i) >= len(f.Imports) || f.Imports[i].IsWeak {
return nil, errors.New("invalid or duplicate weak import index: %d", i)
}
f.Imports[i].IsWeak = true
}
for i, path := range fd.GetDependency() {
var n int
imp := &f.Imports[i]
r.RangeFilesByPath(path, func(fd protoreflect.FileDescriptor) bool {
imp.FileDescriptor = fd
n++
return true
})
if n > 1 {
return nil, errors.New("duplicate files for import %q", path)
}
if imp.IsWeak || imp.FileDescriptor == nil {
imp.FileDescriptor = prototype.PlaceholderFile(path, "")
}
}
var err error
f.Messages, err = messagesFromDescriptorProto(fd.GetMessageType(), f.Syntax, r)
if err != nil {
return nil, err
}
f.Enums, err = enumsFromDescriptorProto(fd.GetEnumType(), r)
if err != nil {
return nil, err
}
f.Extensions, err = extensionsFromDescriptorProto(fd.GetExtension(), r)
if err != nil {
return nil, err
}
f.Services, err = servicesFromDescriptorProto(fd.GetService(), r)
if err != nil {
return nil, err
}
return prototype.NewFile(&f)
}
func messagesFromDescriptorProto(mds []*descriptorpb.DescriptorProto, syntax protoreflect.Syntax, r *protoregistry.Files) (ms []prototype.Message, err error) {
for _, md := range mds {
var m prototype.Message
m.Name = protoreflect.Name(md.GetName())
m.Options = md.GetOptions()
m.IsMapEntry = md.GetOptions().GetMapEntry()
for _, fd := range md.GetField() {
var f prototype.Field
f.Name = protoreflect.Name(fd.GetName())
f.Number = protoreflect.FieldNumber(fd.GetNumber())
f.Cardinality = protoreflect.Cardinality(fd.GetLabel())
f.Kind = protoreflect.Kind(fd.GetType())
opts := fd.GetOptions()
f.Options = opts
if opts != nil && opts.Packed != nil {
if *opts.Packed {
f.IsPacked = prototype.True
} else {
f.IsPacked = prototype.False
}
}
f.IsWeak = opts.GetWeak()
f.JSONName = fd.GetJsonName()
if fd.DefaultValue != nil {
f.Default, err = defval.Unmarshal(fd.GetDefaultValue(), f.Kind, defval.Descriptor)
if err != nil {
return nil, err
}
}
if fd.OneofIndex != nil {
i := int(fd.GetOneofIndex())
if i >= len(md.GetOneofDecl()) {
return nil, errors.New("invalid oneof index: %d", i)
}
f.OneofName = protoreflect.Name(md.GetOneofDecl()[i].GetName())
}
switch f.Kind {
case protoreflect.EnumKind:
f.EnumType, err = findEnumDescriptor(fd.GetTypeName(), r)
if err != nil {
return nil, err
}
if opts.GetWeak() && !f.EnumType.IsPlaceholder() {
f.EnumType = prototype.PlaceholderEnum(f.EnumType.FullName())
}
case protoreflect.MessageKind, protoreflect.GroupKind:
f.MessageType, err = findMessageDescriptor(fd.GetTypeName(), r)
if err != nil {
return nil, err
}
if opts.GetWeak() && !f.MessageType.IsPlaceholder() {
f.MessageType = prototype.PlaceholderMessage(f.MessageType.FullName())
}
}
m.Fields = append(m.Fields, f)
}
for _, od := range md.GetOneofDecl() {
m.Oneofs = append(m.Oneofs, prototype.Oneof{
Name: protoreflect.Name(od.GetName()),
Options: od.Options,
})
}
for _, s := range md.GetReservedName() {
m.ReservedNames = append(m.ReservedNames, protoreflect.Name(s))
}
for _, rr := range md.GetReservedRange() {
m.ReservedRanges = append(m.ReservedRanges, [2]protoreflect.FieldNumber{
protoreflect.FieldNumber(rr.GetStart()),
protoreflect.FieldNumber(rr.GetEnd()),
})
}
for _, xr := range md.GetExtensionRange() {
m.ExtensionRanges = append(m.ExtensionRanges, [2]protoreflect.FieldNumber{
protoreflect.FieldNumber(xr.GetStart()),
protoreflect.FieldNumber(xr.GetEnd()),
})
m.ExtensionRangeOptions = append(m.ExtensionRangeOptions, xr.GetOptions())
}
m.Messages, err = messagesFromDescriptorProto(md.GetNestedType(), syntax, r)
if err != nil {
return nil, err
}
m.Enums, err = enumsFromDescriptorProto(md.GetEnumType(), r)
if err != nil {
return nil, err
}
m.Extensions, err = extensionsFromDescriptorProto(md.GetExtension(), r)
if err != nil {
return nil, err
}
ms = append(ms, m)
}
return ms, nil
}
func enumsFromDescriptorProto(eds []*descriptorpb.EnumDescriptorProto, r *protoregistry.Files) (es []prototype.Enum, err error) {
for _, ed := range eds {
var e prototype.Enum
e.Name = protoreflect.Name(ed.GetName())
e.Options = ed.GetOptions()
for _, vd := range ed.GetValue() {
e.Values = append(e.Values, prototype.EnumValue{
Name: protoreflect.Name(vd.GetName()),
Number: protoreflect.EnumNumber(vd.GetNumber()),
Options: vd.Options,
})
}
for _, s := range ed.GetReservedName() {
e.ReservedNames = append(e.ReservedNames, protoreflect.Name(s))
}
for _, rr := range ed.GetReservedRange() {
e.ReservedRanges = append(e.ReservedRanges, [2]protoreflect.EnumNumber{
protoreflect.EnumNumber(rr.GetStart()),
protoreflect.EnumNumber(rr.GetEnd()),
})
}
es = append(es, e)
}
return es, nil
}
func extensionsFromDescriptorProto(xds []*descriptorpb.FieldDescriptorProto, r *protoregistry.Files) (xs []prototype.Extension, err error) {
for _, xd := range xds {
var x prototype.Extension
x.Name = protoreflect.Name(xd.GetName())
x.Number = protoreflect.FieldNumber(xd.GetNumber())
x.Cardinality = protoreflect.Cardinality(xd.GetLabel())
x.Kind = protoreflect.Kind(xd.GetType())
x.Options = xd.GetOptions()
if xd.DefaultValue != nil {
x.Default, err = defval.Unmarshal(xd.GetDefaultValue(), x.Kind, defval.Descriptor)
if err != nil {
return nil, err
}
}
switch x.Kind {
case protoreflect.EnumKind:
x.EnumType, err = findEnumDescriptor(xd.GetTypeName(), r)
if err != nil {
return nil, err
}
case protoreflect.MessageKind, protoreflect.GroupKind:
x.MessageType, err = findMessageDescriptor(xd.GetTypeName(), r)
if err != nil {
return nil, err
}
}
x.ExtendedType, err = findMessageDescriptor(xd.GetExtendee(), r)
if err != nil {
return nil, err
}
xs = append(xs, x)
}
return xs, nil
}
func servicesFromDescriptorProto(sds []*descriptorpb.ServiceDescriptorProto, r *protoregistry.Files) (ss []prototype.Service, err error) {
for _, sd := range sds {
var s prototype.Service
s.Name = protoreflect.Name(sd.GetName())
s.Options = sd.GetOptions()
for _, md := range sd.GetMethod() {
var m prototype.Method
m.Name = protoreflect.Name(md.GetName())
m.Options = md.GetOptions()
m.InputType, err = findMessageDescriptor(md.GetInputType(), r)
if err != nil {
return nil, err
}
m.OutputType, err = findMessageDescriptor(md.GetOutputType(), r)
if err != nil {
return nil, err
}
m.IsStreamingClient = md.GetClientStreaming()
m.IsStreamingServer = md.GetServerStreaming()
s.Methods = append(s.Methods, m)
}
ss = append(ss, s)
}
return ss, nil
}
// TODO: Should we allow relative names? The protoc compiler has emitted
// absolute names for some time now. Requiring absolute names as an input
// simplifies our implementation as we won't need to implement C++'s namespace
// scoping rules.
func findMessageDescriptor(s string, r *protoregistry.Files) (protoreflect.MessageDescriptor, error) {
if !strings.HasPrefix(s, ".") {
return nil, errors.New("identifier name must be fully qualified with a leading dot: %v", s)
}
name := protoreflect.FullName(strings.TrimPrefix(s, "."))
md, err := r.FindMessageByName(name)
if err != nil {
return prototype.PlaceholderMessage(name), nil
}
return md, nil
}
func findEnumDescriptor(s string, r *protoregistry.Files) (protoreflect.EnumDescriptor, error) {
if !strings.HasPrefix(s, ".") {
return nil, errors.New("identifier name must be fully qualified with a leading dot: %v", s)
}
name := protoreflect.FullName(strings.TrimPrefix(s, "."))
ed, err := r.FindEnumByName(name)
if err != nil {
return prototype.PlaceholderEnum(name), nil
}
return ed, nil
}
func typeName(t protoreflect.Descriptor) string {
switch t.(type) {
case protoreflect.EnumType:
return "enum"
case protoreflect.MessageType:
return "message"
default:
return fmt.Sprintf("%T", t)
}
}