blob: 360c63329d4dc624ff6519b0300fa5fe249430bd [file] [log] [blame]
David K. Bainbridgebd6b2882021-08-26 13:31:02 +00001// 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"
17 pref "google.golang.org/protobuf/reflect/protoreflect"
18)
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) {
33 case pref.Names:
34 name = "Names"
35 case pref.FieldNumbers:
36 name = "FieldNumbers"
37 case pref.FieldRanges:
38 name = "FieldRanges"
39 case pref.EnumRanges:
40 name = "EnumRanges"
41 case pref.FileImports:
42 name = "FileImports"
43 case pref.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 pref.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 pref.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 pref.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 pref.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 pref.FileImports:
84 for i := 0; i < vs.Len(); i++ {
85 var rs records
86 rs.Append(reflect.ValueOf(vs.Get(i)), "Path", "Package", "IsPublic", "IsWeak")
87 ss = append(ss, "{"+rs.Join()+"}")
88 }
89 return start + joinStrings(ss, allowMulti) + end
90 default:
91 _, isEnumValue := vs.(pref.EnumValueDescriptors)
92 for i := 0; i < vs.Len(); i++ {
93 m := reflect.ValueOf(vs).MethodByName("Get")
94 v := m.Call([]reflect.Value{reflect.ValueOf(i)})[0].Interface()
95 ss = append(ss, formatDescOpt(v.(pref.Descriptor), false, allowMulti && !isEnumValue))
96 }
97 return start + joinStrings(ss, allowMulti && isEnumValue) + end
98 }
99}
100
101// descriptorAccessors is a list of accessors to print for each descriptor.
102//
103// Do not print all accessors since some contain redundant information,
104// while others are pointers that we do not want to follow since the descriptor
105// is actually a cyclic graph.
106//
107// Using a list allows us to print the accessors in a sensible order.
108var descriptorAccessors = map[reflect.Type][]string{
109 reflect.TypeOf((*pref.FileDescriptor)(nil)).Elem(): {"Path", "Package", "Imports", "Messages", "Enums", "Extensions", "Services"},
110 reflect.TypeOf((*pref.MessageDescriptor)(nil)).Elem(): {"IsMapEntry", "Fields", "Oneofs", "ReservedNames", "ReservedRanges", "RequiredNumbers", "ExtensionRanges", "Messages", "Enums", "Extensions"},
111 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"},
112 reflect.TypeOf((*pref.OneofDescriptor)(nil)).Elem(): {"Fields"}, // not directly used; must keep in sync with formatDescOpt
113 reflect.TypeOf((*pref.EnumDescriptor)(nil)).Elem(): {"Values", "ReservedNames", "ReservedRanges"},
114 reflect.TypeOf((*pref.EnumValueDescriptor)(nil)).Elem(): {"Number"},
115 reflect.TypeOf((*pref.ServiceDescriptor)(nil)).Elem(): {"Methods"},
116 reflect.TypeOf((*pref.MethodDescriptor)(nil)).Elem(): {"Input", "Output", "IsStreamingClient", "IsStreamingServer"},
117}
118
119func FormatDesc(s fmt.State, r rune, t pref.Descriptor) {
120 io.WriteString(s, formatDescOpt(t, true, r == 'v' && (s.Flag('+') || s.Flag('#'))))
121}
122func formatDescOpt(t pref.Descriptor, isRoot, allowMulti bool) string {
123 rv := reflect.ValueOf(t)
124 rt := rv.MethodByName("ProtoType").Type().In(0)
125
126 start, end := "{", "}"
127 if isRoot {
128 start = rt.Name() + "{"
129 }
130
131 _, isFile := t.(pref.FileDescriptor)
132 rs := records{allowMulti: allowMulti}
133 if t.IsPlaceholder() {
134 if isFile {
135 rs.Append(rv, "Path", "Package", "IsPlaceholder")
136 } else {
137 rs.Append(rv, "FullName", "IsPlaceholder")
138 }
139 } else {
140 switch {
141 case isFile:
142 rs.Append(rv, "Syntax")
143 case isRoot:
144 rs.Append(rv, "Syntax", "FullName")
145 default:
146 rs.Append(rv, "Name")
147 }
148 switch t := t.(type) {
149 case pref.FieldDescriptor:
150 for _, s := range descriptorAccessors[rt] {
151 switch s {
152 case "MapKey":
153 if k := t.MapKey(); k != nil {
154 rs.recs = append(rs.recs, [2]string{"MapKey", k.Kind().String()})
155 }
156 case "MapValue":
157 if v := t.MapValue(); v != nil {
158 switch v.Kind() {
159 case pref.EnumKind:
160 rs.recs = append(rs.recs, [2]string{"MapValue", string(v.Enum().FullName())})
161 case pref.MessageKind, pref.GroupKind:
162 rs.recs = append(rs.recs, [2]string{"MapValue", string(v.Message().FullName())})
163 default:
164 rs.recs = append(rs.recs, [2]string{"MapValue", v.Kind().String()})
165 }
166 }
167 case "ContainingOneof":
168 if od := t.ContainingOneof(); od != nil {
169 rs.recs = append(rs.recs, [2]string{"Oneof", string(od.Name())})
170 }
171 case "ContainingMessage":
172 if t.IsExtension() {
173 rs.recs = append(rs.recs, [2]string{"Extendee", string(t.ContainingMessage().FullName())})
174 }
175 case "Message":
176 if !t.IsMap() {
177 rs.Append(rv, s)
178 }
179 default:
180 rs.Append(rv, s)
181 }
182 }
183 case pref.OneofDescriptor:
184 var ss []string
185 fs := t.Fields()
186 for i := 0; i < fs.Len(); i++ {
187 ss = append(ss, string(fs.Get(i).Name()))
188 }
189 if len(ss) > 0 {
190 rs.recs = append(rs.recs, [2]string{"Fields", "[" + joinStrings(ss, false) + "]"})
191 }
192 default:
193 rs.Append(rv, descriptorAccessors[rt]...)
194 }
195 if rv.MethodByName("GoType").IsValid() {
196 rs.Append(rv, "GoType")
197 }
198 }
199 return start + rs.Join() + end
200}
201
202type records struct {
203 recs [][2]string
204 allowMulti bool
205}
206
207func (rs *records) Append(v reflect.Value, accessors ...string) {
208 for _, a := range accessors {
209 var rv reflect.Value
210 if m := v.MethodByName(a); m.IsValid() {
211 rv = m.Call(nil)[0]
212 }
213 if v.Kind() == reflect.Struct && !rv.IsValid() {
214 rv = v.FieldByName(a)
215 }
216 if !rv.IsValid() {
217 panic(fmt.Sprintf("unknown accessor: %v.%s", v.Type(), a))
218 }
219 if _, ok := rv.Interface().(pref.Value); ok {
220 rv = rv.MethodByName("Interface").Call(nil)[0]
221 if !rv.IsNil() {
222 rv = rv.Elem()
223 }
224 }
225
226 // Ignore zero values.
227 var isZero bool
228 switch rv.Kind() {
229 case reflect.Interface, reflect.Slice:
230 isZero = rv.IsNil()
231 case reflect.Bool:
232 isZero = rv.Bool() == false
233 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
234 isZero = rv.Int() == 0
235 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
236 isZero = rv.Uint() == 0
237 case reflect.String:
238 isZero = rv.String() == ""
239 }
240 if n, ok := rv.Interface().(list); ok {
241 isZero = n.Len() == 0
242 }
243 if isZero {
244 continue
245 }
246
247 // Format the value.
248 var s string
249 v := rv.Interface()
250 switch v := v.(type) {
251 case list:
252 s = formatListOpt(v, false, rs.allowMulti)
253 case pref.FieldDescriptor, pref.OneofDescriptor, pref.EnumValueDescriptor, pref.MethodDescriptor:
254 s = string(v.(pref.Descriptor).Name())
255 case pref.Descriptor:
256 s = string(v.FullName())
257 case string:
258 s = strconv.Quote(v)
259 case []byte:
260 s = fmt.Sprintf("%q", v)
261 default:
262 s = fmt.Sprint(v)
263 }
264 rs.recs = append(rs.recs, [2]string{a, s})
265 }
266}
267
268func (rs *records) Join() string {
269 var ss []string
270
271 // In single line mode, simply join all records with commas.
272 if !rs.allowMulti {
273 for _, r := range rs.recs {
274 ss = append(ss, r[0]+formatColon(0)+r[1])
275 }
276 return joinStrings(ss, false)
277 }
278
279 // In allowMulti line mode, align single line records for more readable output.
280 var maxLen int
281 flush := func(i int) {
282 for _, r := range rs.recs[len(ss):i] {
283 ss = append(ss, r[0]+formatColon(maxLen-len(r[0]))+r[1])
284 }
285 maxLen = 0
286 }
287 for i, r := range rs.recs {
288 if isMulti := strings.Contains(r[1], "\n"); isMulti {
289 flush(i)
290 ss = append(ss, r[0]+formatColon(0)+strings.Join(strings.Split(r[1], "\n"), "\n\t"))
291 } else if maxLen < len(r[0]) {
292 maxLen = len(r[0])
293 }
294 }
295 flush(len(rs.recs))
296 return joinStrings(ss, true)
297}
298
299func formatColon(padding int) string {
300 // Deliberately introduce instability into the debug output to
301 // discourage users from performing string comparisons.
302 // This provides us flexibility to change the output in the future.
303 if detrand.Bool() {
304 return ":" + strings.Repeat(" ", 1+padding) // use non-breaking spaces (U+00a0)
305 } else {
306 return ":" + strings.Repeat(" ", 1+padding) // use regular spaces (U+0020)
307 }
308}
309
310func joinStrings(ss []string, isMulti bool) string {
311 if len(ss) == 0 {
312 return ""
313 }
314 if isMulti {
315 return "\n\t" + strings.Join(ss, "\n\t") + "\n"
316 }
317 return strings.Join(ss, ", ")
318}