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