blob: 79fe5aef4bf26d5c700dc75300ecc8fc3a06e02f [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"
22 "google.golang.org/protobuf/internal/mapsort"
23 "google.golang.org/protobuf/proto"
24 "google.golang.org/protobuf/reflect/protoreflect"
25 "google.golang.org/protobuf/reflect/protoregistry"
26)
27
28// Format returns a formatted string for the message.
29func Format(m proto.Message) string {
30 return string(appendMessage(nil, m.ProtoReflect()))
31}
32
33func appendValue(b []byte, v protoreflect.Value, fd protoreflect.FieldDescriptor) []byte {
34 switch v := v.Interface().(type) {
35 case bool, int32, int64, uint32, uint64, float32, float64:
36 return append(b, fmt.Sprint(v)...)
37 case string:
38 return append(b, strconv.Quote(string(v))...)
39 case []byte:
40 return append(b, strconv.Quote(string(v))...)
41 case protoreflect.EnumNumber:
42 return appendEnum(b, v, fd.Enum())
43 case protoreflect.Message:
44 return appendMessage(b, v)
45 case protoreflect.List:
46 return appendList(b, v, fd)
47 case protoreflect.Map:
48 return appendMap(b, v, fd)
49 default:
50 panic(fmt.Sprintf("invalid type: %T", v))
51 }
52}
53
54func appendEnum(b []byte, v protoreflect.EnumNumber, ed protoreflect.EnumDescriptor) []byte {
55 if ev := ed.Values().ByNumber(v); ev != nil {
56 return append(b, ev.Name()...)
57 }
58 return strconv.AppendInt(b, int64(v), 10)
59}
60
61func appendMessage(b []byte, m protoreflect.Message) []byte {
62 if b2 := appendKnownMessage(b, m); b2 != nil {
63 return b2
64 }
65
66 var fds []protoreflect.FieldDescriptor
67 m.Range(func(fd protoreflect.FieldDescriptor, _ protoreflect.Value) bool {
68 fds = append(fds, fd)
69 return true
70 })
71 sort.Slice(fds, func(i, j int) bool {
72 fdi, fdj := fds[i], fds[j]
73 switch {
74 case !fdi.IsExtension() && !fdj.IsExtension():
75 return fdi.Index() < fdj.Index()
76 case fdi.IsExtension() && fdj.IsExtension():
77 return fdi.FullName() < fdj.FullName()
78 default:
79 return !fdi.IsExtension() && fdj.IsExtension()
80 }
81 })
82
83 b = append(b, '{')
84 for _, fd := range fds {
85 k := string(fd.Name())
86 if fd.IsExtension() {
87 k = string("[" + fd.FullName() + "]")
88 }
89
90 b = append(b, k...)
91 b = append(b, ':')
92 b = appendValue(b, m.Get(fd), fd)
93 b = append(b, delim()...)
94 }
95 b = appendUnknown(b, m.GetUnknown())
96 b = bytes.TrimRight(b, delim())
97 b = append(b, '}')
98 return b
99}
100
101var protocmpMessageType = reflect.TypeOf(map[string]interface{}(nil))
102
103func appendKnownMessage(b []byte, m protoreflect.Message) []byte {
104 md := m.Descriptor()
105 if md.FullName().Parent() != "google.protobuf" {
106 return nil
107 }
108
109 fds := md.Fields()
110 switch md.Name() {
111 case "Any":
112 var msgVal protoreflect.Message
113 url := m.Get(fds.ByName("type_url")).String()
114 if v := reflect.ValueOf(m); v.Type().ConvertibleTo(protocmpMessageType) {
115 // For protocmp.Message, directly obtain the sub-message value
116 // which is stored in structured form, rather than as raw bytes.
117 m2 := v.Convert(protocmpMessageType).Interface().(map[string]interface{})
118 v, ok := m2["value"].(proto.Message)
119 if !ok {
120 return nil
121 }
122 msgVal = v.ProtoReflect()
123 } else {
124 val := m.Get(fds.ByName("value")).Bytes()
125 mt, err := protoregistry.GlobalTypes.FindMessageByURL(url)
126 if err != nil {
127 return nil
128 }
129 msgVal = mt.New()
130 err = proto.UnmarshalOptions{AllowPartial: true}.Unmarshal(val, msgVal.Interface())
131 if err != nil {
132 return nil
133 }
134 }
135
136 b = append(b, '{')
137 b = append(b, "["+url+"]"...)
138 b = append(b, ':')
139 b = appendMessage(b, msgVal)
140 b = append(b, '}')
141 return b
142
143 case "Timestamp":
144 const minTimestamp = -62135596800 // Seconds between 1970-01-01T00:00:00Z and 0001-01-01T00:00:00Z, inclusive
145 const maxTimestamp = +253402300799 // Seconds between 1970-01-01T00:00:00Z and 9999-12-31T23:59:59Z, inclusive
146 secs := m.Get(fds.ByName("seconds")).Int()
147 nanos := m.Get(fds.ByName("nanos")).Int()
148 switch {
149 case secs < minTimestamp || secs > maxTimestamp:
150 return nil
151 case nanos < 0 || nanos >= 1e9:
152 return nil
153 }
154
155 t := time.Unix(secs, nanos).UTC()
156 x := t.Format("2006-01-02T15:04:05.000000000") // RFC 3339
157 x = strings.TrimSuffix(x, "000")
158 x = strings.TrimSuffix(x, "000")
159 x = strings.TrimSuffix(x, ".000")
160 return append(b, x+"Z"...)
161
162 case "Duration":
163 const absDuration = 315576000000 // 10000yr * 365.25day/yr * 24hr/day * 60min/hr * 60sec/min
164 secs := m.Get(fds.ByName("seconds")).Int()
165 nanos := m.Get(fds.ByName("nanos")).Int()
166 switch {
167 case secs < -absDuration || secs > +absDuration:
168 return nil
169 case nanos <= -1e9 || nanos >= 1e9:
170 return nil
171 case (secs > 0 && nanos < 0) || (secs < 0 && nanos > 0):
172 return nil
173 }
174
175 x := fmt.Sprintf("%d.%09d", secs, int64(math.Abs(float64(nanos))))
176 x = strings.TrimSuffix(x, "000")
177 x = strings.TrimSuffix(x, "000")
178 x = strings.TrimSuffix(x, ".000")
179 return append(b, x+"s"...)
180
181 case "BoolValue", "Int32Value", "Int64Value", "UInt32Value", "UInt64Value", "FloatValue", "DoubleValue", "StringValue", "BytesValue":
182 fd := fds.ByName("value")
183 return appendValue(b, m.Get(fd), fd)
184 }
185
186 return nil
187}
188
189func appendUnknown(b []byte, raw protoreflect.RawFields) []byte {
190 rs := make(map[protoreflect.FieldNumber][]protoreflect.RawFields)
191 for len(raw) > 0 {
192 num, _, n := protowire.ConsumeField(raw)
193 rs[num] = append(rs[num], raw[:n])
194 raw = raw[n:]
195 }
196
197 var ns []protoreflect.FieldNumber
198 for n := range rs {
199 ns = append(ns, n)
200 }
201 sort.Slice(ns, func(i, j int) bool { return ns[i] < ns[j] })
202
203 for _, n := range ns {
204 var leftBracket, rightBracket string
205 if len(rs[n]) > 1 {
206 leftBracket, rightBracket = "[", "]"
207 }
208
209 b = strconv.AppendInt(b, int64(n), 10)
210 b = append(b, ':')
211 b = append(b, leftBracket...)
212 for _, r := range rs[n] {
213 num, typ, n := protowire.ConsumeTag(r)
214 r = r[n:]
215 switch typ {
216 case protowire.VarintType:
217 v, _ := protowire.ConsumeVarint(r)
218 b = strconv.AppendInt(b, int64(v), 10)
219 case protowire.Fixed32Type:
220 v, _ := protowire.ConsumeFixed32(r)
221 b = append(b, fmt.Sprintf("0x%08x", v)...)
222 case protowire.Fixed64Type:
223 v, _ := protowire.ConsumeFixed64(r)
224 b = append(b, fmt.Sprintf("0x%016x", v)...)
225 case protowire.BytesType:
226 v, _ := protowire.ConsumeBytes(r)
227 b = strconv.AppendQuote(b, string(v))
228 case protowire.StartGroupType:
229 v, _ := protowire.ConsumeGroup(num, r)
230 b = append(b, '{')
231 b = appendUnknown(b, v)
232 b = bytes.TrimRight(b, delim())
233 b = append(b, '}')
234 default:
235 panic(fmt.Sprintf("invalid type: %v", typ))
236 }
237 b = append(b, delim()...)
238 }
239 b = bytes.TrimRight(b, delim())
240 b = append(b, rightBracket...)
241 b = append(b, delim()...)
242 }
243 return b
244}
245
246func appendList(b []byte, v protoreflect.List, fd protoreflect.FieldDescriptor) []byte {
247 b = append(b, '[')
248 for i := 0; i < v.Len(); i++ {
249 b = appendValue(b, v.Get(i), fd)
250 b = append(b, delim()...)
251 }
252 b = bytes.TrimRight(b, delim())
253 b = append(b, ']')
254 return b
255}
256
257func appendMap(b []byte, v protoreflect.Map, fd protoreflect.FieldDescriptor) []byte {
258 var ks []protoreflect.MapKey
259 mapsort.Range(v, fd.MapKey().Kind(), func(k protoreflect.MapKey, _ protoreflect.Value) bool {
260 ks = append(ks, k)
261 return true
262 })
263
264 b = append(b, '{')
265 for _, k := range ks {
266 b = appendValue(b, k.Value(), fd.MapKey())
267 b = append(b, ':')
268 b = appendValue(b, v.Get(k), fd.MapValue())
269 b = append(b, delim()...)
270 }
271 b = bytes.TrimRight(b, delim())
272 b = append(b, '}')
273 return b
274}
275
276func delim() string {
277 // Deliberately introduce instability into the message string to
278 // discourage users from depending on it.
279 if detrand.Bool() {
280 return " "
281 }
282 return ", "
283}