1*1c12ee1eSDan Willemsen// Copyright 2018 The Go Authors. All rights reserved. 2*1c12ee1eSDan Willemsen// Use of this source code is governed by a BSD-style 3*1c12ee1eSDan Willemsen// license that can be found in the LICENSE file. 4*1c12ee1eSDan Willemsen 5*1c12ee1eSDan Willemsen// Package descfmt provides functionality to format descriptors. 6*1c12ee1eSDan Willemsenpackage descfmt 7*1c12ee1eSDan Willemsen 8*1c12ee1eSDan Willemsenimport ( 9*1c12ee1eSDan Willemsen "fmt" 10*1c12ee1eSDan Willemsen "io" 11*1c12ee1eSDan Willemsen "reflect" 12*1c12ee1eSDan Willemsen "strconv" 13*1c12ee1eSDan Willemsen "strings" 14*1c12ee1eSDan Willemsen 15*1c12ee1eSDan Willemsen "google.golang.org/protobuf/internal/detrand" 16*1c12ee1eSDan Willemsen "google.golang.org/protobuf/internal/pragma" 17*1c12ee1eSDan Willemsen "google.golang.org/protobuf/reflect/protoreflect" 18*1c12ee1eSDan Willemsen) 19*1c12ee1eSDan Willemsen 20*1c12ee1eSDan Willemsentype list interface { 21*1c12ee1eSDan Willemsen Len() int 22*1c12ee1eSDan Willemsen pragma.DoNotImplement 23*1c12ee1eSDan Willemsen} 24*1c12ee1eSDan Willemsen 25*1c12ee1eSDan Willemsenfunc FormatList(s fmt.State, r rune, vs list) { 26*1c12ee1eSDan Willemsen io.WriteString(s, formatListOpt(vs, true, r == 'v' && (s.Flag('+') || s.Flag('#')))) 27*1c12ee1eSDan Willemsen} 28*1c12ee1eSDan Willemsenfunc formatListOpt(vs list, isRoot, allowMulti bool) string { 29*1c12ee1eSDan Willemsen start, end := "[", "]" 30*1c12ee1eSDan Willemsen if isRoot { 31*1c12ee1eSDan Willemsen var name string 32*1c12ee1eSDan Willemsen switch vs.(type) { 33*1c12ee1eSDan Willemsen case protoreflect.Names: 34*1c12ee1eSDan Willemsen name = "Names" 35*1c12ee1eSDan Willemsen case protoreflect.FieldNumbers: 36*1c12ee1eSDan Willemsen name = "FieldNumbers" 37*1c12ee1eSDan Willemsen case protoreflect.FieldRanges: 38*1c12ee1eSDan Willemsen name = "FieldRanges" 39*1c12ee1eSDan Willemsen case protoreflect.EnumRanges: 40*1c12ee1eSDan Willemsen name = "EnumRanges" 41*1c12ee1eSDan Willemsen case protoreflect.FileImports: 42*1c12ee1eSDan Willemsen name = "FileImports" 43*1c12ee1eSDan Willemsen case protoreflect.Descriptor: 44*1c12ee1eSDan Willemsen name = reflect.ValueOf(vs).MethodByName("Get").Type().Out(0).Name() + "s" 45*1c12ee1eSDan Willemsen default: 46*1c12ee1eSDan Willemsen name = reflect.ValueOf(vs).Elem().Type().Name() 47*1c12ee1eSDan Willemsen } 48*1c12ee1eSDan Willemsen start, end = name+"{", "}" 49*1c12ee1eSDan Willemsen } 50*1c12ee1eSDan Willemsen 51*1c12ee1eSDan Willemsen var ss []string 52*1c12ee1eSDan Willemsen switch vs := vs.(type) { 53*1c12ee1eSDan Willemsen case protoreflect.Names: 54*1c12ee1eSDan Willemsen for i := 0; i < vs.Len(); i++ { 55*1c12ee1eSDan Willemsen ss = append(ss, fmt.Sprint(vs.Get(i))) 56*1c12ee1eSDan Willemsen } 57*1c12ee1eSDan Willemsen return start + joinStrings(ss, false) + end 58*1c12ee1eSDan Willemsen case protoreflect.FieldNumbers: 59*1c12ee1eSDan Willemsen for i := 0; i < vs.Len(); i++ { 60*1c12ee1eSDan Willemsen ss = append(ss, fmt.Sprint(vs.Get(i))) 61*1c12ee1eSDan Willemsen } 62*1c12ee1eSDan Willemsen return start + joinStrings(ss, false) + end 63*1c12ee1eSDan Willemsen case protoreflect.FieldRanges: 64*1c12ee1eSDan Willemsen for i := 0; i < vs.Len(); i++ { 65*1c12ee1eSDan Willemsen r := vs.Get(i) 66*1c12ee1eSDan Willemsen if r[0]+1 == r[1] { 67*1c12ee1eSDan Willemsen ss = append(ss, fmt.Sprintf("%d", r[0])) 68*1c12ee1eSDan Willemsen } else { 69*1c12ee1eSDan Willemsen ss = append(ss, fmt.Sprintf("%d:%d", r[0], r[1])) // enum ranges are end exclusive 70*1c12ee1eSDan Willemsen } 71*1c12ee1eSDan Willemsen } 72*1c12ee1eSDan Willemsen return start + joinStrings(ss, false) + end 73*1c12ee1eSDan Willemsen case protoreflect.EnumRanges: 74*1c12ee1eSDan Willemsen for i := 0; i < vs.Len(); i++ { 75*1c12ee1eSDan Willemsen r := vs.Get(i) 76*1c12ee1eSDan Willemsen if r[0] == r[1] { 77*1c12ee1eSDan Willemsen ss = append(ss, fmt.Sprintf("%d", r[0])) 78*1c12ee1eSDan Willemsen } else { 79*1c12ee1eSDan Willemsen ss = append(ss, fmt.Sprintf("%d:%d", r[0], int64(r[1])+1)) // enum ranges are end inclusive 80*1c12ee1eSDan Willemsen } 81*1c12ee1eSDan Willemsen } 82*1c12ee1eSDan Willemsen return start + joinStrings(ss, false) + end 83*1c12ee1eSDan Willemsen case protoreflect.FileImports: 84*1c12ee1eSDan Willemsen for i := 0; i < vs.Len(); i++ { 85*1c12ee1eSDan Willemsen var rs records 86*1c12ee1eSDan Willemsen rs.Append(reflect.ValueOf(vs.Get(i)), "Path", "Package", "IsPublic", "IsWeak") 87*1c12ee1eSDan Willemsen ss = append(ss, "{"+rs.Join()+"}") 88*1c12ee1eSDan Willemsen } 89*1c12ee1eSDan Willemsen return start + joinStrings(ss, allowMulti) + end 90*1c12ee1eSDan Willemsen default: 91*1c12ee1eSDan Willemsen _, isEnumValue := vs.(protoreflect.EnumValueDescriptors) 92*1c12ee1eSDan Willemsen for i := 0; i < vs.Len(); i++ { 93*1c12ee1eSDan Willemsen m := reflect.ValueOf(vs).MethodByName("Get") 94*1c12ee1eSDan Willemsen v := m.Call([]reflect.Value{reflect.ValueOf(i)})[0].Interface() 95*1c12ee1eSDan Willemsen ss = append(ss, formatDescOpt(v.(protoreflect.Descriptor), false, allowMulti && !isEnumValue)) 96*1c12ee1eSDan Willemsen } 97*1c12ee1eSDan Willemsen return start + joinStrings(ss, allowMulti && isEnumValue) + end 98*1c12ee1eSDan Willemsen } 99*1c12ee1eSDan Willemsen} 100*1c12ee1eSDan Willemsen 101*1c12ee1eSDan Willemsen// descriptorAccessors is a list of accessors to print for each descriptor. 102*1c12ee1eSDan Willemsen// 103*1c12ee1eSDan Willemsen// Do not print all accessors since some contain redundant information, 104*1c12ee1eSDan Willemsen// while others are pointers that we do not want to follow since the descriptor 105*1c12ee1eSDan Willemsen// is actually a cyclic graph. 106*1c12ee1eSDan Willemsen// 107*1c12ee1eSDan Willemsen// Using a list allows us to print the accessors in a sensible order. 108*1c12ee1eSDan Willemsenvar descriptorAccessors = map[reflect.Type][]string{ 109*1c12ee1eSDan Willemsen reflect.TypeOf((*protoreflect.FileDescriptor)(nil)).Elem(): {"Path", "Package", "Imports", "Messages", "Enums", "Extensions", "Services"}, 110*1c12ee1eSDan Willemsen reflect.TypeOf((*protoreflect.MessageDescriptor)(nil)).Elem(): {"IsMapEntry", "Fields", "Oneofs", "ReservedNames", "ReservedRanges", "RequiredNumbers", "ExtensionRanges", "Messages", "Enums", "Extensions"}, 111*1c12ee1eSDan Willemsen reflect.TypeOf((*protoreflect.FieldDescriptor)(nil)).Elem(): {"Number", "Cardinality", "Kind", "HasJSONName", "JSONName", "HasPresence", "IsExtension", "IsPacked", "IsWeak", "IsList", "IsMap", "MapKey", "MapValue", "HasDefault", "Default", "ContainingOneof", "ContainingMessage", "Message", "Enum"}, 112*1c12ee1eSDan Willemsen reflect.TypeOf((*protoreflect.OneofDescriptor)(nil)).Elem(): {"Fields"}, // not directly used; must keep in sync with formatDescOpt 113*1c12ee1eSDan Willemsen reflect.TypeOf((*protoreflect.EnumDescriptor)(nil)).Elem(): {"Values", "ReservedNames", "ReservedRanges"}, 114*1c12ee1eSDan Willemsen reflect.TypeOf((*protoreflect.EnumValueDescriptor)(nil)).Elem(): {"Number"}, 115*1c12ee1eSDan Willemsen reflect.TypeOf((*protoreflect.ServiceDescriptor)(nil)).Elem(): {"Methods"}, 116*1c12ee1eSDan Willemsen reflect.TypeOf((*protoreflect.MethodDescriptor)(nil)).Elem(): {"Input", "Output", "IsStreamingClient", "IsStreamingServer"}, 117*1c12ee1eSDan Willemsen} 118*1c12ee1eSDan Willemsen 119*1c12ee1eSDan Willemsenfunc FormatDesc(s fmt.State, r rune, t protoreflect.Descriptor) { 120*1c12ee1eSDan Willemsen io.WriteString(s, formatDescOpt(t, true, r == 'v' && (s.Flag('+') || s.Flag('#')))) 121*1c12ee1eSDan Willemsen} 122*1c12ee1eSDan Willemsenfunc formatDescOpt(t protoreflect.Descriptor, isRoot, allowMulti bool) string { 123*1c12ee1eSDan Willemsen rv := reflect.ValueOf(t) 124*1c12ee1eSDan Willemsen rt := rv.MethodByName("ProtoType").Type().In(0) 125*1c12ee1eSDan Willemsen 126*1c12ee1eSDan Willemsen start, end := "{", "}" 127*1c12ee1eSDan Willemsen if isRoot { 128*1c12ee1eSDan Willemsen start = rt.Name() + "{" 129*1c12ee1eSDan Willemsen } 130*1c12ee1eSDan Willemsen 131*1c12ee1eSDan Willemsen _, isFile := t.(protoreflect.FileDescriptor) 132*1c12ee1eSDan Willemsen rs := records{allowMulti: allowMulti} 133*1c12ee1eSDan Willemsen if t.IsPlaceholder() { 134*1c12ee1eSDan Willemsen if isFile { 135*1c12ee1eSDan Willemsen rs.Append(rv, "Path", "Package", "IsPlaceholder") 136*1c12ee1eSDan Willemsen } else { 137*1c12ee1eSDan Willemsen rs.Append(rv, "FullName", "IsPlaceholder") 138*1c12ee1eSDan Willemsen } 139*1c12ee1eSDan Willemsen } else { 140*1c12ee1eSDan Willemsen switch { 141*1c12ee1eSDan Willemsen case isFile: 142*1c12ee1eSDan Willemsen rs.Append(rv, "Syntax") 143*1c12ee1eSDan Willemsen case isRoot: 144*1c12ee1eSDan Willemsen rs.Append(rv, "Syntax", "FullName") 145*1c12ee1eSDan Willemsen default: 146*1c12ee1eSDan Willemsen rs.Append(rv, "Name") 147*1c12ee1eSDan Willemsen } 148*1c12ee1eSDan Willemsen switch t := t.(type) { 149*1c12ee1eSDan Willemsen case protoreflect.FieldDescriptor: 150*1c12ee1eSDan Willemsen for _, s := range descriptorAccessors[rt] { 151*1c12ee1eSDan Willemsen switch s { 152*1c12ee1eSDan Willemsen case "MapKey": 153*1c12ee1eSDan Willemsen if k := t.MapKey(); k != nil { 154*1c12ee1eSDan Willemsen rs.recs = append(rs.recs, [2]string{"MapKey", k.Kind().String()}) 155*1c12ee1eSDan Willemsen } 156*1c12ee1eSDan Willemsen case "MapValue": 157*1c12ee1eSDan Willemsen if v := t.MapValue(); v != nil { 158*1c12ee1eSDan Willemsen switch v.Kind() { 159*1c12ee1eSDan Willemsen case protoreflect.EnumKind: 160*1c12ee1eSDan Willemsen rs.recs = append(rs.recs, [2]string{"MapValue", string(v.Enum().FullName())}) 161*1c12ee1eSDan Willemsen case protoreflect.MessageKind, protoreflect.GroupKind: 162*1c12ee1eSDan Willemsen rs.recs = append(rs.recs, [2]string{"MapValue", string(v.Message().FullName())}) 163*1c12ee1eSDan Willemsen default: 164*1c12ee1eSDan Willemsen rs.recs = append(rs.recs, [2]string{"MapValue", v.Kind().String()}) 165*1c12ee1eSDan Willemsen } 166*1c12ee1eSDan Willemsen } 167*1c12ee1eSDan Willemsen case "ContainingOneof": 168*1c12ee1eSDan Willemsen if od := t.ContainingOneof(); od != nil { 169*1c12ee1eSDan Willemsen rs.recs = append(rs.recs, [2]string{"Oneof", string(od.Name())}) 170*1c12ee1eSDan Willemsen } 171*1c12ee1eSDan Willemsen case "ContainingMessage": 172*1c12ee1eSDan Willemsen if t.IsExtension() { 173*1c12ee1eSDan Willemsen rs.recs = append(rs.recs, [2]string{"Extendee", string(t.ContainingMessage().FullName())}) 174*1c12ee1eSDan Willemsen } 175*1c12ee1eSDan Willemsen case "Message": 176*1c12ee1eSDan Willemsen if !t.IsMap() { 177*1c12ee1eSDan Willemsen rs.Append(rv, s) 178*1c12ee1eSDan Willemsen } 179*1c12ee1eSDan Willemsen default: 180*1c12ee1eSDan Willemsen rs.Append(rv, s) 181*1c12ee1eSDan Willemsen } 182*1c12ee1eSDan Willemsen } 183*1c12ee1eSDan Willemsen case protoreflect.OneofDescriptor: 184*1c12ee1eSDan Willemsen var ss []string 185*1c12ee1eSDan Willemsen fs := t.Fields() 186*1c12ee1eSDan Willemsen for i := 0; i < fs.Len(); i++ { 187*1c12ee1eSDan Willemsen ss = append(ss, string(fs.Get(i).Name())) 188*1c12ee1eSDan Willemsen } 189*1c12ee1eSDan Willemsen if len(ss) > 0 { 190*1c12ee1eSDan Willemsen rs.recs = append(rs.recs, [2]string{"Fields", "[" + joinStrings(ss, false) + "]"}) 191*1c12ee1eSDan Willemsen } 192*1c12ee1eSDan Willemsen default: 193*1c12ee1eSDan Willemsen rs.Append(rv, descriptorAccessors[rt]...) 194*1c12ee1eSDan Willemsen } 195*1c12ee1eSDan Willemsen if rv.MethodByName("GoType").IsValid() { 196*1c12ee1eSDan Willemsen rs.Append(rv, "GoType") 197*1c12ee1eSDan Willemsen } 198*1c12ee1eSDan Willemsen } 199*1c12ee1eSDan Willemsen return start + rs.Join() + end 200*1c12ee1eSDan Willemsen} 201*1c12ee1eSDan Willemsen 202*1c12ee1eSDan Willemsentype records struct { 203*1c12ee1eSDan Willemsen recs [][2]string 204*1c12ee1eSDan Willemsen allowMulti bool 205*1c12ee1eSDan Willemsen} 206*1c12ee1eSDan Willemsen 207*1c12ee1eSDan Willemsenfunc (rs *records) Append(v reflect.Value, accessors ...string) { 208*1c12ee1eSDan Willemsen for _, a := range accessors { 209*1c12ee1eSDan Willemsen var rv reflect.Value 210*1c12ee1eSDan Willemsen if m := v.MethodByName(a); m.IsValid() { 211*1c12ee1eSDan Willemsen rv = m.Call(nil)[0] 212*1c12ee1eSDan Willemsen } 213*1c12ee1eSDan Willemsen if v.Kind() == reflect.Struct && !rv.IsValid() { 214*1c12ee1eSDan Willemsen rv = v.FieldByName(a) 215*1c12ee1eSDan Willemsen } 216*1c12ee1eSDan Willemsen if !rv.IsValid() { 217*1c12ee1eSDan Willemsen panic(fmt.Sprintf("unknown accessor: %v.%s", v.Type(), a)) 218*1c12ee1eSDan Willemsen } 219*1c12ee1eSDan Willemsen if _, ok := rv.Interface().(protoreflect.Value); ok { 220*1c12ee1eSDan Willemsen rv = rv.MethodByName("Interface").Call(nil)[0] 221*1c12ee1eSDan Willemsen if !rv.IsNil() { 222*1c12ee1eSDan Willemsen rv = rv.Elem() 223*1c12ee1eSDan Willemsen } 224*1c12ee1eSDan Willemsen } 225*1c12ee1eSDan Willemsen 226*1c12ee1eSDan Willemsen // Ignore zero values. 227*1c12ee1eSDan Willemsen var isZero bool 228*1c12ee1eSDan Willemsen switch rv.Kind() { 229*1c12ee1eSDan Willemsen case reflect.Interface, reflect.Slice: 230*1c12ee1eSDan Willemsen isZero = rv.IsNil() 231*1c12ee1eSDan Willemsen case reflect.Bool: 232*1c12ee1eSDan Willemsen isZero = rv.Bool() == false 233*1c12ee1eSDan Willemsen case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 234*1c12ee1eSDan Willemsen isZero = rv.Int() == 0 235*1c12ee1eSDan Willemsen case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 236*1c12ee1eSDan Willemsen isZero = rv.Uint() == 0 237*1c12ee1eSDan Willemsen case reflect.String: 238*1c12ee1eSDan Willemsen isZero = rv.String() == "" 239*1c12ee1eSDan Willemsen } 240*1c12ee1eSDan Willemsen if n, ok := rv.Interface().(list); ok { 241*1c12ee1eSDan Willemsen isZero = n.Len() == 0 242*1c12ee1eSDan Willemsen } 243*1c12ee1eSDan Willemsen if isZero { 244*1c12ee1eSDan Willemsen continue 245*1c12ee1eSDan Willemsen } 246*1c12ee1eSDan Willemsen 247*1c12ee1eSDan Willemsen // Format the value. 248*1c12ee1eSDan Willemsen var s string 249*1c12ee1eSDan Willemsen v := rv.Interface() 250*1c12ee1eSDan Willemsen switch v := v.(type) { 251*1c12ee1eSDan Willemsen case list: 252*1c12ee1eSDan Willemsen s = formatListOpt(v, false, rs.allowMulti) 253*1c12ee1eSDan Willemsen case protoreflect.FieldDescriptor, protoreflect.OneofDescriptor, protoreflect.EnumValueDescriptor, protoreflect.MethodDescriptor: 254*1c12ee1eSDan Willemsen s = string(v.(protoreflect.Descriptor).Name()) 255*1c12ee1eSDan Willemsen case protoreflect.Descriptor: 256*1c12ee1eSDan Willemsen s = string(v.FullName()) 257*1c12ee1eSDan Willemsen case string: 258*1c12ee1eSDan Willemsen s = strconv.Quote(v) 259*1c12ee1eSDan Willemsen case []byte: 260*1c12ee1eSDan Willemsen s = fmt.Sprintf("%q", v) 261*1c12ee1eSDan Willemsen default: 262*1c12ee1eSDan Willemsen s = fmt.Sprint(v) 263*1c12ee1eSDan Willemsen } 264*1c12ee1eSDan Willemsen rs.recs = append(rs.recs, [2]string{a, s}) 265*1c12ee1eSDan Willemsen } 266*1c12ee1eSDan Willemsen} 267*1c12ee1eSDan Willemsen 268*1c12ee1eSDan Willemsenfunc (rs *records) Join() string { 269*1c12ee1eSDan Willemsen var ss []string 270*1c12ee1eSDan Willemsen 271*1c12ee1eSDan Willemsen // In single line mode, simply join all records with commas. 272*1c12ee1eSDan Willemsen if !rs.allowMulti { 273*1c12ee1eSDan Willemsen for _, r := range rs.recs { 274*1c12ee1eSDan Willemsen ss = append(ss, r[0]+formatColon(0)+r[1]) 275*1c12ee1eSDan Willemsen } 276*1c12ee1eSDan Willemsen return joinStrings(ss, false) 277*1c12ee1eSDan Willemsen } 278*1c12ee1eSDan Willemsen 279*1c12ee1eSDan Willemsen // In allowMulti line mode, align single line records for more readable output. 280*1c12ee1eSDan Willemsen var maxLen int 281*1c12ee1eSDan Willemsen flush := func(i int) { 282*1c12ee1eSDan Willemsen for _, r := range rs.recs[len(ss):i] { 283*1c12ee1eSDan Willemsen ss = append(ss, r[0]+formatColon(maxLen-len(r[0]))+r[1]) 284*1c12ee1eSDan Willemsen } 285*1c12ee1eSDan Willemsen maxLen = 0 286*1c12ee1eSDan Willemsen } 287*1c12ee1eSDan Willemsen for i, r := range rs.recs { 288*1c12ee1eSDan Willemsen if isMulti := strings.Contains(r[1], "\n"); isMulti { 289*1c12ee1eSDan Willemsen flush(i) 290*1c12ee1eSDan Willemsen ss = append(ss, r[0]+formatColon(0)+strings.Join(strings.Split(r[1], "\n"), "\n\t")) 291*1c12ee1eSDan Willemsen } else if maxLen < len(r[0]) { 292*1c12ee1eSDan Willemsen maxLen = len(r[0]) 293*1c12ee1eSDan Willemsen } 294*1c12ee1eSDan Willemsen } 295*1c12ee1eSDan Willemsen flush(len(rs.recs)) 296*1c12ee1eSDan Willemsen return joinStrings(ss, true) 297*1c12ee1eSDan Willemsen} 298*1c12ee1eSDan Willemsen 299*1c12ee1eSDan Willemsenfunc formatColon(padding int) string { 300*1c12ee1eSDan Willemsen // Deliberately introduce instability into the debug output to 301*1c12ee1eSDan Willemsen // discourage users from performing string comparisons. 302*1c12ee1eSDan Willemsen // This provides us flexibility to change the output in the future. 303*1c12ee1eSDan Willemsen if detrand.Bool() { 304*1c12ee1eSDan Willemsen return ":" + strings.Repeat(" ", 1+padding) // use non-breaking spaces (U+00a0) 305*1c12ee1eSDan Willemsen } else { 306*1c12ee1eSDan Willemsen return ":" + strings.Repeat(" ", 1+padding) // use regular spaces (U+0020) 307*1c12ee1eSDan Willemsen } 308*1c12ee1eSDan Willemsen} 309*1c12ee1eSDan Willemsen 310*1c12ee1eSDan Willemsenfunc joinStrings(ss []string, isMulti bool) string { 311*1c12ee1eSDan Willemsen if len(ss) == 0 { 312*1c12ee1eSDan Willemsen return "" 313*1c12ee1eSDan Willemsen } 314*1c12ee1eSDan Willemsen if isMulti { 315*1c12ee1eSDan Willemsen return "\n\t" + strings.Join(ss, "\n\t") + "\n" 316*1c12ee1eSDan Willemsen } 317*1c12ee1eSDan Willemsen return strings.Join(ss, ", ") 318*1c12ee1eSDan Willemsen} 319