go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/proto/google/descutil/util.go (about)

     1  // Copyright 2015 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package descutil
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  	"strconv"
    21  	"strings"
    22  
    23  	"github.com/golang/protobuf/proto"
    24  	pb "google.golang.org/protobuf/types/descriptorpb"
    25  )
    26  
    27  // splitFullName splits package name and service/type name.
    28  func splitFullName(fullName string) (pkg string, name string) {
    29  	lastDot := strings.LastIndex(fullName, ".")
    30  	if lastDot < 0 {
    31  		return "", fullName
    32  	}
    33  	return fullName[:lastDot], fullName[lastDot+1:]
    34  }
    35  
    36  ////////////////////////////////////////////////////////////////////////////////
    37  // FileDescriptorSet
    38  
    39  // FindFile searches for a FileDescriptorProto by name.
    40  func FindFile(s *pb.FileDescriptorSet, name string) int {
    41  	for i, x := range s.GetFile() {
    42  		if x.GetName() == name {
    43  			return i
    44  		}
    45  	}
    46  	return -1
    47  }
    48  
    49  // Resolve searches for an object by full name. obj can be one of
    50  // *ServiceDescriptorProto,
    51  // *MethodDescriptorProto,
    52  // *DescriptorProto,
    53  // *FieldDescriptorProto,
    54  // *DescriptorProto,
    55  // *EnumDescriptorProto,
    56  // *EnumValueDescriptorProto or
    57  // nil
    58  //
    59  // For path, see comment in SourceCodeInfo message.
    60  func Resolve(s *pb.FileDescriptorSet, fullName string) (file *pb.FileDescriptorProto, obj any, path []int) {
    61  	if fullName == "" {
    62  		return nil, nil, nil
    63  	}
    64  	pkg, name := splitFullName(fullName)
    65  
    66  	// Check top-level objects.
    67  	for _, f := range s.GetFile() {
    68  		if f.GetPackage() == pkg {
    69  			if i := FindServiceForFile(f, name); i != -1 {
    70  				return f, f.Service[i], []int{FileDescriptorProtoServiceTag, i}
    71  			}
    72  			if i := FindMessageForFile(f, name); i != -1 {
    73  				return f, f.MessageType[i], []int{FileDescriptorProtoMessageTag, i}
    74  			}
    75  			if i := FindEnumForFile(f, name); i != -1 {
    76  				return f, f.EnumType[i], []int{FileDescriptorProtoEnumTag, i}
    77  			}
    78  		}
    79  	}
    80  
    81  	// Recurse.
    82  	var parent any
    83  	file, parent, path = Resolve(s, pkg)
    84  	switch parent := parent.(type) {
    85  
    86  	case *pb.ServiceDescriptorProto:
    87  		if i := FindMethodForService(parent, name); i != -1 {
    88  			return file, parent.Method[i], append(path, ServiceDescriptorProtoMethodTag, i)
    89  		}
    90  
    91  	case *pb.DescriptorProto:
    92  		if i := FindMessage(parent, name); i != -1 {
    93  			return file, parent.NestedType[i], append(path, DescriptorProtoNestedTypeTag, i)
    94  		}
    95  		if i := FindEnum(parent, name); i != -1 {
    96  			return file, parent.EnumType[i], append(path, DescriptorProtoEnumTypeTag, i)
    97  		}
    98  		if i := FindField(parent, name); i != -1 {
    99  			return file, parent.Field[i], append(path, DescriptorProtoFieldTag, i)
   100  		}
   101  		if i := FindOneOf(parent, name); i != -1 {
   102  			return file, parent.OneofDecl[i], append(path, DescriptorProtoOneOfTag, i)
   103  		}
   104  
   105  	case *pb.EnumDescriptorProto:
   106  		if i := FindEnumValue(parent, name); i != -1 {
   107  			return file, parent.Value[i], append(path, EnumDescriptorProtoValueTag, i)
   108  		}
   109  	}
   110  
   111  	return nil, nil, nil
   112  }
   113  
   114  // FindService searches for a service by full name.
   115  func FindService(s *pb.FileDescriptorSet, fullName string) (file *pb.FileDescriptorProto, serviceIndex int) {
   116  	pkg, name := splitFullName(fullName)
   117  	for _, f := range s.GetFile() {
   118  		if f.GetPackage() == pkg {
   119  			if i := FindServiceForFile(f, name); i != -1 {
   120  				return f, i
   121  			}
   122  		}
   123  	}
   124  	return nil, -1
   125  }
   126  
   127  ////////////////////////////////////////////////////////////////////////////////
   128  // FileDescriptorProto
   129  
   130  // FindServiceForFile searches for a FileDescriptorProto by name.
   131  func FindServiceForFile(f *pb.FileDescriptorProto, name string) int {
   132  	for i, x := range f.GetService() {
   133  		if x.GetName() == name {
   134  			return i
   135  		}
   136  	}
   137  	return -1
   138  }
   139  
   140  // FindMessageForFile searches for a DescriptorProto by name.
   141  func FindMessageForFile(f *pb.FileDescriptorProto, name string) int {
   142  	for i, x := range f.GetMessageType() {
   143  		if x.GetName() == name {
   144  			return i
   145  		}
   146  	}
   147  	return -1
   148  }
   149  
   150  // FindEnumForFile searches for an EnumDescriptorProto by name.
   151  func FindEnumForFile(f *pb.FileDescriptorProto, name string) int {
   152  	for i, x := range f.GetEnumType() {
   153  		if x.GetName() == name {
   154  			return i
   155  		}
   156  	}
   157  	return -1
   158  }
   159  
   160  ////////////////////////////////////////////////////////////////////////////////
   161  // ServiceDescriptorProto
   162  
   163  // FindMethodForService searches for a MethodDescriptorProto by name.
   164  func FindMethodForService(s *pb.ServiceDescriptorProto, name string) int {
   165  	for i, x := range s.GetMethod() {
   166  		if x.GetName() == name {
   167  			return i
   168  		}
   169  	}
   170  	return -1
   171  }
   172  
   173  ////////////////////////////////////////////////////////////////////////////////
   174  // DescriptorProto (a message)
   175  
   176  // FindField searches for a FieldDescriptorProto by name.
   177  func FindField(d *pb.DescriptorProto, name string) int {
   178  	for i, x := range d.GetField() {
   179  		if x.GetName() == name {
   180  			return i
   181  		}
   182  	}
   183  	return -1
   184  }
   185  
   186  // FindMessage searches for a nested DescriptorProto by name.
   187  func FindMessage(d *pb.DescriptorProto, name string) int {
   188  	for i, x := range d.GetNestedType() {
   189  		if x.GetName() == name {
   190  			return i
   191  		}
   192  	}
   193  	return -1
   194  }
   195  
   196  // FindEnum searches for a nested EnumDescriptorProto by name.
   197  func FindEnum(d *pb.DescriptorProto, name string) int {
   198  	for i, x := range d.GetEnumType() {
   199  		if x.GetName() == name {
   200  			return i
   201  		}
   202  	}
   203  	return -1
   204  }
   205  
   206  // FindOneOf searches for a nested OneofDescriptorProto by name.
   207  func FindOneOf(d *pb.DescriptorProto, name string) int {
   208  	for i, x := range d.GetOneofDecl() {
   209  		if x.GetName() == name {
   210  			return i
   211  		}
   212  	}
   213  	return -1
   214  }
   215  
   216  ////////////////////////////////////////////////////////////////////////////////
   217  // FieldDescriptorProto
   218  
   219  // Repeated returns true if the field is repeated.
   220  func Repeated(f *pb.FieldDescriptorProto) bool {
   221  	return f.GetLabel() == pb.FieldDescriptorProto_LABEL_REPEATED
   222  }
   223  
   224  // Required returns true if the field is required.
   225  func Required(f *pb.FieldDescriptorProto) bool {
   226  	return f.GetLabel() == pb.FieldDescriptorProto_LABEL_REQUIRED
   227  }
   228  
   229  ////////////////////////////////////////////////////////////////////////////////
   230  // EnumDescriptorProto
   231  
   232  // FindEnumValue searches for an EnumValueDescriptorProto by name.
   233  func FindEnumValue(e *pb.EnumDescriptorProto, name string) int {
   234  	for i, x := range e.GetValue() {
   235  		if x.GetName() == name {
   236  			return i
   237  		}
   238  	}
   239  	return -1
   240  }
   241  
   242  // FindValueByNumber searches for an EnumValueDescriptorProto by number.
   243  func FindValueByNumber(e *pb.EnumDescriptorProto, number int32) int {
   244  	for i, x := range e.GetValue() {
   245  		if x.GetNumber() == number {
   246  			return i
   247  		}
   248  	}
   249  	return -1
   250  }
   251  
   252  ////////////////////////////////////////////////////////////////////////////////
   253  // SourceCodeInfo
   254  
   255  // At returns a descriptor proto or its field value at the given path.
   256  // The path has same semantics as
   257  // descriptor.SourceCodeInfo_Location.Path. See its comment for explanation.
   258  //
   259  // For example, given a FileDescriptorProto and path [4, 2],
   260  // At will return the 2nd top-level message DescriptorProto
   261  // because 4 is FileDescriptorProto.MessageType field tag.
   262  //
   263  // Does not supported uninterpreted options, returns (nil, nil).
   264  func At(descProto proto.Message, path []int32) (any, error) {
   265  	cur := reflect.ValueOf(descProto)
   266  
   267  	pathStr := func() string {
   268  		s := make([]string, len(path))
   269  		for i, x := range path {
   270  			s[i] = strconv.FormatInt(int64(x), 10)
   271  		}
   272  		return fmt.Sprintf("[%s]", strings.Join(s, ","))
   273  	}
   274  
   275  	for i := 0; i < len(path); i++ {
   276  		if cur.Kind() == reflect.Slice {
   277  			index := int(path[i])
   278  			if index < 0 || index >= cur.Len() {
   279  				return nil, fmt.Errorf("element #%d of path %s is an index and it is out of bounds", i, pathStr())
   280  			}
   281  			cur = cur.Index(int(path[i]))
   282  		} else if msg, ok := cur.Interface().(proto.Message); ok {
   283  			tag := path[i]
   284  			// The tag->field index mapping could be precomputed.
   285  			var prop *proto.Properties
   286  			for _, p := range proto.GetProperties(cur.Type().Elem()).Prop {
   287  				if p.Tag == int(tag) {
   288  					prop = p
   289  					break
   290  				}
   291  			}
   292  			if prop == nil {
   293  				// Is this an extension?
   294  				if _, err := proto.GetExtension(msg, &proto.ExtensionDesc{Field: tag}); err == nil {
   295  					// Yes. Skip it.
   296  					return nil, nil
   297  				}
   298  
   299  				return nil, fmt.Errorf("%T has no tag %d", cur.Interface(), tag)
   300  			}
   301  			cur = cur.Elem().FieldByName(prop.Name)
   302  			if cur.Kind() != reflect.Ptr && cur.Kind() != reflect.Slice {
   303  				panic("a field value is not a slice or pointer")
   304  			}
   305  		} else {
   306  			return nil, fmt.Errorf("expected end at index %d in path %s", i, pathStr())
   307  		}
   308  	}
   309  	return cur.Interface(), nil
   310  }
   311  
   312  // IndexSourceCodeInfo returns a map that maps a pointer to the associated
   313  // source code info, where the pointer points to a descriptor proto or its
   314  // field, e.g. &myFieldDescriptorProto.Name.
   315  //
   316  // IndexSourceCodeInfo can be used to retrieve comments.
   317  //
   318  // Does not support whole-slice locations.
   319  func IndexSourceCodeInfo(f *pb.FileDescriptorProto) (map[any]*pb.SourceCodeInfo_Location, error) {
   320  	if f.SourceCodeInfo == nil {
   321  		return nil, nil
   322  	}
   323  	ret := make(map[any]*pb.SourceCodeInfo_Location, len(f.SourceCodeInfo.Location))
   324  	for _, loc := range f.SourceCodeInfo.Location {
   325  		ptr, err := At(f, loc.Path)
   326  		switch v := reflect.ValueOf(ptr); {
   327  		case err != nil:
   328  			return nil, err
   329  		case !v.IsValid():
   330  		case v.Kind() == reflect.Slice:
   331  			// A slice cannot be used as a map key.
   332  			// Whole slice declarations are not supported.
   333  		default:
   334  			ret[ptr] = loc
   335  		}
   336  	}
   337  	return ret, nil
   338  }