123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318 |
- // Copyright 2018 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- // Package descfmt provides functionality to format descriptors.
- package descfmt
- import (
- "fmt"
- "io"
- "reflect"
- "strconv"
- "strings"
- "google.golang.org/protobuf/internal/detrand"
- "google.golang.org/protobuf/internal/pragma"
- pref "google.golang.org/protobuf/reflect/protoreflect"
- )
- type list interface {
- Len() int
- pragma.DoNotImplement
- }
- func FormatList(s fmt.State, r rune, vs list) {
- io.WriteString(s, formatListOpt(vs, true, r == 'v' && (s.Flag('+') || s.Flag('#'))))
- }
- func formatListOpt(vs list, isRoot, allowMulti bool) string {
- start, end := "[", "]"
- if isRoot {
- var name string
- switch vs.(type) {
- case pref.Names:
- name = "Names"
- case pref.FieldNumbers:
- name = "FieldNumbers"
- case pref.FieldRanges:
- name = "FieldRanges"
- case pref.EnumRanges:
- name = "EnumRanges"
- case pref.FileImports:
- name = "FileImports"
- case pref.Descriptor:
- name = reflect.ValueOf(vs).MethodByName("Get").Type().Out(0).Name() + "s"
- default:
- name = reflect.ValueOf(vs).Elem().Type().Name()
- }
- start, end = name+"{", "}"
- }
- var ss []string
- switch vs := vs.(type) {
- case pref.Names:
- for i := 0; i < vs.Len(); i++ {
- ss = append(ss, fmt.Sprint(vs.Get(i)))
- }
- return start + joinStrings(ss, false) + end
- case pref.FieldNumbers:
- for i := 0; i < vs.Len(); i++ {
- ss = append(ss, fmt.Sprint(vs.Get(i)))
- }
- return start + joinStrings(ss, false) + end
- case pref.FieldRanges:
- for i := 0; i < vs.Len(); i++ {
- r := vs.Get(i)
- if r[0]+1 == r[1] {
- ss = append(ss, fmt.Sprintf("%d", r[0]))
- } else {
- ss = append(ss, fmt.Sprintf("%d:%d", r[0], r[1])) // enum ranges are end exclusive
- }
- }
- return start + joinStrings(ss, false) + end
- case pref.EnumRanges:
- for i := 0; i < vs.Len(); i++ {
- r := vs.Get(i)
- if r[0] == r[1] {
- ss = append(ss, fmt.Sprintf("%d", r[0]))
- } else {
- ss = append(ss, fmt.Sprintf("%d:%d", r[0], int64(r[1])+1)) // enum ranges are end inclusive
- }
- }
- return start + joinStrings(ss, false) + end
- case pref.FileImports:
- for i := 0; i < vs.Len(); i++ {
- var rs records
- rs.Append(reflect.ValueOf(vs.Get(i)), "Path", "Package", "IsPublic", "IsWeak")
- ss = append(ss, "{"+rs.Join()+"}")
- }
- return start + joinStrings(ss, allowMulti) + end
- default:
- _, isEnumValue := vs.(pref.EnumValueDescriptors)
- for i := 0; i < vs.Len(); i++ {
- m := reflect.ValueOf(vs).MethodByName("Get")
- v := m.Call([]reflect.Value{reflect.ValueOf(i)})[0].Interface()
- ss = append(ss, formatDescOpt(v.(pref.Descriptor), false, allowMulti && !isEnumValue))
- }
- return start + joinStrings(ss, allowMulti && isEnumValue) + end
- }
- }
- // descriptorAccessors is a list of accessors to print for each descriptor.
- //
- // Do not print all accessors since some contain redundant information,
- // while others are pointers that we do not want to follow since the descriptor
- // is actually a cyclic graph.
- //
- // Using a list allows us to print the accessors in a sensible order.
- var descriptorAccessors = map[reflect.Type][]string{
- reflect.TypeOf((*pref.FileDescriptor)(nil)).Elem(): {"Path", "Package", "Imports", "Messages", "Enums", "Extensions", "Services"},
- reflect.TypeOf((*pref.MessageDescriptor)(nil)).Elem(): {"IsMapEntry", "Fields", "Oneofs", "ReservedNames", "ReservedRanges", "RequiredNumbers", "ExtensionRanges", "Messages", "Enums", "Extensions"},
- reflect.TypeOf((*pref.FieldDescriptor)(nil)).Elem(): {"Number", "Cardinality", "Kind", "HasJSONName", "JSONName", "HasPresence", "IsExtension", "IsPacked", "IsWeak", "IsList", "IsMap", "MapKey", "MapValue", "HasDefault", "Default", "ContainingOneof", "ContainingMessage", "Message", "Enum"},
- reflect.TypeOf((*pref.OneofDescriptor)(nil)).Elem(): {"Fields"}, // not directly used; must keep in sync with formatDescOpt
- reflect.TypeOf((*pref.EnumDescriptor)(nil)).Elem(): {"Values", "ReservedNames", "ReservedRanges"},
- reflect.TypeOf((*pref.EnumValueDescriptor)(nil)).Elem(): {"Number"},
- reflect.TypeOf((*pref.ServiceDescriptor)(nil)).Elem(): {"Methods"},
- reflect.TypeOf((*pref.MethodDescriptor)(nil)).Elem(): {"Input", "Output", "IsStreamingClient", "IsStreamingServer"},
- }
- func FormatDesc(s fmt.State, r rune, t pref.Descriptor) {
- io.WriteString(s, formatDescOpt(t, true, r == 'v' && (s.Flag('+') || s.Flag('#'))))
- }
- func formatDescOpt(t pref.Descriptor, isRoot, allowMulti bool) string {
- rv := reflect.ValueOf(t)
- rt := rv.MethodByName("ProtoType").Type().In(0)
- start, end := "{", "}"
- if isRoot {
- start = rt.Name() + "{"
- }
- _, isFile := t.(pref.FileDescriptor)
- rs := records{allowMulti: allowMulti}
- if t.IsPlaceholder() {
- if isFile {
- rs.Append(rv, "Path", "Package", "IsPlaceholder")
- } else {
- rs.Append(rv, "FullName", "IsPlaceholder")
- }
- } else {
- switch {
- case isFile:
- rs.Append(rv, "Syntax")
- case isRoot:
- rs.Append(rv, "Syntax", "FullName")
- default:
- rs.Append(rv, "Name")
- }
- switch t := t.(type) {
- case pref.FieldDescriptor:
- for _, s := range descriptorAccessors[rt] {
- switch s {
- case "MapKey":
- if k := t.MapKey(); k != nil {
- rs.recs = append(rs.recs, [2]string{"MapKey", k.Kind().String()})
- }
- case "MapValue":
- if v := t.MapValue(); v != nil {
- switch v.Kind() {
- case pref.EnumKind:
- rs.recs = append(rs.recs, [2]string{"MapValue", string(v.Enum().FullName())})
- case pref.MessageKind, pref.GroupKind:
- rs.recs = append(rs.recs, [2]string{"MapValue", string(v.Message().FullName())})
- default:
- rs.recs = append(rs.recs, [2]string{"MapValue", v.Kind().String()})
- }
- }
- case "ContainingOneof":
- if od := t.ContainingOneof(); od != nil {
- rs.recs = append(rs.recs, [2]string{"Oneof", string(od.Name())})
- }
- case "ContainingMessage":
- if t.IsExtension() {
- rs.recs = append(rs.recs, [2]string{"Extendee", string(t.ContainingMessage().FullName())})
- }
- case "Message":
- if !t.IsMap() {
- rs.Append(rv, s)
- }
- default:
- rs.Append(rv, s)
- }
- }
- case pref.OneofDescriptor:
- var ss []string
- fs := t.Fields()
- for i := 0; i < fs.Len(); i++ {
- ss = append(ss, string(fs.Get(i).Name()))
- }
- if len(ss) > 0 {
- rs.recs = append(rs.recs, [2]string{"Fields", "[" + joinStrings(ss, false) + "]"})
- }
- default:
- rs.Append(rv, descriptorAccessors[rt]...)
- }
- if rv.MethodByName("GoType").IsValid() {
- rs.Append(rv, "GoType")
- }
- }
- return start + rs.Join() + end
- }
- type records struct {
- recs [][2]string
- allowMulti bool
- }
- func (rs *records) Append(v reflect.Value, accessors ...string) {
- for _, a := range accessors {
- var rv reflect.Value
- if m := v.MethodByName(a); m.IsValid() {
- rv = m.Call(nil)[0]
- }
- if v.Kind() == reflect.Struct && !rv.IsValid() {
- rv = v.FieldByName(a)
- }
- if !rv.IsValid() {
- panic(fmt.Sprintf("unknown accessor: %v.%s", v.Type(), a))
- }
- if _, ok := rv.Interface().(pref.Value); ok {
- rv = rv.MethodByName("Interface").Call(nil)[0]
- if !rv.IsNil() {
- rv = rv.Elem()
- }
- }
- // Ignore zero values.
- var isZero bool
- switch rv.Kind() {
- case reflect.Interface, reflect.Slice:
- isZero = rv.IsNil()
- case reflect.Bool:
- isZero = rv.Bool() == false
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- isZero = rv.Int() == 0
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- isZero = rv.Uint() == 0
- case reflect.String:
- isZero = rv.String() == ""
- }
- if n, ok := rv.Interface().(list); ok {
- isZero = n.Len() == 0
- }
- if isZero {
- continue
- }
- // Format the value.
- var s string
- v := rv.Interface()
- switch v := v.(type) {
- case list:
- s = formatListOpt(v, false, rs.allowMulti)
- case pref.FieldDescriptor, pref.OneofDescriptor, pref.EnumValueDescriptor, pref.MethodDescriptor:
- s = string(v.(pref.Descriptor).Name())
- case pref.Descriptor:
- s = string(v.FullName())
- case string:
- s = strconv.Quote(v)
- case []byte:
- s = fmt.Sprintf("%q", v)
- default:
- s = fmt.Sprint(v)
- }
- rs.recs = append(rs.recs, [2]string{a, s})
- }
- }
- func (rs *records) Join() string {
- var ss []string
- // In single line mode, simply join all records with commas.
- if !rs.allowMulti {
- for _, r := range rs.recs {
- ss = append(ss, r[0]+formatColon(0)+r[1])
- }
- return joinStrings(ss, false)
- }
- // In allowMulti line mode, align single line records for more readable output.
- var maxLen int
- flush := func(i int) {
- for _, r := range rs.recs[len(ss):i] {
- ss = append(ss, r[0]+formatColon(maxLen-len(r[0]))+r[1])
- }
- maxLen = 0
- }
- for i, r := range rs.recs {
- if isMulti := strings.Contains(r[1], "\n"); isMulti {
- flush(i)
- ss = append(ss, r[0]+formatColon(0)+strings.Join(strings.Split(r[1], "\n"), "\n\t"))
- } else if maxLen < len(r[0]) {
- maxLen = len(r[0])
- }
- }
- flush(len(rs.recs))
- return joinStrings(ss, true)
- }
- func formatColon(padding int) string {
- // Deliberately introduce instability into the debug output to
- // discourage users from performing string comparisons.
- // This provides us flexibility to change the output in the future.
- if detrand.Bool() {
- return ":" + strings.Repeat(" ", 1+padding) // use non-breaking spaces (U+00a0)
- } else {
- return ":" + strings.Repeat(" ", 1+padding) // use regular spaces (U+0020)
- }
- }
- func joinStrings(ss []string, isMulti bool) string {
- if len(ss) == 0 {
- return ""
- }
- if isMulti {
- return "\n\t" + strings.Join(ss, "\n\t") + "\n"
- }
- return strings.Join(ss, ", ")
- }
|