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 }