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