blob: 9547a5301a226e75fea0d3910e33b2bc10fe60a3 [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"
21 "google.golang.org/protobuf/internal/detrand"
Joe Tsaie0b77db2020-05-26 11:21:59 -070022 "google.golang.org/protobuf/internal/genid"
Joe Tsai2ce1ca92020-02-28 18:37:16 -080023 "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 Tsaib2f4e622020-05-27 16:28:31 -0700107 switch md.FullName() {
108 case genid.Any_message_fullname:
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800109 var msgVal protoreflect.Message
Joe Tsaib2f4e622020-05-27 16:28:31 -0700110 url := m.Get(fds.ByNumber(genid.Any_TypeUrl_field_number)).String()
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800111 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{})
Joe Tsaie0b77db2020-05-26 11:21:59 -0700115 v, ok := m2[string(genid.Any_Value_field_name)].(proto.Message)
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800116 if !ok {
117 return nil
118 }
119 msgVal = v.ProtoReflect()
120 } else {
Joe Tsaib2f4e622020-05-27 16:28:31 -0700121 val := m.Get(fds.ByNumber(genid.Any_Value_field_number)).Bytes()
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800122 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 Tsaib2f4e622020-05-27 16:28:31 -0700140 case genid.Timestamp_message_fullname:
141 secs := m.Get(fds.ByNumber(genid.Timestamp_Seconds_field_number)).Int()
142 nanos := m.Get(fds.ByNumber(genid.Timestamp_Nanos_field_number)).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 Tsaib2f4e622020-05-27 16:28:31 -0700153 case genid.Duration_message_fullname:
154 secs := m.Get(fds.ByNumber(genid.Duration_Seconds_field_number)).Int()
155 nanos := m.Get(fds.ByNumber(genid.Duration_Nanos_field_number)).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 Tsai69839c72020-05-29 01:20:58 -0700165 case genid.BoolValue_message_fullname,
166 genid.Int32Value_message_fullname,
167 genid.Int64Value_message_fullname,
168 genid.UInt32Value_message_fullname,
169 genid.UInt64Value_message_fullname,
170 genid.FloatValue_message_fullname,
171 genid.DoubleValue_message_fullname,
172 genid.StringValue_message_fullname,
173 genid.BytesValue_message_fullname:
Joe Tsaib2f4e622020-05-27 16:28:31 -0700174 fd := fds.ByNumber(genid.WrapperValue_Value_field_number)
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800175 return appendValue(b, m.Get(fd), fd)
176 }
177
178 return nil
179}
180
181func appendUnknown(b []byte, raw protoreflect.RawFields) []byte {
182 rs := make(map[protoreflect.FieldNumber][]protoreflect.RawFields)
183 for len(raw) > 0 {
184 num, _, n := protowire.ConsumeField(raw)
185 rs[num] = append(rs[num], raw[:n])
186 raw = raw[n:]
187 }
188
189 var ns []protoreflect.FieldNumber
190 for n := range rs {
191 ns = append(ns, n)
192 }
193 sort.Slice(ns, func(i, j int) bool { return ns[i] < ns[j] })
194
195 for _, n := range ns {
196 var leftBracket, rightBracket string
197 if len(rs[n]) > 1 {
198 leftBracket, rightBracket = "[", "]"
199 }
200
201 b = strconv.AppendInt(b, int64(n), 10)
202 b = append(b, ':')
203 b = append(b, leftBracket...)
204 for _, r := range rs[n] {
205 num, typ, n := protowire.ConsumeTag(r)
206 r = r[n:]
207 switch typ {
208 case protowire.VarintType:
209 v, _ := protowire.ConsumeVarint(r)
210 b = strconv.AppendInt(b, int64(v), 10)
211 case protowire.Fixed32Type:
212 v, _ := protowire.ConsumeFixed32(r)
213 b = append(b, fmt.Sprintf("0x%08x", v)...)
214 case protowire.Fixed64Type:
215 v, _ := protowire.ConsumeFixed64(r)
216 b = append(b, fmt.Sprintf("0x%016x", v)...)
217 case protowire.BytesType:
218 v, _ := protowire.ConsumeBytes(r)
219 b = strconv.AppendQuote(b, string(v))
220 case protowire.StartGroupType:
221 v, _ := protowire.ConsumeGroup(num, r)
222 b = append(b, '{')
223 b = appendUnknown(b, v)
224 b = bytes.TrimRight(b, delim())
225 b = append(b, '}')
226 default:
227 panic(fmt.Sprintf("invalid type: %v", typ))
228 }
229 b = append(b, delim()...)
230 }
231 b = bytes.TrimRight(b, delim())
232 b = append(b, rightBracket...)
233 b = append(b, delim()...)
234 }
235 return b
236}
237
238func appendList(b []byte, v protoreflect.List, fd protoreflect.FieldDescriptor) []byte {
239 b = append(b, '[')
240 for i := 0; i < v.Len(); i++ {
241 b = appendValue(b, v.Get(i), fd)
242 b = append(b, delim()...)
243 }
244 b = bytes.TrimRight(b, delim())
245 b = append(b, ']')
246 return b
247}
248
249func appendMap(b []byte, v protoreflect.Map, fd protoreflect.FieldDescriptor) []byte {
250 var ks []protoreflect.MapKey
251 mapsort.Range(v, fd.MapKey().Kind(), func(k protoreflect.MapKey, _ protoreflect.Value) bool {
252 ks = append(ks, k)
253 return true
254 })
255
256 b = append(b, '{')
257 for _, k := range ks {
258 b = appendValue(b, k.Value(), fd.MapKey())
259 b = append(b, ':')
260 b = appendValue(b, v.Get(k), fd.MapValue())
261 b = append(b, delim()...)
262 }
263 b = bytes.TrimRight(b, delim())
264 b = append(b, '}')
265 return b
266}
267
268func delim() string {
269 // Deliberately introduce instability into the message string to
270 // discourage users from depending on it.
271 if detrand.Bool() {
272 return " "
273 }
274 return ", "
275}