blob: 87e46bd4dfb96ccc6f001f6863cc5a7dd5f6b2a0 [file] [log] [blame]
khenaidoo7d3c5582021-08-11 18:09:44 -04001// 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.
6package descfmt
7
8import (
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"
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +053017 "google.golang.org/protobuf/reflect/protoreflect"
khenaidoo7d3c5582021-08-11 18:09:44 -040018)
19
20type list interface {
21 Len() int
22 pragma.DoNotImplement
23}
24
25func FormatList(s fmt.State, r rune, vs list) {
26 io.WriteString(s, formatListOpt(vs, true, r == 'v' && (s.Flag('+') || s.Flag('#'))))
27}
28func formatListOpt(vs list, isRoot, allowMulti bool) string {
29 start, end := "[", "]"
30 if isRoot {
31 var name string
32 switch vs.(type) {
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +053033 case protoreflect.Names:
khenaidoo7d3c5582021-08-11 18:09:44 -040034 name = "Names"
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +053035 case protoreflect.FieldNumbers:
khenaidoo7d3c5582021-08-11 18:09:44 -040036 name = "FieldNumbers"
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +053037 case protoreflect.FieldRanges:
khenaidoo7d3c5582021-08-11 18:09:44 -040038 name = "FieldRanges"
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +053039 case protoreflect.EnumRanges:
khenaidoo7d3c5582021-08-11 18:09:44 -040040 name = "EnumRanges"
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +053041 case protoreflect.FileImports:
khenaidoo7d3c5582021-08-11 18:09:44 -040042 name = "FileImports"
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +053043 case protoreflect.Descriptor:
khenaidoo7d3c5582021-08-11 18:09:44 -040044 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) {
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +053053 case protoreflect.Names:
khenaidoo7d3c5582021-08-11 18:09:44 -040054 for i := 0; i < vs.Len(); i++ {
55 ss = append(ss, fmt.Sprint(vs.Get(i)))
56 }
57 return start + joinStrings(ss, false) + end
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +053058 case protoreflect.FieldNumbers:
khenaidoo7d3c5582021-08-11 18:09:44 -040059 for i := 0; i < vs.Len(); i++ {
60 ss = append(ss, fmt.Sprint(vs.Get(i)))
61 }
62 return start + joinStrings(ss, false) + end
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +053063 case protoreflect.FieldRanges:
khenaidoo7d3c5582021-08-11 18:09:44 -040064 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
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +053073 case protoreflect.EnumRanges:
khenaidoo7d3c5582021-08-11 18:09:44 -040074 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
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +053083 case protoreflect.FileImports:
khenaidoo7d3c5582021-08-11 18:09:44 -040084 for i := 0; i < vs.Len(); i++ {
85 var rs records
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +053086 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 }...)
khenaidoo7d3c5582021-08-11 18:09:44 -040093 ss = append(ss, "{"+rs.Join()+"}")
94 }
95 return start + joinStrings(ss, allowMulti) + end
96 default:
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +053097 _, isEnumValue := vs.(protoreflect.EnumValueDescriptors)
khenaidoo7d3c5582021-08-11 18:09:44 -040098 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()
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530101 ss = append(ss, formatDescOpt(v.(protoreflect.Descriptor), false, allowMulti && !isEnumValue, nil))
khenaidoo7d3c5582021-08-11 18:09:44 -0400102 }
103 return start + joinStrings(ss, allowMulti && isEnumValue) + end
104 }
105}
106
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530107type methodAndName struct {
108 method reflect.Value
109 name string
khenaidoo7d3c5582021-08-11 18:09:44 -0400110}
111
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530112func FormatDesc(s fmt.State, r rune, t protoreflect.Descriptor) {
113 io.WriteString(s, formatDescOpt(t, true, r == 'v' && (s.Flag('+') || s.Flag('#')), nil))
khenaidoo7d3c5582021-08-11 18:09:44 -0400114}
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530115
116func InternalFormatDescOptForTesting(t protoreflect.Descriptor, isRoot, allowMulti bool, record func(string)) string {
117 return formatDescOpt(t, isRoot, allowMulti, record)
118}
119
120func formatDescOpt(t protoreflect.Descriptor, isRoot, allowMulti bool, record func(string)) string {
khenaidoo7d3c5582021-08-11 18:09:44 -0400121 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
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530129 _, isFile := t.(protoreflect.FileDescriptor)
130 rs := records{
131 allowMulti: allowMulti,
132 record: record,
133 }
khenaidoo7d3c5582021-08-11 18:09:44 -0400134 if t.IsPlaceholder() {
135 if isFile {
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530136 rs.Append(rv, []methodAndName{
137 {rv.MethodByName("Path"), "Path"},
138 {rv.MethodByName("Package"), "Package"},
139 {rv.MethodByName("IsPlaceholder"), "IsPlaceholder"},
140 }...)
khenaidoo7d3c5582021-08-11 18:09:44 -0400141 } else {
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530142 rs.Append(rv, []methodAndName{
143 {rv.MethodByName("FullName"), "FullName"},
144 {rv.MethodByName("IsPlaceholder"), "IsPlaceholder"},
145 }...)
khenaidoo7d3c5582021-08-11 18:09:44 -0400146 }
147 } else {
148 switch {
149 case isFile:
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530150 rs.Append(rv, methodAndName{rv.MethodByName("Syntax"), "Syntax"})
khenaidoo7d3c5582021-08-11 18:09:44 -0400151 case isRoot:
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530152 rs.Append(rv, []methodAndName{
153 {rv.MethodByName("Syntax"), "Syntax"},
154 {rv.MethodByName("FullName"), "FullName"},
155 }...)
khenaidoo7d3c5582021-08-11 18:09:44 -0400156 default:
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530157 rs.Append(rv, methodAndName{rv.MethodByName("Name"), "Name"})
khenaidoo7d3c5582021-08-11 18:09:44 -0400158 }
159 switch t := t.(type) {
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530160 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 {
khenaidoo7d3c5582021-08-11 18:09:44 -0400184 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() {
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530191 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())})
khenaidoo7d3c5582021-08-11 18:09:44 -0400195 default:
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530196 rs.AppendRecs("MapValue", [2]string{"MapValue", v.Kind().String()})
khenaidoo7d3c5582021-08-11 18:09:44 -0400197 }
198 }
199 case "ContainingOneof":
200 if od := t.ContainingOneof(); od != nil {
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530201 rs.AppendRecs("ContainingOneof", [2]string{"Oneof", string(od.Name())})
khenaidoo7d3c5582021-08-11 18:09:44 -0400202 }
203 case "ContainingMessage":
204 if t.IsExtension() {
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530205 rs.AppendRecs("ContainingMessage", [2]string{"Extendee", string(t.ContainingMessage().FullName())})
khenaidoo7d3c5582021-08-11 18:09:44 -0400206 }
207 case "Message":
208 if !t.IsMap() {
209 rs.Append(rv, s)
210 }
211 default:
212 rs.Append(rv, s)
213 }
214 }
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530215 case protoreflect.OneofDescriptor:
khenaidoo7d3c5582021-08-11 18:09:44 -0400216 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 {
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530222 rs.AppendRecs("Fields", [2]string{"Fields", "[" + joinStrings(ss, false) + "]"})
khenaidoo7d3c5582021-08-11 18:09:44 -0400223 }
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530224
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 }...)
khenaidoo7d3c5582021-08-11 18:09:44 -0400275 }
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530276 if m := rv.MethodByName("GoType"); m.IsValid() {
277 rs.Append(rv, methodAndName{m, "GoType"})
khenaidoo7d3c5582021-08-11 18:09:44 -0400278 }
279 }
280 return start + rs.Join() + end
281}
282
283type records struct {
284 recs [][2]string
285 allowMulti bool
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530286
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)
khenaidoo7d3c5582021-08-11 18:09:44 -0400291}
292
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530293func (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
300func (rs *records) Append(v reflect.Value, accessors ...methodAndName) {
khenaidoo7d3c5582021-08-11 18:09:44 -0400301 for _, a := range accessors {
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530302 if rs.record != nil {
303 rs.record(a.name)
304 }
khenaidoo7d3c5582021-08-11 18:09:44 -0400305 var rv reflect.Value
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530306 if a.method.IsValid() {
307 rv = a.method.Call(nil)[0]
khenaidoo7d3c5582021-08-11 18:09:44 -0400308 }
309 if v.Kind() == reflect.Struct && !rv.IsValid() {
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530310 rv = v.FieldByName(a.name)
khenaidoo7d3c5582021-08-11 18:09:44 -0400311 }
312 if !rv.IsValid() {
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530313 panic(fmt.Sprintf("unknown accessor: %v.%s", v.Type(), a.name))
khenaidoo7d3c5582021-08-11 18:09:44 -0400314 }
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530315 if _, ok := rv.Interface().(protoreflect.Value); ok {
khenaidoo7d3c5582021-08-11 18:09:44 -0400316 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)
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530349 case protoreflect.FieldDescriptor, protoreflect.OneofDescriptor, protoreflect.EnumValueDescriptor, protoreflect.MethodDescriptor:
350 s = string(v.(protoreflect.Descriptor).Name())
351 case protoreflect.Descriptor:
khenaidoo7d3c5582021-08-11 18:09:44 -0400352 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 }
Akash Reddy Kankanala92dfdf82025-03-23 22:07:09 +0530360 rs.recs = append(rs.recs, [2]string{a.name, s})
khenaidoo7d3c5582021-08-11 18:09:44 -0400361 }
362}
363
364func (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
395func 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
406func 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}