package protodesc
import (
"google.golang.org/protobuf/internal/errors"
"google.golang.org/protobuf/internal/filedesc"
"google.golang.org/protobuf/internal/pragma"
"google.golang.org/protobuf/internal/strs"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/descriptorpb"
)
type Resolver interface {
FindFileByPath (string ) (protoreflect .FileDescriptor , error )
FindDescriptorByName (protoreflect .FullName ) (protoreflect .Descriptor , error )
}
type FileOptions struct {
pragma .NoUnkeyedLiterals
AllowUnresolvable bool
}
func NewFile (fd *descriptorpb .FileDescriptorProto , r Resolver ) (protoreflect .FileDescriptor , error ) {
return FileOptions {}.New (fd , r )
}
func NewFiles (fd *descriptorpb .FileDescriptorSet ) (*protoregistry .Files , error ) {
return FileOptions {}.NewFiles (fd )
}
func (o FileOptions ) New (fd *descriptorpb .FileDescriptorProto , r Resolver ) (protoreflect .FileDescriptor , error ) {
if r == nil {
r = (*protoregistry .Files )(nil )
}
f := &filedesc .File {L2 : &filedesc .FileL2 {}}
switch fd .GetSyntax () {
case "proto2" , "" :
f .L1 .Syntax = protoreflect .Proto2
case "proto3" :
f .L1 .Syntax = protoreflect .Proto3
default :
return nil , errors .New ("invalid syntax: %q" , fd .GetSyntax ())
}
f .L1 .Path = fd .GetName ()
if f .L1 .Path == "" {
return nil , errors .New ("file path must be populated" )
}
f .L1 .Package = protoreflect .FullName (fd .GetPackage ())
if !f .L1 .Package .IsValid () && f .L1 .Package != "" {
return nil , errors .New ("invalid package: %q" , f .L1 .Package )
}
if opts := fd .GetOptions (); opts != nil {
opts = proto .Clone (opts ).(*descriptorpb .FileOptions )
f .L2 .Options = func () protoreflect .ProtoMessage { return opts }
}
f .L2 .Imports = make (filedesc .FileImports , len (fd .GetDependency ()))
for _ , i := range fd .GetPublicDependency () {
if !(0 <= i && int (i ) < len (f .L2 .Imports )) || f .L2 .Imports [i ].IsPublic {
return nil , errors .New ("invalid or duplicate public import index: %d" , i )
}
f .L2 .Imports [i ].IsPublic = true
}
for _ , i := range fd .GetWeakDependency () {
if !(0 <= i && int (i ) < len (f .L2 .Imports )) || f .L2 .Imports [i ].IsWeak {
return nil , errors .New ("invalid or duplicate weak import index: %d" , i )
}
f .L2 .Imports [i ].IsWeak = true
}
imps := importSet {f .Path (): true }
for i , path := range fd .GetDependency () {
imp := &f .L2 .Imports [i ]
f , err := r .FindFileByPath (path )
if err == protoregistry .NotFound && (o .AllowUnresolvable || imp .IsWeak ) {
f = filedesc .PlaceholderFile (path )
} else if err != nil {
return nil , errors .New ("could not resolve import %q: %v" , path , err )
}
imp .FileDescriptor = f
if imps [imp .Path ()] {
return nil , errors .New ("already imported %q" , path )
}
imps [imp .Path ()] = true
}
for i := range fd .GetDependency () {
imp := &f .L2 .Imports [i ]
imps .importPublic (imp .Imports ())
}
f .L2 .Locations .File = f
for _ , loc := range fd .GetSourceCodeInfo ().GetLocation () {
var l protoreflect .SourceLocation
l .Path = protoreflect .SourcePath (loc .GetPath ())
s := loc .GetSpan ()
switch len (s ) {
case 3 :
l .StartLine , l .StartColumn , l .EndLine , l .EndColumn = int (s [0 ]), int (s [1 ]), int (s [0 ]), int (s [2 ])
case 4 :
l .StartLine , l .StartColumn , l .EndLine , l .EndColumn = int (s [0 ]), int (s [1 ]), int (s [2 ]), int (s [3 ])
default :
return nil , errors .New ("invalid span: %v" , s )
}
if false && (l .EndLine < l .StartLine || l .StartLine < 0 || l .StartColumn < 0 || l .EndColumn < 0 ||
(l .StartLine == l .EndLine && l .EndColumn <= l .StartColumn )) {
return nil , errors .New ("invalid span: %v" , s )
}
l .LeadingDetachedComments = loc .GetLeadingDetachedComments ()
l .LeadingComments = loc .GetLeadingComments ()
l .TrailingComments = loc .GetTrailingComments ()
f .L2 .Locations .List = append (f .L2 .Locations .List , l )
}
var err error
sb := new (strs .Builder )
r1 := make (descsByName )
if f .L1 .Enums .List , err = r1 .initEnumDeclarations (fd .GetEnumType (), f , sb ); err != nil {
return nil , err
}
if f .L1 .Messages .List , err = r1 .initMessagesDeclarations (fd .GetMessageType (), f , sb ); err != nil {
return nil , err
}
if f .L1 .Extensions .List , err = r1 .initExtensionDeclarations (fd .GetExtension (), f , sb ); err != nil {
return nil , err
}
if f .L1 .Services .List , err = r1 .initServiceDeclarations (fd .GetService (), f , sb ); err != nil {
return nil , err
}
r2 := &resolver {local : r1 , remote : r , imports : imps , allowUnresolvable : o .AllowUnresolvable }
if err := r2 .resolveMessageDependencies (f .L1 .Messages .List , fd .GetMessageType ()); err != nil {
return nil , err
}
if err := r2 .resolveExtensionDependencies (f .L1 .Extensions .List , fd .GetExtension ()); err != nil {
return nil , err
}
if err := r2 .resolveServiceDependencies (f .L1 .Services .List , fd .GetService ()); err != nil {
return nil , err
}
if err := validateEnumDeclarations (f .L1 .Enums .List , fd .GetEnumType ()); err != nil {
return nil , err
}
if err := validateMessageDeclarations (f .L1 .Messages .List , fd .GetMessageType ()); err != nil {
return nil , err
}
if err := validateExtensionDeclarations (f .L1 .Extensions .List , fd .GetExtension ()); err != nil {
return nil , err
}
return f , nil
}
type importSet map [string ]bool
func (is importSet ) importPublic (imps protoreflect .FileImports ) {
for i := 0 ; i < imps .Len (); i ++ {
if imp := imps .Get (i ); imp .IsPublic {
is [imp .Path ()] = true
is .importPublic (imp .Imports ())
}
}
}
func (o FileOptions ) NewFiles (fds *descriptorpb .FileDescriptorSet ) (*protoregistry .Files , error ) {
files := make (map [string ]*descriptorpb .FileDescriptorProto )
for _ , fd := range fds .File {
if _ , ok := files [fd .GetName ()]; ok {
return nil , errors .New ("file appears multiple times: %q" , fd .GetName ())
}
files [fd .GetName ()] = fd
}
r := &protoregistry .Files {}
for _ , fd := range files {
if err := o .addFileDeps (r , fd , files ); err != nil {
return nil , err
}
}
return r , nil
}
func (o FileOptions ) addFileDeps (r *protoregistry .Files , fd *descriptorpb .FileDescriptorProto , files map [string ]*descriptorpb .FileDescriptorProto ) error {
files [fd .GetName ()] = nil
for _ , dep := range fd .Dependency {
depfd , ok := files [dep ]
if depfd == nil {
if ok {
return errors .New ("import cycle in file: %q" , dep )
}
continue
}
if err := o .addFileDeps (r , depfd , files ); err != nil {
return err
}
}
delete (files , fd .GetName ())
f , err := o .New (fd , r )
if err != nil {
return err
}
return r .RegisterFile (f )
}