You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
289 lines
8.4 KiB
289 lines
8.4 KiB
package desc_source |
|
|
|
import ( |
|
"context" |
|
"errors" |
|
"fmt" |
|
"os" |
|
"sync" |
|
|
|
"github.com/golang/protobuf/proto" |
|
descpb "github.com/golang/protobuf/protoc-gen-go/descriptor" |
|
"github.com/jhump/protoreflect/desc" |
|
"github.com/jhump/protoreflect/desc/protoparse" |
|
"github.com/jhump/protoreflect/dynamic" |
|
"github.com/jhump/protoreflect/grpcreflect" |
|
"google.golang.org/grpc/codes" |
|
"google.golang.org/grpc/status" |
|
) |
|
|
|
// ErrReflectionNotSupported is returned by DescriptorSource operations that |
|
// rely on interacting with the reflection service when the source does not |
|
// actually expose the reflection service. When this occurs, an alternate source |
|
// (like file descriptor sets) must be used. |
|
var ErrReflectionNotSupported = errors.New("server does not support the reflection API") |
|
|
|
// DescriptorSource is a source of protobuf descriptor information. It can be backed by a FileDescriptorSet |
|
// proto (like a file generated by protoc) or a remote server that supports the reflection API. |
|
type DescriptorSource interface { |
|
// ListServices returns a list of fully-qualified service names. It will be all services in a set of |
|
// descriptor files or the set of all services exposed by a gRPC server. |
|
ListServices() ([]string, error) |
|
// FindSymbol returns a descriptor for the given fully-qualified symbol name. |
|
FindSymbol(fullyQualifiedName string) (desc.Descriptor, error) |
|
// AllExtensionsForType returns all known extension fields that extend the given message type name. |
|
AllExtensionsForType(typeName string) ([]*desc.FieldDescriptor, error) |
|
} |
|
|
|
// DescriptorSourceFromProtoSets creates a DescriptorSource that is backed by the named files, whose contents |
|
// are encoded FileDescriptorSet protos. |
|
func DescriptorSourceFromProtoSets(fileNames ...string) (DescriptorSource, error) { |
|
files := &descpb.FileDescriptorSet{} |
|
for _, fileName := range fileNames { |
|
b, err := os.ReadFile(fileName) |
|
if err != nil { |
|
return nil, fmt.Errorf("could not load protoset file %q: %v", fileName, err) |
|
} |
|
|
|
var fs descpb.FileDescriptorSet |
|
err = proto.Unmarshal(b, &fs) |
|
if err != nil { |
|
return nil, fmt.Errorf("could not parse contents of protoset file %q: %v", fileName, err) |
|
} |
|
|
|
files.File = append(files.File, fs.File...) |
|
} |
|
return DescriptorSourceFromFileDescriptorSet(files) |
|
} |
|
|
|
// DescriptorSourceFromProtoFiles creates a DescriptorSource that is backed by the named files, |
|
// whose contents are Protocol Buffer source files. The given importPaths are used to locate |
|
// any imported files. |
|
func DescriptorSourceFromProtoFiles(importPaths []string, fileNames ...string) (DescriptorSource, error) { |
|
p := protoparse.Parser{ |
|
ImportPaths: importPaths, |
|
InferImportPaths: len(importPaths) == 0, |
|
} |
|
fds, err := p.ParseFiles(fileNames...) |
|
if err != nil { |
|
return nil, fmt.Errorf("could not parse given files: %v", err) |
|
} |
|
|
|
return DescriptorSourceFromFileDescriptors(fds...) |
|
} |
|
|
|
// DescriptorSourceFromFileDescriptorSet creates a DescriptorSource that is backed by the FileDescriptorSet. |
|
func DescriptorSourceFromFileDescriptorSet(files *descpb.FileDescriptorSet) (DescriptorSource, error) { |
|
unresolved := map[string]*descpb.FileDescriptorProto{} |
|
for _, fd := range files.File { |
|
unresolved[fd.GetName()] = fd |
|
} |
|
|
|
resolved := map[string]*desc.FileDescriptor{} |
|
for _, fd := range files.File { |
|
_, err := resolveFileDescriptor(unresolved, resolved, fd.GetName()) |
|
if err != nil { |
|
return nil, err |
|
} |
|
} |
|
|
|
return &fileSource{files: resolved}, nil |
|
} |
|
|
|
func resolveFileDescriptor(unresolved map[string]*descpb.FileDescriptorProto, resolved map[string]*desc.FileDescriptor, filename string) (*desc.FileDescriptor, error) { |
|
if r, ok := resolved[filename]; ok { |
|
return r, nil |
|
} |
|
|
|
fd, ok := unresolved[filename] |
|
if !ok { |
|
return nil, fmt.Errorf("no descriptor found for %q", filename) |
|
} |
|
|
|
deps := make([]*desc.FileDescriptor, 0, len(fd.GetDependency())) |
|
for _, dep := range fd.GetDependency() { |
|
depFd, err := resolveFileDescriptor(unresolved, resolved, dep) |
|
if err != nil { |
|
return nil, err |
|
} |
|
deps = append(deps, depFd) |
|
} |
|
|
|
result, err := desc.CreateFileDescriptor(fd, deps...) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
resolved[filename] = result |
|
return result, nil |
|
} |
|
|
|
// DescriptorSourceFromFileDescriptors creates a DescriptorSource that is backed by the given |
|
// file descriptors |
|
func DescriptorSourceFromFileDescriptors(files ...*desc.FileDescriptor) (DescriptorSource, error) { |
|
fds := map[string]*desc.FileDescriptor{} |
|
for _, fd := range files { |
|
if err := addFile(fd, fds); err != nil { |
|
return nil, err |
|
} |
|
} |
|
|
|
return &fileSource{files: fds}, nil |
|
} |
|
|
|
func addFile(fd *desc.FileDescriptor, fds map[string]*desc.FileDescriptor) error { |
|
name := fd.GetName() |
|
if existing, ok := fds[name]; ok { |
|
// already added this file |
|
if existing != fd { |
|
// doh! duplicate files provided |
|
return fmt.Errorf("given files include multiple copies of %q", name) |
|
} |
|
return nil |
|
} |
|
|
|
fds[name] = fd |
|
for _, dep := range fd.GetDependencies() { |
|
if err := addFile(dep, fds); err != nil { |
|
return err |
|
} |
|
} |
|
|
|
return nil |
|
} |
|
|
|
type fileSource struct { |
|
files map[string]*desc.FileDescriptor |
|
er *dynamic.ExtensionRegistry |
|
erInit sync.Once |
|
} |
|
|
|
func (fs *fileSource) ListServices() ([]string, error) { |
|
set := map[string]bool{} |
|
for _, fd := range fs.files { |
|
for _, svc := range fd.GetServices() { |
|
set[svc.GetFullyQualifiedName()] = true |
|
} |
|
} |
|
sl := make([]string, 0, len(set)) |
|
for svc := range set { |
|
sl = append(sl, svc) |
|
} |
|
|
|
return sl, nil |
|
} |
|
|
|
func (fs *fileSource) ListMethods() (map[string][]string, error) { |
|
set := map[string][]string{} |
|
for _, fd := range fs.files { |
|
for _, svc := range fd.GetServices() { |
|
m, ok := set[svc.GetFullyQualifiedName()] |
|
if !ok { |
|
m = []string{} |
|
} |
|
for _, v := range svc.GetMethods() { |
|
m = append(m, v.GetName()) |
|
// m = append(.GetFullyQualifiedName()], v.GetName()) |
|
} |
|
|
|
set[svc.GetFullyQualifiedName()] = m |
|
} |
|
} |
|
|
|
return set, nil |
|
} |
|
|
|
// GetAllFiles returns all of the underlying file descriptors. This is |
|
// more thorough and more efficient than the fallback strategy used by |
|
// the GetAllFiles package method, for enumerating all files from a |
|
// descriptor source. |
|
func (fs *fileSource) GetAllFiles() ([]*desc.FileDescriptor, error) { |
|
files := make([]*desc.FileDescriptor, len(fs.files)) |
|
i := 0 |
|
for _, fd := range fs.files { |
|
files[i] = fd |
|
i++ |
|
} |
|
|
|
return files, nil |
|
} |
|
|
|
func (fs *fileSource) FindSymbol(fullyQualifiedName string) (desc.Descriptor, error) { |
|
for _, fd := range fs.files { |
|
if dsc := fd.FindSymbol(fullyQualifiedName); dsc != nil { |
|
return dsc, nil |
|
} |
|
} |
|
|
|
return nil, notFound("Symbol", fullyQualifiedName) |
|
} |
|
|
|
func (fs *fileSource) AllExtensionsForType(typeName string) ([]*desc.FieldDescriptor, error) { |
|
fs.erInit.Do(func() { |
|
fs.er = &dynamic.ExtensionRegistry{} |
|
for _, fd := range fs.files { |
|
fs.er.AddExtensionsFromFile(fd) |
|
} |
|
}) |
|
return fs.er.AllExtensionsForType(typeName), nil |
|
} |
|
|
|
// DescriptorSourceFromServer creates a DescriptorSource that uses the given gRPC reflection client |
|
// to interrogate a server for descriptor information. If the server does not support the reflection |
|
// API then the various DescriptorSource methods will return ErrReflectionNotSupported |
|
func DescriptorSourceFromServer(_ context.Context, refClient *grpcreflect.Client) DescriptorSource { |
|
return serverSource{client: refClient} |
|
} |
|
|
|
type serverSource struct { |
|
client *grpcreflect.Client |
|
} |
|
|
|
func (ss serverSource) ListServices() ([]string, error) { |
|
svcs, err := ss.client.ListServices() |
|
return svcs, reflectionSupport(err) |
|
} |
|
|
|
func (ss serverSource) FindSymbol(fullyQualifiedName string) (desc.Descriptor, error) { |
|
file, err := ss.client.FileContainingSymbol(fullyQualifiedName) |
|
if err != nil { |
|
return nil, reflectionSupport(err) |
|
} |
|
|
|
d := file.FindSymbol(fullyQualifiedName) |
|
if d == nil { |
|
return nil, notFound("Symbol", fullyQualifiedName) |
|
} |
|
|
|
return d, nil |
|
} |
|
|
|
func (ss serverSource) AllExtensionsForType(typeName string) ([]*desc.FieldDescriptor, error) { |
|
var exts []*desc.FieldDescriptor |
|
nums, err := ss.client.AllExtensionNumbersForType(typeName) |
|
if err != nil { |
|
return nil, reflectionSupport(err) |
|
} |
|
|
|
for _, fieldNum := range nums { |
|
ext, err := ss.client.ResolveExtension(typeName, fieldNum) |
|
if err != nil { |
|
return nil, reflectionSupport(err) |
|
} |
|
exts = append(exts, ext) |
|
} |
|
|
|
return exts, nil |
|
} |
|
|
|
func reflectionSupport(err error) error { |
|
if err == nil { |
|
return nil |
|
} |
|
|
|
if stat, ok := status.FromError(err); ok && stat.Code() == codes.Unimplemented { |
|
return ErrReflectionNotSupported |
|
} |
|
|
|
return err |
|
}
|
|
|