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 | 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.(protoreflect.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.(protoreflect.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.
|
---|
108 | var descriptorAccessors = map[reflect.Type][]string{
|
---|
109 | reflect.TypeOf((*protoreflect.FileDescriptor)(nil)).Elem(): {"Path", "Package", "Imports", "Messages", "Enums", "Extensions", "Services"},
|
---|
110 | reflect.TypeOf((*protoreflect.MessageDescriptor)(nil)).Elem(): {"IsMapEntry", "Fields", "Oneofs", "ReservedNames", "ReservedRanges", "RequiredNumbers", "ExtensionRanges", "Messages", "Enums", "Extensions"},
|
---|
111 | reflect.TypeOf((*protoreflect.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((*protoreflect.OneofDescriptor)(nil)).Elem(): {"Fields"}, // not directly used; must keep in sync with formatDescOpt
|
---|
113 | reflect.TypeOf((*protoreflect.EnumDescriptor)(nil)).Elem(): {"Values", "ReservedNames", "ReservedRanges"},
|
---|
114 | reflect.TypeOf((*protoreflect.EnumValueDescriptor)(nil)).Elem(): {"Number"},
|
---|
115 | reflect.TypeOf((*protoreflect.ServiceDescriptor)(nil)).Elem(): {"Methods"},
|
---|
116 | reflect.TypeOf((*protoreflect.MethodDescriptor)(nil)).Elem(): {"Input", "Output", "IsStreamingClient", "IsStreamingServer"},
|
---|
117 | }
|
---|
118 |
|
---|
119 | func FormatDesc(s fmt.State, r rune, t protoreflect.Descriptor) {
|
---|
120 | io.WriteString(s, formatDescOpt(t, true, r == 'v' && (s.Flag('+') || s.Flag('#'))))
|
---|
121 | }
|
---|
122 | func formatDescOpt(t protoreflect.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.(protoreflect.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 protoreflect.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 protoreflect.EnumKind:
|
---|
160 | rs.recs = append(rs.recs, [2]string{"MapValue", string(v.Enum().FullName())})
|
---|
161 | case protoreflect.MessageKind, protoreflect.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 protoreflect.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 |
|
---|
202 | type records struct {
|
---|
203 | recs [][2]string
|
---|
204 | allowMulti bool
|
---|
205 | }
|
---|
206 |
|
---|
207 | func (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().(protoreflect.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 protoreflect.FieldDescriptor, protoreflect.OneofDescriptor, protoreflect.EnumValueDescriptor, protoreflect.MethodDescriptor:
|
---|
254 | s = string(v.(protoreflect.Descriptor).Name())
|
---|
255 | case protoreflect.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 |
|
---|
268 | func (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 |
|
---|
299 | func 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 |
|
---|
310 | func 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 | }
|
---|