...

Source file src/google.golang.org/protobuf/internal/descfmt/stringer.go

Documentation: google.golang.org/protobuf/internal/descfmt

     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  			rv := reflect.ValueOf(vs.Get(i))
    87  			rs.Append(rv, []methodAndName{
    88  				{rv.MethodByName("Path"), "Path"},
    89  				{rv.MethodByName("Package"), "Package"},
    90  				{rv.MethodByName("IsPublic"), "IsPublic"},
    91  				{rv.MethodByName("IsWeak"), "IsWeak"},
    92  			}...)
    93  			ss = append(ss, "{"+rs.Join()+"}")
    94  		}
    95  		return start + joinStrings(ss, allowMulti) + end
    96  	default:
    97  		_, isEnumValue := vs.(protoreflect.EnumValueDescriptors)
    98  		for i := 0; i < vs.Len(); i++ {
    99  			m := reflect.ValueOf(vs).MethodByName("Get")
   100  			v := m.Call([]reflect.Value{reflect.ValueOf(i)})[0].Interface()
   101  			ss = append(ss, formatDescOpt(v.(protoreflect.Descriptor), false, allowMulti && !isEnumValue, nil))
   102  		}
   103  		return start + joinStrings(ss, allowMulti && isEnumValue) + end
   104  	}
   105  }
   106  
   107  type methodAndName struct {
   108  	method reflect.Value
   109  	name   string
   110  }
   111  
   112  func FormatDesc(s fmt.State, r rune, t protoreflect.Descriptor) {
   113  	io.WriteString(s, formatDescOpt(t, true, r == 'v' && (s.Flag('+') || s.Flag('#')), nil))
   114  }
   115  
   116  func InternalFormatDescOptForTesting(t protoreflect.Descriptor, isRoot, allowMulti bool, record func(string)) string {
   117  	return formatDescOpt(t, isRoot, allowMulti, record)
   118  }
   119  
   120  func formatDescOpt(t protoreflect.Descriptor, isRoot, allowMulti bool, record func(string)) string {
   121  	rv := reflect.ValueOf(t)
   122  	rt := rv.MethodByName("ProtoType").Type().In(0)
   123  
   124  	start, end := "{", "}"
   125  	if isRoot {
   126  		start = rt.Name() + "{"
   127  	}
   128  
   129  	_, isFile := t.(protoreflect.FileDescriptor)
   130  	rs := records{
   131  		allowMulti: allowMulti,
   132  		record:     record,
   133  	}
   134  	if t.IsPlaceholder() {
   135  		if isFile {
   136  			rs.Append(rv, []methodAndName{
   137  				{rv.MethodByName("Path"), "Path"},
   138  				{rv.MethodByName("Package"), "Package"},
   139  				{rv.MethodByName("IsPlaceholder"), "IsPlaceholder"},
   140  			}...)
   141  		} else {
   142  			rs.Append(rv, []methodAndName{
   143  				{rv.MethodByName("FullName"), "FullName"},
   144  				{rv.MethodByName("IsPlaceholder"), "IsPlaceholder"},
   145  			}...)
   146  		}
   147  	} else {
   148  		switch {
   149  		case isFile:
   150  			rs.Append(rv, methodAndName{rv.MethodByName("Syntax"), "Syntax"})
   151  		case isRoot:
   152  			rs.Append(rv, []methodAndName{
   153  				{rv.MethodByName("Syntax"), "Syntax"},
   154  				{rv.MethodByName("FullName"), "FullName"},
   155  			}...)
   156  		default:
   157  			rs.Append(rv, methodAndName{rv.MethodByName("Name"), "Name"})
   158  		}
   159  		switch t := t.(type) {
   160  		case protoreflect.FieldDescriptor:
   161  			accessors := []methodAndName{
   162  				{rv.MethodByName("Number"), "Number"},
   163  				{rv.MethodByName("Cardinality"), "Cardinality"},
   164  				{rv.MethodByName("Kind"), "Kind"},
   165  				{rv.MethodByName("HasJSONName"), "HasJSONName"},
   166  				{rv.MethodByName("JSONName"), "JSONName"},
   167  				{rv.MethodByName("HasPresence"), "HasPresence"},
   168  				{rv.MethodByName("IsExtension"), "IsExtension"},
   169  				{rv.MethodByName("IsPacked"), "IsPacked"},
   170  				{rv.MethodByName("IsWeak"), "IsWeak"},
   171  				{rv.MethodByName("IsList"), "IsList"},
   172  				{rv.MethodByName("IsMap"), "IsMap"},
   173  				{rv.MethodByName("MapKey"), "MapKey"},
   174  				{rv.MethodByName("MapValue"), "MapValue"},
   175  				{rv.MethodByName("HasDefault"), "HasDefault"},
   176  				{rv.MethodByName("Default"), "Default"},
   177  				{rv.MethodByName("ContainingOneof"), "ContainingOneof"},
   178  				{rv.MethodByName("ContainingMessage"), "ContainingMessage"},
   179  				{rv.MethodByName("Message"), "Message"},
   180  				{rv.MethodByName("Enum"), "Enum"},
   181  			}
   182  			for _, s := range accessors {
   183  				switch s.name {
   184  				case "MapKey":
   185  					if k := t.MapKey(); k != nil {
   186  						rs.recs = append(rs.recs, [2]string{"MapKey", k.Kind().String()})
   187  					}
   188  				case "MapValue":
   189  					if v := t.MapValue(); v != nil {
   190  						switch v.Kind() {
   191  						case protoreflect.EnumKind:
   192  							rs.AppendRecs("MapValue", [2]string{"MapValue", string(v.Enum().FullName())})
   193  						case protoreflect.MessageKind, protoreflect.GroupKind:
   194  							rs.AppendRecs("MapValue", [2]string{"MapValue", string(v.Message().FullName())})
   195  						default:
   196  							rs.AppendRecs("MapValue", [2]string{"MapValue", v.Kind().String()})
   197  						}
   198  					}
   199  				case "ContainingOneof":
   200  					if od := t.ContainingOneof(); od != nil {
   201  						rs.AppendRecs("ContainingOneof", [2]string{"Oneof", string(od.Name())})
   202  					}
   203  				case "ContainingMessage":
   204  					if t.IsExtension() {
   205  						rs.AppendRecs("ContainingMessage", [2]string{"Extendee", string(t.ContainingMessage().FullName())})
   206  					}
   207  				case "Message":
   208  					if !t.IsMap() {
   209  						rs.Append(rv, s)
   210  					}
   211  				default:
   212  					rs.Append(rv, s)
   213  				}
   214  			}
   215  		case protoreflect.OneofDescriptor:
   216  			var ss []string
   217  			fs := t.Fields()
   218  			for i := 0; i < fs.Len(); i++ {
   219  				ss = append(ss, string(fs.Get(i).Name()))
   220  			}
   221  			if len(ss) > 0 {
   222  				rs.AppendRecs("Fields", [2]string{"Fields", "[" + joinStrings(ss, false) + "]"})
   223  			}
   224  
   225  		case protoreflect.FileDescriptor:
   226  			rs.Append(rv, []methodAndName{
   227  				{rv.MethodByName("Path"), "Path"},
   228  				{rv.MethodByName("Package"), "Package"},
   229  				{rv.MethodByName("Imports"), "Imports"},
   230  				{rv.MethodByName("Messages"), "Messages"},
   231  				{rv.MethodByName("Enums"), "Enums"},
   232  				{rv.MethodByName("Extensions"), "Extensions"},
   233  				{rv.MethodByName("Services"), "Services"},
   234  			}...)
   235  
   236  		case protoreflect.MessageDescriptor:
   237  			rs.Append(rv, []methodAndName{
   238  				{rv.MethodByName("IsMapEntry"), "IsMapEntry"},
   239  				{rv.MethodByName("Fields"), "Fields"},
   240  				{rv.MethodByName("Oneofs"), "Oneofs"},
   241  				{rv.MethodByName("ReservedNames"), "ReservedNames"},
   242  				{rv.MethodByName("ReservedRanges"), "ReservedRanges"},
   243  				{rv.MethodByName("RequiredNumbers"), "RequiredNumbers"},
   244  				{rv.MethodByName("ExtensionRanges"), "ExtensionRanges"},
   245  				{rv.MethodByName("Messages"), "Messages"},
   246  				{rv.MethodByName("Enums"), "Enums"},
   247  				{rv.MethodByName("Extensions"), "Extensions"},
   248  			}...)
   249  
   250  		case protoreflect.EnumDescriptor:
   251  			rs.Append(rv, []methodAndName{
   252  				{rv.MethodByName("Values"), "Values"},
   253  				{rv.MethodByName("ReservedNames"), "ReservedNames"},
   254  				{rv.MethodByName("ReservedRanges"), "ReservedRanges"},
   255  			}...)
   256  
   257  		case protoreflect.EnumValueDescriptor:
   258  			rs.Append(rv, []methodAndName{
   259  				{rv.MethodByName("Number"), "Number"},
   260  			}...)
   261  
   262  		case protoreflect.ServiceDescriptor:
   263  			rs.Append(rv, []methodAndName{
   264  				{rv.MethodByName("Methods"), "Methods"},
   265  			}...)
   266  
   267  		case protoreflect.MethodDescriptor:
   268  			rs.Append(rv, []methodAndName{
   269  				{rv.MethodByName("Input"), "Input"},
   270  				{rv.MethodByName("Output"), "Output"},
   271  				{rv.MethodByName("IsStreamingClient"), "IsStreamingClient"},
   272  				{rv.MethodByName("IsStreamingServer"), "IsStreamingServer"},
   273  			}...)
   274  		}
   275  		if m := rv.MethodByName("GoType"); m.IsValid() {
   276  			rs.Append(rv, methodAndName{m, "GoType"})
   277  		}
   278  	}
   279  	return start + rs.Join() + end
   280  }
   281  
   282  type records struct {
   283  	recs       [][2]string
   284  	allowMulti bool
   285  
   286  	// record is a function that will be called for every Append() or
   287  	// AppendRecs() call, to be used for testing with the
   288  	// InternalFormatDescOptForTesting function.
   289  	record func(string)
   290  }
   291  
   292  func (rs *records) AppendRecs(fieldName string, newRecs [2]string) {
   293  	if rs.record != nil {
   294  		rs.record(fieldName)
   295  	}
   296  	rs.recs = append(rs.recs, newRecs)
   297  }
   298  
   299  func (rs *records) Append(v reflect.Value, accessors ...methodAndName) {
   300  	for _, a := range accessors {
   301  		if rs.record != nil {
   302  			rs.record(a.name)
   303  		}
   304  		var rv reflect.Value
   305  		if a.method.IsValid() {
   306  			rv = a.method.Call(nil)[0]
   307  		}
   308  		if v.Kind() == reflect.Struct && !rv.IsValid() {
   309  			rv = v.FieldByName(a.name)
   310  		}
   311  		if !rv.IsValid() {
   312  			panic(fmt.Sprintf("unknown accessor: %v.%s", v.Type(), a.name))
   313  		}
   314  		if _, ok := rv.Interface().(protoreflect.Value); ok {
   315  			rv = rv.MethodByName("Interface").Call(nil)[0]
   316  			if !rv.IsNil() {
   317  				rv = rv.Elem()
   318  			}
   319  		}
   320  
   321  		// Ignore zero values.
   322  		var isZero bool
   323  		switch rv.Kind() {
   324  		case reflect.Interface, reflect.Slice:
   325  			isZero = rv.IsNil()
   326  		case reflect.Bool:
   327  			isZero = rv.Bool() == false
   328  		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   329  			isZero = rv.Int() == 0
   330  		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   331  			isZero = rv.Uint() == 0
   332  		case reflect.String:
   333  			isZero = rv.String() == ""
   334  		}
   335  		if n, ok := rv.Interface().(list); ok {
   336  			isZero = n.Len() == 0
   337  		}
   338  		if isZero {
   339  			continue
   340  		}
   341  
   342  		// Format the value.
   343  		var s string
   344  		v := rv.Interface()
   345  		switch v := v.(type) {
   346  		case list:
   347  			s = formatListOpt(v, false, rs.allowMulti)
   348  		case protoreflect.FieldDescriptor, protoreflect.OneofDescriptor, protoreflect.EnumValueDescriptor, protoreflect.MethodDescriptor:
   349  			s = string(v.(protoreflect.Descriptor).Name())
   350  		case protoreflect.Descriptor:
   351  			s = string(v.FullName())
   352  		case string:
   353  			s = strconv.Quote(v)
   354  		case []byte:
   355  			s = fmt.Sprintf("%q", v)
   356  		default:
   357  			s = fmt.Sprint(v)
   358  		}
   359  		rs.recs = append(rs.recs, [2]string{a.name, s})
   360  	}
   361  }
   362  
   363  func (rs *records) Join() string {
   364  	var ss []string
   365  
   366  	// In single line mode, simply join all records with commas.
   367  	if !rs.allowMulti {
   368  		for _, r := range rs.recs {
   369  			ss = append(ss, r[0]+formatColon(0)+r[1])
   370  		}
   371  		return joinStrings(ss, false)
   372  	}
   373  
   374  	// In allowMulti line mode, align single line records for more readable output.
   375  	var maxLen int
   376  	flush := func(i int) {
   377  		for _, r := range rs.recs[len(ss):i] {
   378  			ss = append(ss, r[0]+formatColon(maxLen-len(r[0]))+r[1])
   379  		}
   380  		maxLen = 0
   381  	}
   382  	for i, r := range rs.recs {
   383  		if isMulti := strings.Contains(r[1], "\n"); isMulti {
   384  			flush(i)
   385  			ss = append(ss, r[0]+formatColon(0)+strings.Join(strings.Split(r[1], "\n"), "\n\t"))
   386  		} else if maxLen < len(r[0]) {
   387  			maxLen = len(r[0])
   388  		}
   389  	}
   390  	flush(len(rs.recs))
   391  	return joinStrings(ss, true)
   392  }
   393  
   394  func formatColon(padding int) string {
   395  	// Deliberately introduce instability into the debug output to
   396  	// discourage users from performing string comparisons.
   397  	// This provides us flexibility to change the output in the future.
   398  	if detrand.Bool() {
   399  		return ":" + strings.Repeat(" ", 1+padding) // use non-breaking spaces (U+00a0)
   400  	} else {
   401  		return ":" + strings.Repeat(" ", 1+padding) // use regular spaces (U+0020)
   402  	}
   403  }
   404  
   405  func joinStrings(ss []string, isMulti bool) string {
   406  	if len(ss) == 0 {
   407  		return ""
   408  	}
   409  	if isMulti {
   410  		return "\n\t" + strings.Join(ss, "\n\t") + "\n"
   411  	}
   412  	return strings.Join(ss, ", ")
   413  }
   414  

View as plain text