|
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 pref "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 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. |
|
108 var 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 |
|
119 func FormatDesc(s fmt.State, r rune, t pref.Descriptor) { |
|
120 io.WriteString(s, formatDescOpt(t, true, r == 'v' && (s.Flag('+') || s.Flag('#')))) |
|
121 } |
|
122 func 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 |
|
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().(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 |
|
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 } |