123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413 |
- // 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"
- "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 protoreflect.Names:
- name = "Names"
- case protoreflect.FieldNumbers:
- name = "FieldNumbers"
- case protoreflect.FieldRanges:
- name = "FieldRanges"
- case protoreflect.EnumRanges:
- name = "EnumRanges"
- case protoreflect.FileImports:
- name = "FileImports"
- case protoreflect.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 protoreflect.Names:
- for i := 0; i < vs.Len(); i++ {
- ss = append(ss, fmt.Sprint(vs.Get(i)))
- }
- return start + joinStrings(ss, false) + end
- case protoreflect.FieldNumbers:
- for i := 0; i < vs.Len(); i++ {
- ss = append(ss, fmt.Sprint(vs.Get(i)))
- }
- return start + joinStrings(ss, false) + end
- case protoreflect.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 protoreflect.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 protoreflect.FileImports:
- for i := 0; i < vs.Len(); i++ {
- var rs records
- rv := reflect.ValueOf(vs.Get(i))
- rs.Append(rv, []methodAndName{
- {rv.MethodByName("Path"), "Path"},
- {rv.MethodByName("Package"), "Package"},
- {rv.MethodByName("IsPublic"), "IsPublic"},
- {rv.MethodByName("IsWeak"), "IsWeak"},
- }...)
- ss = append(ss, "{"+rs.Join()+"}")
- }
- return start + joinStrings(ss, allowMulti) + end
- default:
- _, isEnumValue := vs.(protoreflect.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.(protoreflect.Descriptor), false, allowMulti && !isEnumValue, nil))
- }
- return start + joinStrings(ss, allowMulti && isEnumValue) + end
- }
- }
- type methodAndName struct {
- method reflect.Value
- name string
- }
- func FormatDesc(s fmt.State, r rune, t protoreflect.Descriptor) {
- io.WriteString(s, formatDescOpt(t, true, r == 'v' && (s.Flag('+') || s.Flag('#')), nil))
- }
- func InternalFormatDescOptForTesting(t protoreflect.Descriptor, isRoot, allowMulti bool, record func(string)) string {
- return formatDescOpt(t, isRoot, allowMulti, record)
- }
- func formatDescOpt(t protoreflect.Descriptor, isRoot, allowMulti bool, record func(string)) string {
- rv := reflect.ValueOf(t)
- rt := rv.MethodByName("ProtoType").Type().In(0)
- start, end := "{", "}"
- if isRoot {
- start = rt.Name() + "{"
- }
- _, isFile := t.(protoreflect.FileDescriptor)
- rs := records{
- allowMulti: allowMulti,
- record: record,
- }
- if t.IsPlaceholder() {
- if isFile {
- rs.Append(rv, []methodAndName{
- {rv.MethodByName("Path"), "Path"},
- {rv.MethodByName("Package"), "Package"},
- {rv.MethodByName("IsPlaceholder"), "IsPlaceholder"},
- }...)
- } else {
- rs.Append(rv, []methodAndName{
- {rv.MethodByName("FullName"), "FullName"},
- {rv.MethodByName("IsPlaceholder"), "IsPlaceholder"},
- }...)
- }
- } else {
- switch {
- case isFile:
- rs.Append(rv, methodAndName{rv.MethodByName("Syntax"), "Syntax"})
- case isRoot:
- rs.Append(rv, []methodAndName{
- {rv.MethodByName("Syntax"), "Syntax"},
- {rv.MethodByName("FullName"), "FullName"},
- }...)
- default:
- rs.Append(rv, methodAndName{rv.MethodByName("Name"), "Name"})
- }
- switch t := t.(type) {
- case protoreflect.FieldDescriptor:
- accessors := []methodAndName{
- {rv.MethodByName("Number"), "Number"},
- {rv.MethodByName("Cardinality"), "Cardinality"},
- {rv.MethodByName("Kind"), "Kind"},
- {rv.MethodByName("HasJSONName"), "HasJSONName"},
- {rv.MethodByName("JSONName"), "JSONName"},
- {rv.MethodByName("HasPresence"), "HasPresence"},
- {rv.MethodByName("IsExtension"), "IsExtension"},
- {rv.MethodByName("IsPacked"), "IsPacked"},
- {rv.MethodByName("IsWeak"), "IsWeak"},
- {rv.MethodByName("IsList"), "IsList"},
- {rv.MethodByName("IsMap"), "IsMap"},
- {rv.MethodByName("MapKey"), "MapKey"},
- {rv.MethodByName("MapValue"), "MapValue"},
- {rv.MethodByName("HasDefault"), "HasDefault"},
- {rv.MethodByName("Default"), "Default"},
- {rv.MethodByName("ContainingOneof"), "ContainingOneof"},
- {rv.MethodByName("ContainingMessage"), "ContainingMessage"},
- {rv.MethodByName("Message"), "Message"},
- {rv.MethodByName("Enum"), "Enum"},
- }
- for _, s := range accessors {
- switch s.name {
- 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 protoreflect.EnumKind:
- rs.AppendRecs("MapValue", [2]string{"MapValue", string(v.Enum().FullName())})
- case protoreflect.MessageKind, protoreflect.GroupKind:
- rs.AppendRecs("MapValue", [2]string{"MapValue", string(v.Message().FullName())})
- default:
- rs.AppendRecs("MapValue", [2]string{"MapValue", v.Kind().String()})
- }
- }
- case "ContainingOneof":
- if od := t.ContainingOneof(); od != nil {
- rs.AppendRecs("ContainingOneof", [2]string{"Oneof", string(od.Name())})
- }
- case "ContainingMessage":
- if t.IsExtension() {
- rs.AppendRecs("ContainingMessage", [2]string{"Extendee", string(t.ContainingMessage().FullName())})
- }
- case "Message":
- if !t.IsMap() {
- rs.Append(rv, s)
- }
- default:
- rs.Append(rv, s)
- }
- }
- case protoreflect.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.AppendRecs("Fields", [2]string{"Fields", "[" + joinStrings(ss, false) + "]"})
- }
- case protoreflect.FileDescriptor:
- rs.Append(rv, []methodAndName{
- {rv.MethodByName("Path"), "Path"},
- {rv.MethodByName("Package"), "Package"},
- {rv.MethodByName("Imports"), "Imports"},
- {rv.MethodByName("Messages"), "Messages"},
- {rv.MethodByName("Enums"), "Enums"},
- {rv.MethodByName("Extensions"), "Extensions"},
- {rv.MethodByName("Services"), "Services"},
- }...)
- case protoreflect.MessageDescriptor:
- rs.Append(rv, []methodAndName{
- {rv.MethodByName("IsMapEntry"), "IsMapEntry"},
- {rv.MethodByName("Fields"), "Fields"},
- {rv.MethodByName("Oneofs"), "Oneofs"},
- {rv.MethodByName("ReservedNames"), "ReservedNames"},
- {rv.MethodByName("ReservedRanges"), "ReservedRanges"},
- {rv.MethodByName("RequiredNumbers"), "RequiredNumbers"},
- {rv.MethodByName("ExtensionRanges"), "ExtensionRanges"},
- {rv.MethodByName("Messages"), "Messages"},
- {rv.MethodByName("Enums"), "Enums"},
- {rv.MethodByName("Extensions"), "Extensions"},
- }...)
- case protoreflect.EnumDescriptor:
- rs.Append(rv, []methodAndName{
- {rv.MethodByName("Values"), "Values"},
- {rv.MethodByName("ReservedNames"), "ReservedNames"},
- {rv.MethodByName("ReservedRanges"), "ReservedRanges"},
- }...)
- case protoreflect.EnumValueDescriptor:
- rs.Append(rv, []methodAndName{
- {rv.MethodByName("Number"), "Number"},
- }...)
- case protoreflect.ServiceDescriptor:
- rs.Append(rv, []methodAndName{
- {rv.MethodByName("Methods"), "Methods"},
- }...)
- case protoreflect.MethodDescriptor:
- rs.Append(rv, []methodAndName{
- {rv.MethodByName("Input"), "Input"},
- {rv.MethodByName("Output"), "Output"},
- {rv.MethodByName("IsStreamingClient"), "IsStreamingClient"},
- {rv.MethodByName("IsStreamingServer"), "IsStreamingServer"},
- }...)
- }
- if m := rv.MethodByName("GoType"); m.IsValid() {
- rs.Append(rv, methodAndName{m, "GoType"})
- }
- }
- return start + rs.Join() + end
- }
- type records struct {
- recs [][2]string
- allowMulti bool
- // record is a function that will be called for every Append() or
- // AppendRecs() call, to be used for testing with the
- // InternalFormatDescOptForTesting function.
- record func(string)
- }
- func (rs *records) AppendRecs(fieldName string, newRecs [2]string) {
- if rs.record != nil {
- rs.record(fieldName)
- }
- rs.recs = append(rs.recs, newRecs)
- }
- func (rs *records) Append(v reflect.Value, accessors ...methodAndName) {
- for _, a := range accessors {
- if rs.record != nil {
- rs.record(a.name)
- }
- var rv reflect.Value
- if a.method.IsValid() {
- rv = a.method.Call(nil)[0]
- }
- if v.Kind() == reflect.Struct && !rv.IsValid() {
- rv = v.FieldByName(a.name)
- }
- if !rv.IsValid() {
- panic(fmt.Sprintf("unknown accessor: %v.%s", v.Type(), a.name))
- }
- if _, ok := rv.Interface().(protoreflect.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 protoreflect.FieldDescriptor, protoreflect.OneofDescriptor, protoreflect.EnumValueDescriptor, protoreflect.MethodDescriptor:
- s = string(v.(protoreflect.Descriptor).Name())
- case protoreflect.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.name, 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, ", ")
- }
|