github.com/jhump/protoreflect@v1.16.0/desc/sourceinfo/registry.go (about)

     1  // Package sourceinfo provides the ability to register and query source code info
     2  // for file descriptors that are compiled into the binary. This data is registered
     3  // by code generated from the protoc-gen-gosrcinfo plugin.
     4  //
     5  // The standard descriptors bundled into the compiled binary are stripped of source
     6  // code info, to reduce binary size and reduce runtime memory footprint. However,
     7  // the source code info can be very handy and worth the size cost when used with
     8  // gRPC services and the server reflection service. Without source code info, the
     9  // descriptors that a client downloads from the reflection service have no comments.
    10  // But the presence of comments, and the ability to show them to humans, can greatly
    11  // improve the utility of user agents that use the reflection service.
    12  //
    13  // When the protoc-gen-gosrcinfo plugin is used, the desc.Load* methods, which load
    14  // descriptors for compiled-in elements, will automatically include source code
    15  // info, using the data registered with this package.
    16  //
    17  // In order to make the reflection service use this functionality, you will need to
    18  // be using v1.45 or higher of the Go runtime for gRPC (google.golang.org/grpc). The
    19  // following snippet demonstrates how to do this in your server. Do this instead of
    20  // using the reflection.Register function:
    21  //
    22  //	refSvr := reflection.NewServer(reflection.ServerOptions{
    23  //	    Services:           grpcServer,
    24  //	    DescriptorResolver: sourceinfo.GlobalFiles,
    25  //	    ExtensionResolver:  sourceinfo.GlobalFiles,
    26  //	})
    27  //	grpc_reflection_v1alpha.RegisterServerReflectionServer(grpcServer, refSvr)
    28  package sourceinfo
    29  
    30  import (
    31  	"bytes"
    32  	"compress/gzip"
    33  	"fmt"
    34  	"github.com/golang/protobuf/proto"
    35  	"io/ioutil"
    36  	"sync"
    37  
    38  	"google.golang.org/protobuf/reflect/protodesc"
    39  	"google.golang.org/protobuf/reflect/protoreflect"
    40  	"google.golang.org/protobuf/reflect/protoregistry"
    41  	"google.golang.org/protobuf/types/descriptorpb"
    42  )
    43  
    44  var (
    45  	// GlobalFiles is a registry of descriptors that include source code info, if the
    46  	// files they belong to were processed with protoc-gen-gosrcinfo.
    47  	//
    48  	// If is mean to serve as a drop-in alternative to protoregistry.GlobalFiles that
    49  	// can include source code info in the returned descriptors.
    50  	GlobalFiles Resolver = registry{}
    51  
    52  	// GlobalTypes is a registry of descriptors that include source code info, if the
    53  	// files they belong to were processed with protoc-gen-gosrcinfo.
    54  	//
    55  	// If is mean to serve as a drop-in alternative to protoregistry.GlobalTypes that
    56  	// can include source code info in the returned descriptors.
    57  	GlobalTypes TypeResolver = registry{}
    58  
    59  	mu               sync.RWMutex
    60  	sourceInfoByFile = map[string]*descriptorpb.SourceCodeInfo{}
    61  	fileDescriptors  = map[protoreflect.FileDescriptor]protoreflect.FileDescriptor{}
    62  )
    63  
    64  // Resolver can resolve file names into file descriptors and also provides methods for
    65  // resolving extensions.
    66  type Resolver interface {
    67  	protodesc.Resolver
    68  	protoregistry.ExtensionTypeResolver
    69  	RangeExtensionsByMessage(message protoreflect.FullName, f func(protoreflect.ExtensionType) bool)
    70  }
    71  
    72  // NB: These interfaces are far from ideal. Ideally, Resolver would have
    73  //    * EITHER been named FileResolver and not included the extension methods.
    74  //    * OR also included message methods (i.e. embed protoregistry.MessageTypeResolver).
    75  //   Now (since it's been released) we can't add the message methods to the interface as
    76  //   that's not a backwards-compatible change. So we have to introduce the new interface
    77  //   below, which is now a little confusing since it has some overlap with Resolver.
    78  
    79  // TypeResolver can resolve message names and URLs into message descriptors and also
    80  // provides methods for resolving extensions.
    81  type TypeResolver interface {
    82  	protoregistry.MessageTypeResolver
    83  	protoregistry.ExtensionTypeResolver
    84  	RangeExtensionsByMessage(message protoreflect.FullName, f func(protoreflect.ExtensionType) bool)
    85  }
    86  
    87  // RegisterSourceInfo registers the given source code info for the file descriptor
    88  // with the given path/name.
    89  //
    90  // This is automatically used from older generated code if using a previous release of
    91  // the protoc-gen-gosrcinfo plugin.
    92  func RegisterSourceInfo(file string, srcInfo *descriptorpb.SourceCodeInfo) {
    93  	mu.Lock()
    94  	defer mu.Unlock()
    95  	sourceInfoByFile[file] = srcInfo
    96  }
    97  
    98  // RegisterEncodedSourceInfo registers the given source code info, which is a serialized
    99  // and gzipped form of a google.protobuf.SourceCodeInfo message.
   100  //
   101  // This is automatically used from generated code if using the protoc-gen-gosrcinfo
   102  // plugin.
   103  func RegisterEncodedSourceInfo(file string, data []byte) error {
   104  	zipReader, err := gzip.NewReader(bytes.NewReader(data))
   105  	if err != nil {
   106  		return err
   107  	}
   108  	defer func() {
   109  		_ = zipReader.Close()
   110  	}()
   111  	unzipped, err := ioutil.ReadAll(zipReader)
   112  	if err != nil {
   113  		return err
   114  	}
   115  	var srcInfo descriptorpb.SourceCodeInfo
   116  	if err := proto.Unmarshal(unzipped, &srcInfo); err != nil {
   117  		return err
   118  	}
   119  	RegisterSourceInfo(file, &srcInfo)
   120  	return nil
   121  }
   122  
   123  // SourceInfoForFile queries for any registered source code info for the file
   124  // descriptor with the given path/name. It returns nil if no source code info
   125  // was registered.
   126  func SourceInfoForFile(file string) *descriptorpb.SourceCodeInfo {
   127  	mu.RLock()
   128  	defer mu.RUnlock()
   129  	return sourceInfoByFile[file]
   130  }
   131  
   132  func canWrap(d protoreflect.Descriptor) bool {
   133  	srcInfo := SourceInfoForFile(d.ParentFile().Path())
   134  	return len(srcInfo.GetLocation()) > 0
   135  }
   136  
   137  func getFile(fd protoreflect.FileDescriptor) protoreflect.FileDescriptor {
   138  	if fd == nil {
   139  		return nil
   140  	}
   141  
   142  	mu.RLock()
   143  	result := fileDescriptors[fd]
   144  	mu.RUnlock()
   145  
   146  	if result != nil {
   147  		return result
   148  	}
   149  
   150  	mu.Lock()
   151  	defer mu.Unlock()
   152  	// double-check, in case it was added to map while upgrading lock
   153  	result = fileDescriptors[fd]
   154  	if result != nil {
   155  		return result
   156  	}
   157  
   158  	srcInfo := sourceInfoByFile[fd.Path()]
   159  	if len(srcInfo.GetLocation()) > 0 {
   160  		result = &fileDescriptor{
   161  			FileDescriptor: fd,
   162  			locs: &sourceLocations{
   163  				orig: srcInfo.Location,
   164  			},
   165  		}
   166  	} else {
   167  		// nothing to do; don't bother wrapping
   168  		result = fd
   169  	}
   170  	fileDescriptors[fd] = result
   171  	return result
   172  }
   173  
   174  type registry struct{}
   175  
   176  var _ protodesc.Resolver = &registry{}
   177  
   178  func (r registry) FindFileByPath(path string) (protoreflect.FileDescriptor, error) {
   179  	fd, err := protoregistry.GlobalFiles.FindFileByPath(path)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  	return getFile(fd), nil
   184  }
   185  
   186  func (r registry) FindDescriptorByName(name protoreflect.FullName) (protoreflect.Descriptor, error) {
   187  	d, err := protoregistry.GlobalFiles.FindDescriptorByName(name)
   188  	if !canWrap(d) {
   189  		return d, nil
   190  	}
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  	switch d := d.(type) {
   195  	case protoreflect.FileDescriptor:
   196  		return getFile(d), nil
   197  	case protoreflect.MessageDescriptor:
   198  		return messageDescriptor{d}, nil
   199  	case protoreflect.ExtensionTypeDescriptor:
   200  		return extensionDescriptor{d}, nil
   201  	case protoreflect.FieldDescriptor:
   202  		return fieldDescriptor{d}, nil
   203  	case protoreflect.OneofDescriptor:
   204  		return oneOfDescriptor{d}, nil
   205  	case protoreflect.EnumDescriptor:
   206  		return enumDescriptor{d}, nil
   207  	case protoreflect.EnumValueDescriptor:
   208  		return enumValueDescriptor{d}, nil
   209  	case protoreflect.ServiceDescriptor:
   210  		return serviceDescriptor{d}, nil
   211  	case protoreflect.MethodDescriptor:
   212  		return methodDescriptor{d}, nil
   213  	default:
   214  		return nil, fmt.Errorf("unrecognized descriptor type: %T", d)
   215  	}
   216  }
   217  
   218  func (r registry) FindMessageByName(message protoreflect.FullName) (protoreflect.MessageType, error) {
   219  	mt, err := protoregistry.GlobalTypes.FindMessageByName(message)
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  	if !canWrap(mt.Descriptor()) {
   224  		return mt, nil
   225  	}
   226  	return messageType{mt}, nil
   227  }
   228  
   229  func (r registry) FindMessageByURL(url string) (protoreflect.MessageType, error) {
   230  	mt, err := protoregistry.GlobalTypes.FindMessageByURL(url)
   231  	if err != nil {
   232  		return nil, err
   233  	}
   234  	if !canWrap(mt.Descriptor()) {
   235  		return mt, nil
   236  	}
   237  	return messageType{mt}, nil
   238  }
   239  
   240  func (r registry) FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error) {
   241  	xt, err := protoregistry.GlobalTypes.FindExtensionByName(field)
   242  	if err != nil {
   243  		return nil, err
   244  	}
   245  	if !canWrap(xt.TypeDescriptor()) {
   246  		return xt, nil
   247  	}
   248  	return extensionType{xt}, nil
   249  }
   250  
   251  func (r registry) FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error) {
   252  	xt, err := protoregistry.GlobalTypes.FindExtensionByNumber(message, field)
   253  	if err != nil {
   254  		return nil, err
   255  	}
   256  	if !canWrap(xt.TypeDescriptor()) {
   257  		return xt, nil
   258  	}
   259  	return extensionType{xt}, nil
   260  }
   261  
   262  func (r registry) RangeExtensionsByMessage(message protoreflect.FullName, fn func(protoreflect.ExtensionType) bool) {
   263  	protoregistry.GlobalTypes.RangeExtensionsByMessage(message, func(xt protoreflect.ExtensionType) bool {
   264  		if canWrap(xt.TypeDescriptor()) {
   265  			xt = extensionType{xt}
   266  		}
   267  		return fn(xt)
   268  	})
   269  }