// 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 functionality for converting // FileDescriptorProto messages to/from protoreflect.FileDescriptor values. // // The google.protobuf.FileDescriptorProto is a protobuf message that describes // the type information for a .proto file in a form that is easily serializable. // The protoreflect.FileDescriptor is a more structured representation of // the FileDescriptorProto message where references and remote dependencies // can be directly followed.
package protodesc import ( ) // Resolver is the resolver used by NewFile to resolve dependencies. // The enums and messages provided must belong to some parent file, // which is also registered. // // It is implemented by protoregistry.Files. type Resolver interface { FindFileByPath(string) (protoreflect.FileDescriptor, error) FindDescriptorByName(protoreflect.FullName) (protoreflect.Descriptor, error) } // FileOptions configures the construction of file descriptors. type FileOptions struct { pragma.NoUnkeyedLiterals // AllowUnresolvable configures New to permissively allow unresolvable // file, enum, or message dependencies. Unresolved dependencies are replaced // by placeholder equivalents. // // The following dependencies may be left unresolved: // • Resolving an imported file. // • 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 // the Enum and Message accessors on the protoreflect.FieldDescriptor. // • Resolving an enum value set as the default for an optional enum field. // If unresolvable, the protoreflect.FieldDescriptor.Default is set to the // first value in the associated enum (or zero if the also enum dependency // is also unresolvable). The protoreflect.FieldDescriptor.DefaultEnumValue // is populated with a placeholder. // • Resolving the extended message type for an extension field. // • Resolving the input or output message type for a service method. // // If the unresolved dependency uses a relative name, // then the placeholder will contain an invalid FullName with a "*." prefix, // indicating that the starting prefix of the full name is unknown. AllowUnresolvable bool } // NewFile creates a new protoreflect.FileDescriptor from the provided // file descriptor message. See FileOptions.New for more information. func ( *descriptorpb.FileDescriptorProto, Resolver) (protoreflect.FileDescriptor, error) { return FileOptions{}.New(, ) } // NewFiles creates a new protoregistry.Files from the provided // FileDescriptorSet message. See FileOptions.NewFiles for more information. func ( *descriptorpb.FileDescriptorSet) (*protoregistry.Files, error) { return FileOptions{}.NewFiles() } // New creates a new protoreflect.FileDescriptor from the provided // 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. // // Any imported 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. func ( FileOptions) ( *descriptorpb.FileDescriptorProto, Resolver) (protoreflect.FileDescriptor, error) { if == nil { = (*protoregistry.Files)(nil) // empty resolver } // Handle the file descriptor content. := &filedesc.File{L2: &filedesc.FileL2{}} switch .GetSyntax() { case "proto2", "": .L1.Syntax = protoreflect.Proto2 case "proto3": .L1.Syntax = protoreflect.Proto3 default: return nil, errors.New("invalid syntax: %q", .GetSyntax()) } .L1.Path = .GetName() if .L1.Path == "" { return nil, errors.New("file path must be populated") } .L1.Package = protoreflect.FullName(.GetPackage()) if !.L1.Package.IsValid() && .L1.Package != "" { return nil, errors.New("invalid package: %q", .L1.Package) } if := .GetOptions(); != nil { = proto.Clone().(*descriptorpb.FileOptions) .L2.Options = func() protoreflect.ProtoMessage { return } } .L2.Imports = make(filedesc.FileImports, len(.GetDependency())) for , := range .GetPublicDependency() { if !(0 <= && int() < len(.L2.Imports)) || .L2.Imports[].IsPublic { return nil, errors.New("invalid or duplicate public import index: %d", ) } .L2.Imports[].IsPublic = true } for , := range .GetWeakDependency() { if !(0 <= && int() < len(.L2.Imports)) || .L2.Imports[].IsWeak { return nil, errors.New("invalid or duplicate weak import index: %d", ) } .L2.Imports[].IsWeak = true } := importSet{.Path(): true} for , := range .GetDependency() { := &.L2.Imports[] , := .FindFileByPath() if == protoregistry.NotFound && (.AllowUnresolvable || .IsWeak) { = filedesc.PlaceholderFile() } else if != nil { return nil, errors.New("could not resolve import %q: %v", , ) } .FileDescriptor = if [.Path()] { return nil, errors.New("already imported %q", ) } [.Path()] = true } for := range .GetDependency() { := &.L2.Imports[] .importPublic(.Imports()) } // Handle source locations. .L2.Locations.File = for , := range .GetSourceCodeInfo().GetLocation() { var protoreflect.SourceLocation // TODO: Validate that the path points to an actual declaration? .Path = protoreflect.SourcePath(.GetPath()) := .GetSpan() switch len() { case 3: .StartLine, .StartColumn, .EndLine, .EndColumn = int([0]), int([1]), int([0]), int([2]) case 4: .StartLine, .StartColumn, .EndLine, .EndColumn = int([0]), int([1]), int([2]), int([3]) default: return nil, errors.New("invalid span: %v", ) } // TODO: Validate that the span information is sensible? // See https://github.com/protocolbuffers/protobuf/issues/6378. if false && (.EndLine < .StartLine || .StartLine < 0 || .StartColumn < 0 || .EndColumn < 0 || (.StartLine == .EndLine && .EndColumn <= .StartColumn)) { return nil, errors.New("invalid span: %v", ) } .LeadingDetachedComments = .GetLeadingDetachedComments() .LeadingComments = .GetLeadingComments() .TrailingComments = .GetTrailingComments() .L2.Locations.List = append(.L2.Locations.List, ) } // Step 1: Allocate and derive the names for all declarations. // This copies all fields from the descriptor proto except: // google.protobuf.FieldDescriptorProto.type_name // google.protobuf.FieldDescriptorProto.default_value // google.protobuf.FieldDescriptorProto.oneof_index // google.protobuf.FieldDescriptorProto.extendee // google.protobuf.MethodDescriptorProto.input // google.protobuf.MethodDescriptorProto.output var error := new(strs.Builder) := make(descsByName) if .L1.Enums.List, = .initEnumDeclarations(.GetEnumType(), , ); != nil { return nil, } if .L1.Messages.List, = .initMessagesDeclarations(.GetMessageType(), , ); != nil { return nil, } if .L1.Extensions.List, = .initExtensionDeclarations(.GetExtension(), , ); != nil { return nil, } if .L1.Services.List, = .initServiceDeclarations(.GetService(), , ); != nil { return nil, } // Step 2: Resolve every dependency reference not handled by step 1. := &resolver{local: , remote: , imports: , allowUnresolvable: .AllowUnresolvable} if := .resolveMessageDependencies(.L1.Messages.List, .GetMessageType()); != nil { return nil, } if := .resolveExtensionDependencies(.L1.Extensions.List, .GetExtension()); != nil { return nil, } if := .resolveServiceDependencies(.L1.Services.List, .GetService()); != nil { return nil, } // Step 3: Validate every enum, message, and extension declaration. if := validateEnumDeclarations(.L1.Enums.List, .GetEnumType()); != nil { return nil, } if := validateMessageDeclarations(.L1.Messages.List, .GetMessageType()); != nil { return nil, } if := validateExtensionDeclarations(.L1.Extensions.List, .GetExtension()); != nil { return nil, } return , nil } type importSet map[string]bool func ( importSet) ( protoreflect.FileImports) { for := 0; < .Len(); ++ { if := .Get(); .IsPublic { [.Path()] = true .(.Imports()) } } } // NewFiles creates a new protoregistry.Files from the provided // FileDescriptorSet message. The descriptor set must include only // valid files according to protobuf semantics. The returned descriptors // are a deep copy of the input. func ( FileOptions) ( *descriptorpb.FileDescriptorSet) (*protoregistry.Files, error) { := make(map[string]*descriptorpb.FileDescriptorProto) for , := range .File { if , := [.GetName()]; { return nil, errors.New("file appears multiple times: %q", .GetName()) } [.GetName()] = } := &protoregistry.Files{} for , := range { if := .addFileDeps(, , ); != nil { return nil, } } return , nil } func ( FileOptions) ( *protoregistry.Files, *descriptorpb.FileDescriptorProto, map[string]*descriptorpb.FileDescriptorProto) error { // Set the entry to nil while descending into a file's dependencies to detect cycles. [.GetName()] = nil for , := range .Dependency { , := [] if == nil { if { return errors.New("import cycle in file: %q", ) } continue } if := .(, , ); != nil { return } } // Delete the entry once dependencies are processed. delete(, .GetName()) , := .New(, ) if != nil { return } return .RegisterFile() }