reflect/protodesc: add FileOptions

The FileOptions type provides the ability to specify specialized options
for how a file descriptor is constructed. It follows the same optional
arguments pattern as used in the proto package.

The resolver is not an option since it almost always necessary
when constructing a file descriptor.

Change-Id: Ib98ac6289881ad8402dd615f6c895da5899cb8d9
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/218940
Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
Joe Tsai 2020-02-10 14:04:25 -08:00 committed by Joe Tsai
parent d2ece139c6
commit 25fc6fb4f2
3 changed files with 45 additions and 51 deletions

View File

@ -66,7 +66,7 @@ func TestNoGoPackage(t *testing.T) {
Name: proto.String("testdata/go_package/no_go_package_import.proto"), Name: proto.String("testdata/go_package/no_go_package_import.proto"),
Syntax: proto.String(protoreflect.Proto3.String()), Syntax: proto.String(protoreflect.Proto3.String()),
Package: proto.String("goproto.testdata"), Package: proto.String("goproto.testdata"),
Dependency: []string{"go_package/no_go_package.proto"}, Dependency: []string{"testdata/go_package/no_go_package.proto"},
}, },
}, },
}, nil) }, nil)

View File

@ -28,37 +28,40 @@ type Resolver interface {
FindDescriptorByName(protoreflect.FullName) (protoreflect.Descriptor, error) FindDescriptorByName(protoreflect.FullName) (protoreflect.Descriptor, error)
} }
// option is an optional argument that may be provided to NewFile. // FileOptions configures the construction of file descriptors.
type option struct { type FileOptions struct {
pragma.DoNotCompare pragma.NoUnkeyedLiterals
allowUnresolvable bool
}
// allowUnresolvable configures NewFile to permissively allow unresolvable // AllowUnresolvable configures New to permissively allow unresolvable
// file, enum, or message dependencies. Unresolved dependencies are replaced by // file, enum, or message dependencies. Unresolved dependencies are replaced
// placeholder equivalents. // by placeholder equivalents.
// //
// The following dependencies may be left unresolved: // The following dependencies may be left unresolved:
// • Resolving an imported file. // • Resolving an imported file.
// • Resolving the type for a message field or extension field. // • Resolving the type for a message field or extension field.
// If the kind of the field is unknown, then a placeholder is used for both // If the kind of the field is unknown, then a placeholder is used for both
// protoreflect.FieldDescriptor.Enum and protoreflect.FieldDescriptor.Message. // the Enum and Message accessors on the protoreflect.FieldDescriptor.
// • Resolving the enum value set as the default for an optional enum field. // • Resolving an enum value set as the default for an optional enum field.
// If unresolvable, the protoreflect.FieldDescriptor.Default is set to the // If unresolvable, the protoreflect.FieldDescriptor.Default is set to the
// first enum value in the associated enum (or zero if the also enum dependency // first value in the associated enum (or zero if the also enum dependency
// is also unresolvable). The protoreflect.FieldDescriptor.DefaultEnumValue // is also unresolvable). The protoreflect.FieldDescriptor.DefaultEnumValue
// is set as a placeholder. // is populated with a placeholder.
// • Resolving the extended message type for an extension field. // • Resolving the extended message type for an extension field.
// • Resolving the input or output message type for a service method. // • Resolving the input or output message type for a service method.
// //
// If the unresolved dependency uses a relative name, then the placeholder will // If the unresolved dependency uses a relative name,
// contain an invalid FullName with a "*." prefix, indicating that the starting // then the placeholder will contain an invalid FullName with a "*." prefix,
// prefix of the full name is unknown. // indicating that the starting prefix of the full name is unknown.
func allowUnresolvable() option { AllowUnresolvable bool
return option{allowUnresolvable: true}
} }
// NewFile creates a new protoreflect.FileDescriptor from the provided // NewFile creates a new protoreflect.FileDescriptor from the provided
// file descriptor message. See FileOptions.New for more information.
func NewFile(fd *descriptorpb.FileDescriptorProto, r Resolver) (protoreflect.FileDescriptor, error) {
return FileOptions{}.New(fd, r)
}
// New creates a new protoreflect.FileDescriptor from the provided
// file descriptor message. The file must represent a valid proto file according // file descriptor message. The file must represent a valid proto file according
// to protobuf semantics. The returned descriptor is a deep copy of the input. // to protobuf semantics. The returned descriptor is a deep copy of the input.
// //
@ -66,16 +69,7 @@ func allowUnresolvable() option {
// resolved using the provided registry. When looking up an import file path, // 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 // the path must be unique. The newly created file descriptor is not registered
// back into the provided file registry. // back into the provided file registry.
func NewFile(fd *descriptorpb.FileDescriptorProto, r Resolver) (protoreflect.FileDescriptor, error) { func (o FileOptions) New(fd *descriptorpb.FileDescriptorProto, r Resolver) (protoreflect.FileDescriptor, error) {
// TODO(blocks): remove setting allowUnresolvable once naughty users are migrated.
return newFile(fd, r, allowUnresolvable())
}
func newFile(fd *descriptorpb.FileDescriptorProto, r Resolver, opts ...option) (protoreflect.FileDescriptor, error) {
// Process the options.
var allowUnresolvable bool
for _, o := range opts {
allowUnresolvable = allowUnresolvable || o.allowUnresolvable
}
if r == nil { if r == nil {
r = (*protoregistry.Files)(nil) // empty resolver r = (*protoregistry.Files)(nil) // empty resolver
} }
@ -120,7 +114,7 @@ func newFile(fd *descriptorpb.FileDescriptorProto, r Resolver, opts ...option) (
for i, path := range fd.GetDependency() { for i, path := range fd.GetDependency() {
imp := &f.L2.Imports[i] imp := &f.L2.Imports[i]
f, err := r.FindFileByPath(path) f, err := r.FindFileByPath(path)
if err == protoregistry.NotFound && (allowUnresolvable || imp.IsWeak) { if err == protoregistry.NotFound && (o.AllowUnresolvable || imp.IsWeak) {
f = filedesc.PlaceholderFile(path) f = filedesc.PlaceholderFile(path)
} else if err != nil { } else if err != nil {
return nil, errors.New("could not resolve import %q: %v", path, err) return nil, errors.New("could not resolve import %q: %v", path, err)
@ -188,7 +182,7 @@ func newFile(fd *descriptorpb.FileDescriptorProto, r Resolver, opts ...option) (
} }
// Step 2: Resolve every dependency reference not handled by step 1. // Step 2: Resolve every dependency reference not handled by step 1.
r2 := &resolver{local: r1, remote: r, imports: imps, allowUnresolvable: allowUnresolvable} r2 := &resolver{local: r1, remote: r, imports: imps, allowUnresolvable: o.AllowUnresolvable}
if err := r2.resolveMessageDependencies(f.L1.Messages.List, fd.GetMessageType()); err != nil { if err := r2.resolveMessageDependencies(f.L1.Messages.List, fd.GetMessageType()); err != nil {
return nil, err return nil, err
} }

View File

@ -88,7 +88,7 @@ func TestNewFile(t *testing.T) {
label string label string
inDeps []*descriptorpb.FileDescriptorProto inDeps []*descriptorpb.FileDescriptorProto
inDesc *descriptorpb.FileDescriptorProto inDesc *descriptorpb.FileDescriptorProto
inOpts []option inOpts FileOptions
wantDesc *descriptorpb.FileDescriptorProto wantDesc *descriptorpb.FileDescriptorProto
wantErr string wantErr string
}{{ }{{
@ -121,7 +121,7 @@ func TestNewFile(t *testing.T) {
package: "" package: ""
dependency: "dep.proto" dependency: "dep.proto"
`), `),
inOpts: []option{allowUnresolvable()}, inOpts: FileOptions{AllowUnresolvable: true},
}, { }, {
label: "duplicate import", label: "duplicate import",
inDesc: mustParseFile(` inDesc: mustParseFile(`
@ -129,7 +129,7 @@ func TestNewFile(t *testing.T) {
package: "" package: ""
dependency: ["dep.proto", "dep.proto"] dependency: ["dep.proto", "dep.proto"]
`), `),
inOpts: []option{allowUnresolvable()}, inOpts: FileOptions{AllowUnresolvable: true},
wantErr: `already imported "dep.proto"`, wantErr: `already imported "dep.proto"`,
}, { }, {
label: "invalid weak import", label: "invalid weak import",
@ -139,7 +139,7 @@ func TestNewFile(t *testing.T) {
dependency: "dep.proto" dependency: "dep.proto"
weak_dependency: [-23] weak_dependency: [-23]
`), `),
inOpts: []option{allowUnresolvable()}, inOpts: FileOptions{AllowUnresolvable: true},
wantErr: `invalid or duplicate weak import index: -23`, wantErr: `invalid or duplicate weak import index: -23`,
}, { }, {
label: "normal weak and public import", label: "normal weak and public import",
@ -150,7 +150,7 @@ func TestNewFile(t *testing.T) {
weak_dependency: [0] weak_dependency: [0]
public_dependency: [0] public_dependency: [0]
`), `),
inOpts: []option{allowUnresolvable()}, inOpts: FileOptions{AllowUnresolvable: true},
}, { }, {
label: "import public indirect dependency duplicate", label: "import public indirect dependency duplicate",
inDeps: []*descriptorpb.FileDescriptorProto{ inDeps: []*descriptorpb.FileDescriptorProto{
@ -306,7 +306,7 @@ func TestNewFile(t *testing.T) {
enum_type: [{name:"E" value:[{name:"V0" number:0}]}] enum_type: [{name:"E" value:[{name:"V0" number:0}]}]
}] }]
`), `),
inOpts: []option{allowUnresolvable()}, inOpts: FileOptions{AllowUnresolvable: true},
}, { }, {
label: "unresolved extendee", label: "unresolved extendee",
inDesc: mustParseFile(` inDesc: mustParseFile(`
@ -342,7 +342,7 @@ func TestNewFile(t *testing.T) {
method: [{name:"M" input_type:"foo.bar.input" output_type:".absolute.foo.bar.output"}] method: [{name:"M" input_type:"foo.bar.input" output_type:".absolute.foo.bar.output"}]
}] }]
`), `),
inOpts: []option{allowUnresolvable()}, inOpts: FileOptions{AllowUnresolvable: true},
}, { }, {
label: "resolved but not imported", label: "resolved but not imported",
inDeps: []*descriptorpb.FileDescriptorProto{mustParseFile(` inDeps: []*descriptorpb.FileDescriptorProto{mustParseFile(`
@ -856,7 +856,7 @@ func TestNewFile(t *testing.T) {
] ]
}] }]
`), `),
inOpts: []option{allowUnresolvable()}, inOpts: FileOptions{AllowUnresolvable: true},
// TODO: Test field and oneof handling in validateMessageDeclarations // TODO: Test field and oneof handling in validateMessageDeclarations
// TODO: Test unmarshalDefault // TODO: Test unmarshalDefault
// TODO: Test validateExtensionDeclarations // TODO: Test validateExtensionDeclarations
@ -883,7 +883,7 @@ func TestNewFile(t *testing.T) {
}] }]
}] }]
`), `),
inOpts: []option{allowUnresolvable()}, inOpts: FileOptions{AllowUnresolvable: true},
}, { }, {
label: "service with wrong reference type", label: "service with wrong reference type",
inDeps: []*descriptorpb.FileDescriptorProto{ inDeps: []*descriptorpb.FileDescriptorProto{
@ -910,7 +910,7 @@ func TestNewFile(t *testing.T) {
t.Run(tt.label, func(t *testing.T) { t.Run(tt.label, func(t *testing.T) {
r := new(protoregistry.Files) r := new(protoregistry.Files)
for i, dep := range tt.inDeps { for i, dep := range tt.inDeps {
f, err := newFile(dep, r) f, err := tt.inOpts.New(dep, r)
if err != nil { if err != nil {
t.Fatalf("dependency %d: unexpected NewFile() error: %v", i, err) t.Fatalf("dependency %d: unexpected NewFile() error: %v", i, err)
} }
@ -922,7 +922,7 @@ func TestNewFile(t *testing.T) {
if tt.wantErr == "" && tt.wantDesc == nil { if tt.wantErr == "" && tt.wantDesc == nil {
tt.wantDesc = cloneFile(tt.inDesc) tt.wantDesc = cloneFile(tt.inDesc)
} }
gotFile, err := newFile(tt.inDesc, r, tt.inOpts...) gotFile, err := tt.inOpts.New(tt.inDesc, r)
if gotFile != nil { if gotFile != nil {
gotDesc = ToFileDescriptorProto(gotFile) gotDesc = ToFileDescriptorProto(gotFile)
} }