stringer.go raw

   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  				{rv.MethodByName("IsClosed"), "IsClosed"},
 256  			}...)
 257  
 258  		case protoreflect.EnumValueDescriptor:
 259  			rs.Append(rv, []methodAndName{
 260  				{rv.MethodByName("Number"), "Number"},
 261  			}...)
 262  
 263  		case protoreflect.ServiceDescriptor:
 264  			rs.Append(rv, []methodAndName{
 265  				{rv.MethodByName("Methods"), "Methods"},
 266  			}...)
 267  
 268  		case protoreflect.MethodDescriptor:
 269  			rs.Append(rv, []methodAndName{
 270  				{rv.MethodByName("Input"), "Input"},
 271  				{rv.MethodByName("Output"), "Output"},
 272  				{rv.MethodByName("IsStreamingClient"), "IsStreamingClient"},
 273  				{rv.MethodByName("IsStreamingServer"), "IsStreamingServer"},
 274  			}...)
 275  		}
 276  		if m := rv.MethodByName("GoType"); m.IsValid() {
 277  			rs.Append(rv, methodAndName{m, "GoType"})
 278  		}
 279  	}
 280  	return start + rs.Join() + end
 281  }
 282  
 283  type records struct {
 284  	recs       [][2]string
 285  	allowMulti bool
 286  
 287  	// record is a function that will be called for every Append() or
 288  	// AppendRecs() call, to be used for testing with the
 289  	// InternalFormatDescOptForTesting function.
 290  	record func(string)
 291  }
 292  
 293  func (rs *records) AppendRecs(fieldName string, newRecs [2]string) {
 294  	if rs.record != nil {
 295  		rs.record(fieldName)
 296  	}
 297  	rs.recs = append(rs.recs, newRecs)
 298  }
 299  
 300  func (rs *records) Append(v reflect.Value, accessors ...methodAndName) {
 301  	for _, a := range accessors {
 302  		if rs.record != nil {
 303  			rs.record(a.name)
 304  		}
 305  		var rv reflect.Value
 306  		if a.method.IsValid() {
 307  			rv = a.method.Call(nil)[0]
 308  		}
 309  		if v.Kind() == reflect.Struct && !rv.IsValid() {
 310  			rv = v.FieldByName(a.name)
 311  		}
 312  		if !rv.IsValid() {
 313  			panic(fmt.Sprintf("unknown accessor: %v.%s", v.Type(), a.name))
 314  		}
 315  		if _, ok := rv.Interface().(protoreflect.Value); ok {
 316  			rv = rv.MethodByName("Interface").Call(nil)[0]
 317  			if !rv.IsNil() {
 318  				rv = rv.Elem()
 319  			}
 320  		}
 321  
 322  		// Ignore zero values.
 323  		var isZero bool
 324  		switch rv.Kind() {
 325  		case reflect.Interface, reflect.Slice:
 326  			isZero = rv.IsNil()
 327  		case reflect.Bool:
 328  			isZero = rv.Bool() == false
 329  		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 330  			isZero = rv.Int() == 0
 331  		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
 332  			isZero = rv.Uint() == 0
 333  		case reflect.String:
 334  			isZero = rv.String() == ""
 335  		}
 336  		if n, ok := rv.Interface().(list); ok {
 337  			isZero = n.Len() == 0
 338  		}
 339  		if isZero {
 340  			continue
 341  		}
 342  
 343  		// Format the value.
 344  		var s string
 345  		v := rv.Interface()
 346  		switch v := v.(type) {
 347  		case list:
 348  			s = formatListOpt(v, false, rs.allowMulti)
 349  		case protoreflect.FieldDescriptor, protoreflect.OneofDescriptor, protoreflect.EnumValueDescriptor, protoreflect.MethodDescriptor:
 350  			s = string(v.(protoreflect.Descriptor).Name())
 351  		case protoreflect.Descriptor:
 352  			s = string(v.FullName())
 353  		case string:
 354  			s = strconv.Quote(v)
 355  		case []byte:
 356  			s = fmt.Sprintf("%q", v)
 357  		default:
 358  			s = fmt.Sprint(v)
 359  		}
 360  		rs.recs = append(rs.recs, [2]string{a.name, s})
 361  	}
 362  }
 363  
 364  func (rs *records) Join() string {
 365  	var ss []string
 366  
 367  	// In single line mode, simply join all records with commas.
 368  	if !rs.allowMulti {
 369  		for _, r := range rs.recs {
 370  			ss = append(ss, r[0]+formatColon(0)+r[1])
 371  		}
 372  		return joinStrings(ss, false)
 373  	}
 374  
 375  	// In allowMulti line mode, align single line records for more readable output.
 376  	var maxLen int
 377  	flush := func(i int) {
 378  		for _, r := range rs.recs[len(ss):i] {
 379  			ss = append(ss, r[0]+formatColon(maxLen-len(r[0]))+r[1])
 380  		}
 381  		maxLen = 0
 382  	}
 383  	for i, r := range rs.recs {
 384  		if isMulti := strings.Contains(r[1], "\n"); isMulti {
 385  			flush(i)
 386  			ss = append(ss, r[0]+formatColon(0)+strings.Join(strings.Split(r[1], "\n"), "\n\t"))
 387  		} else if maxLen < len(r[0]) {
 388  			maxLen = len(r[0])
 389  		}
 390  	}
 391  	flush(len(rs.recs))
 392  	return joinStrings(ss, true)
 393  }
 394  
 395  func formatColon(padding int) string {
 396  	// Deliberately introduce instability into the debug output to
 397  	// discourage users from performing string comparisons.
 398  	// This provides us flexibility to change the output in the future.
 399  	if detrand.Bool() {
 400  		return ":" + strings.Repeat(" ", 1+padding) // use non-breaking spaces (U+00a0)
 401  	} else {
 402  		return ":" + strings.Repeat(" ", 1+padding) // use regular spaces (U+0020)
 403  	}
 404  }
 405  
 406  func joinStrings(ss []string, isMulti bool) string {
 407  	if len(ss) == 0 {
 408  		return ""
 409  	}
 410  	if isMulti {
 411  		return "\n\t" + strings.Join(ss, "\n\t") + "\n"
 412  	}
 413  	return strings.Join(ss, ", ")
 414  }
 415