blob: c2c856fb3bca970388055d33584e4cd01dceb456 [file] [log] [blame]
Joe Tsai2ce1ca92020-02-28 18:37:16 -08001// Copyright 2019 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 msgfmt implements a text marshaler combining the desirable features
6// of both the JSON and proto text formats.
7// It is optimized for human readability and has no associated deserializer.
8package msgfmt
9
10import (
11 "bytes"
12 "fmt"
13 "math"
14 "reflect"
15 "sort"
16 "strconv"
17 "strings"
18 "time"
19
20 "google.golang.org/protobuf/encoding/protowire"
Joe Tsaid0377552020-03-24 11:46:34 -070021 "google.golang.org/protobuf/internal/detectknown"
Joe Tsai2ce1ca92020-02-28 18:37:16 -080022 "google.golang.org/protobuf/internal/detrand"
23 "google.golang.org/protobuf/internal/mapsort"
24 "google.golang.org/protobuf/proto"
25 "google.golang.org/protobuf/reflect/protoreflect"
26 "google.golang.org/protobuf/reflect/protoregistry"
27)
28
29// Format returns a formatted string for the message.
30func Format(m proto.Message) string {
31 return string(appendMessage(nil, m.ProtoReflect()))
32}
33
34func appendValue(b []byte, v protoreflect.Value, fd protoreflect.FieldDescriptor) []byte {
35 switch v := v.Interface().(type) {
36 case bool, int32, int64, uint32, uint64, float32, float64:
37 return append(b, fmt.Sprint(v)...)
38 case string:
39 return append(b, strconv.Quote(string(v))...)
40 case []byte:
41 return append(b, strconv.Quote(string(v))...)
42 case protoreflect.EnumNumber:
43 return appendEnum(b, v, fd.Enum())
44 case protoreflect.Message:
45 return appendMessage(b, v)
46 case protoreflect.List:
47 return appendList(b, v, fd)
48 case protoreflect.Map:
49 return appendMap(b, v, fd)
50 default:
51 panic(fmt.Sprintf("invalid type: %T", v))
52 }
53}
54
55func appendEnum(b []byte, v protoreflect.EnumNumber, ed protoreflect.EnumDescriptor) []byte {
56 if ev := ed.Values().ByNumber(v); ev != nil {
57 return append(b, ev.Name()...)
58 }
59 return strconv.AppendInt(b, int64(v), 10)
60}
61
62func appendMessage(b []byte, m protoreflect.Message) []byte {
63 if b2 := appendKnownMessage(b, m); b2 != nil {
64 return b2
65 }
66
67 var fds []protoreflect.FieldDescriptor
68 m.Range(func(fd protoreflect.FieldDescriptor, _ protoreflect.Value) bool {
69 fds = append(fds, fd)
70 return true
71 })
72 sort.Slice(fds, func(i, j int) bool {
73 fdi, fdj := fds[i], fds[j]
74 switch {
75 case !fdi.IsExtension() && !fdj.IsExtension():
76 return fdi.Index() < fdj.Index()
77 case fdi.IsExtension() && fdj.IsExtension():
78 return fdi.FullName() < fdj.FullName()
79 default:
80 return !fdi.IsExtension() && fdj.IsExtension()
81 }
82 })
83
84 b = append(b, '{')
85 for _, fd := range fds {
86 k := string(fd.Name())
87 if fd.IsExtension() {
88 k = string("[" + fd.FullName() + "]")
89 }
90
91 b = append(b, k...)
92 b = append(b, ':')
93 b = appendValue(b, m.Get(fd), fd)
94 b = append(b, delim()...)
95 }
96 b = appendUnknown(b, m.GetUnknown())
97 b = bytes.TrimRight(b, delim())
98 b = append(b, '}')
99 return b
100}
101
102var protocmpMessageType = reflect.TypeOf(map[string]interface{}(nil))
103
104func appendKnownMessage(b []byte, m protoreflect.Message) []byte {
105 md := m.Descriptor()
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800106 fds := md.Fields()
Joe Tsaid0377552020-03-24 11:46:34 -0700107 switch detectknown.Which(md.FullName()) {
108 case detectknown.AnyProto:
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800109 var msgVal protoreflect.Message
110 url := m.Get(fds.ByName("type_url")).String()
111 if v := reflect.ValueOf(m); v.Type().ConvertibleTo(protocmpMessageType) {
112 // For protocmp.Message, directly obtain the sub-message value
113 // which is stored in structured form, rather than as raw bytes.
114 m2 := v.Convert(protocmpMessageType).Interface().(map[string]interface{})
115 v, ok := m2["value"].(proto.Message)
116 if !ok {
117 return nil
118 }
119 msgVal = v.ProtoReflect()
120 } else {
121 val := m.Get(fds.ByName("value")).Bytes()
122 mt, err := protoregistry.GlobalTypes.FindMessageByURL(url)
123 if err != nil {
124 return nil
125 }
126 msgVal = mt.New()
127 err = proto.UnmarshalOptions{AllowPartial: true}.Unmarshal(val, msgVal.Interface())
128 if err != nil {
129 return nil
130 }
131 }
132
133 b = append(b, '{')
134 b = append(b, "["+url+"]"...)
135 b = append(b, ':')
136 b = appendMessage(b, msgVal)
137 b = append(b, '}')
138 return b
139
Joe Tsaid0377552020-03-24 11:46:34 -0700140 case detectknown.TimestampProto:
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800141 secs := m.Get(fds.ByName("seconds")).Int()
142 nanos := m.Get(fds.ByName("nanos")).Int()
Joe Tsaif8d77f82020-03-24 11:58:15 -0700143 if nanos < 0 || nanos >= 1e9 {
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800144 return nil
145 }
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800146 t := time.Unix(secs, nanos).UTC()
147 x := t.Format("2006-01-02T15:04:05.000000000") // RFC 3339
148 x = strings.TrimSuffix(x, "000")
149 x = strings.TrimSuffix(x, "000")
150 x = strings.TrimSuffix(x, ".000")
151 return append(b, x+"Z"...)
152
Joe Tsaid0377552020-03-24 11:46:34 -0700153 case detectknown.DurationProto:
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800154 secs := m.Get(fds.ByName("seconds")).Int()
155 nanos := m.Get(fds.ByName("nanos")).Int()
Joe Tsaif8d77f82020-03-24 11:58:15 -0700156 if nanos <= -1e9 || nanos >= 1e9 || (secs > 0 && nanos < 0) || (secs < 0 && nanos > 0) {
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800157 return nil
158 }
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800159 x := fmt.Sprintf("%d.%09d", secs, int64(math.Abs(float64(nanos))))
160 x = strings.TrimSuffix(x, "000")
161 x = strings.TrimSuffix(x, "000")
162 x = strings.TrimSuffix(x, ".000")
163 return append(b, x+"s"...)
164
Joe Tsaid0377552020-03-24 11:46:34 -0700165 case detectknown.WrappersProto:
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800166 fd := fds.ByName("value")
167 return appendValue(b, m.Get(fd), fd)
168 }
169
170 return nil
171}
172
173func appendUnknown(b []byte, raw protoreflect.RawFields) []byte {
174 rs := make(map[protoreflect.FieldNumber][]protoreflect.RawFields)
175 for len(raw) > 0 {
176 num, _, n := protowire.ConsumeField(raw)
177 rs[num] = append(rs[num], raw[:n])
178 raw = raw[n:]
179 }
180
181 var ns []protoreflect.FieldNumber
182 for n := range rs {
183 ns = append(ns, n)
184 }
185 sort.Slice(ns, func(i, j int) bool { return ns[i] < ns[j] })
186
187 for _, n := range ns {
188 var leftBracket, rightBracket string
189 if len(rs[n]) > 1 {
190 leftBracket, rightBracket = "[", "]"
191 }
192
193 b = strconv.AppendInt(b, int64(n), 10)
194 b = append(b, ':')
195 b = append(b, leftBracket...)
196 for _, r := range rs[n] {
197 num, typ, n := protowire.ConsumeTag(r)
198 r = r[n:]
199 switch typ {
200 case protowire.VarintType:
201 v, _ := protowire.ConsumeVarint(r)
202 b = strconv.AppendInt(b, int64(v), 10)
203 case protowire.Fixed32Type:
204 v, _ := protowire.ConsumeFixed32(r)
205 b = append(b, fmt.Sprintf("0x%08x", v)...)
206 case protowire.Fixed64Type:
207 v, _ := protowire.ConsumeFixed64(r)
208 b = append(b, fmt.Sprintf("0x%016x", v)...)
209 case protowire.BytesType:
210 v, _ := protowire.ConsumeBytes(r)
211 b = strconv.AppendQuote(b, string(v))
212 case protowire.StartGroupType:
213 v, _ := protowire.ConsumeGroup(num, r)
214 b = append(b, '{')
215 b = appendUnknown(b, v)
216 b = bytes.TrimRight(b, delim())
217 b = append(b, '}')
218 default:
219 panic(fmt.Sprintf("invalid type: %v", typ))
220 }
221 b = append(b, delim()...)
222 }
223 b = bytes.TrimRight(b, delim())
224 b = append(b, rightBracket...)
225 b = append(b, delim()...)
226 }
227 return b
228}
229
230func appendList(b []byte, v protoreflect.List, fd protoreflect.FieldDescriptor) []byte {
231 b = append(b, '[')
232 for i := 0; i < v.Len(); i++ {
233 b = appendValue(b, v.Get(i), fd)
234 b = append(b, delim()...)
235 }
236 b = bytes.TrimRight(b, delim())
237 b = append(b, ']')
238 return b
239}
240
241func appendMap(b []byte, v protoreflect.Map, fd protoreflect.FieldDescriptor) []byte {
242 var ks []protoreflect.MapKey
243 mapsort.Range(v, fd.MapKey().Kind(), func(k protoreflect.MapKey, _ protoreflect.Value) bool {
244 ks = append(ks, k)
245 return true
246 })
247
248 b = append(b, '{')
249 for _, k := range ks {
250 b = appendValue(b, k.Value(), fd.MapKey())
251 b = append(b, ':')
252 b = appendValue(b, v.Get(k), fd.MapValue())
253 b = append(b, delim()...)
254 }
255 b = bytes.TrimRight(b, delim())
256 b = append(b, '}')
257 return b
258}
259
260func delim() string {
261 // Deliberately introduce instability into the message string to
262 // discourage users from depending on it.
263 if detrand.Bool() {
264 return " "
265 }
266 return ", "
267}