256
|
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" |
260
|
17 |
"google.golang.org/protobuf/reflect/protoreflect" |
256
|
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) { |
260
|
33 |
case protoreflect.Names: |
256
|
34 |
name = "Names" |
260
|
35 |
case protoreflect.FieldNumbers: |
256
|
36 |
name = "FieldNumbers" |
260
|
37 |
case protoreflect.FieldRanges: |
256
|
38 |
name = "FieldRanges" |
260
|
39 |
case protoreflect.EnumRanges: |
256
|
40 |
name = "EnumRanges" |
260
|
41 |
case protoreflect.FileImports: |
256
|
42 |
name = "FileImports" |
260
|
43 |
case protoreflect.Descriptor: |
256
|
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) { |
260
|
53 |
case protoreflect.Names: |
256
|
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 |
260
|
58 |
case protoreflect.FieldNumbers: |
256
|
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 |
260
|
63 |
case protoreflect.FieldRanges: |
256
|
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 |
260
|
73 |
case protoreflect.EnumRanges: |
256
|
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 |
260
|
83 |
case protoreflect.FileImports: |
256
|
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: |
260
|
91 |
_, isEnumValue := vs.(protoreflect.EnumValueDescriptors) |
256
|
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() |
260
|
95 |
ss = append(ss, formatDescOpt(v.(protoreflect.Descriptor), false, allowMulti && !isEnumValue)) |
256
|
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{ |
260
|
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"}, |
256
|
117 |
} |
|
118 |
|
260
|
119 |
func FormatDesc(s fmt.State, r rune, t protoreflect.Descriptor) { |
256
|
120 |
io.WriteString(s, formatDescOpt(t, true, r == 'v' && (s.Flag('+') || s.Flag('#')))) |
|
121 |
} |
260
|
122 |
func formatDescOpt(t protoreflect.Descriptor, isRoot, allowMulti bool) string { |
256
|
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 |
|
260
|
131 |
_, isFile := t.(protoreflect.FileDescriptor) |
256
|
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) { |
260
|
149 |
case protoreflect.FieldDescriptor: |
256
|
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() { |
260
|
159 |
case protoreflect.EnumKind: |
256
|
160 |
rs.recs = append(rs.recs, [2]string{"MapValue", string(v.Enum().FullName())}) |
260
|
161 |
case protoreflect.MessageKind, protoreflect.GroupKind: |
256
|
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 |
} |
260
|
183 |
case protoreflect.OneofDescriptor: |
256
|
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 |
} |
260
|
219 |
if _, ok := rv.Interface().(protoreflect.Value); ok { |
256
|
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) |
260
|
253 |
case protoreflect.FieldDescriptor, protoreflect.OneofDescriptor, protoreflect.EnumValueDescriptor, protoreflect.MethodDescriptor: |
|
254 |
s = string(v.(protoreflect.Descriptor).Name()) |
|
255 |
case protoreflect.Descriptor: |
256
|
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 |
} |