blob: 21023e5afccaef02c8ba18a748c606e02c9efccf [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":
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800144 secs := m.Get(fds.ByName("seconds")).Int()
145 nanos := m.Get(fds.ByName("nanos")).Int()
Joe Tsaif8d77f82020-03-24 11:58:15 -0700146 if nanos < 0 || nanos >= 1e9 {
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800147 return nil
148 }
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800149 t := time.Unix(secs, nanos).UTC()
150 x := t.Format("2006-01-02T15:04:05.000000000") // RFC 3339
151 x = strings.TrimSuffix(x, "000")
152 x = strings.TrimSuffix(x, "000")
153 x = strings.TrimSuffix(x, ".000")
154 return append(b, x+"Z"...)
155
156 case "Duration":
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800157 secs := m.Get(fds.ByName("seconds")).Int()
158 nanos := m.Get(fds.ByName("nanos")).Int()
Joe Tsaif8d77f82020-03-24 11:58:15 -0700159 if nanos <= -1e9 || nanos >= 1e9 || (secs > 0 && nanos < 0) || (secs < 0 && nanos > 0) {
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800160 return nil
161 }
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800162 x := fmt.Sprintf("%d.%09d", secs, int64(math.Abs(float64(nanos))))
163 x = strings.TrimSuffix(x, "000")
164 x = strings.TrimSuffix(x, "000")
165 x = strings.TrimSuffix(x, ".000")
166 return append(b, x+"s"...)
167
168 case "BoolValue", "Int32Value", "Int64Value", "UInt32Value", "UInt64Value", "FloatValue", "DoubleValue", "StringValue", "BytesValue":
169 fd := fds.ByName("value")
170 return appendValue(b, m.Get(fd), fd)
171 }
172
173 return nil
174}
175
176func appendUnknown(b []byte, raw protoreflect.RawFields) []byte {
177 rs := make(map[protoreflect.FieldNumber][]protoreflect.RawFields)
178 for len(raw) > 0 {
179 num, _, n := protowire.ConsumeField(raw)
180 rs[num] = append(rs[num], raw[:n])
181 raw = raw[n:]
182 }
183
184 var ns []protoreflect.FieldNumber
185 for n := range rs {
186 ns = append(ns, n)
187 }
188 sort.Slice(ns, func(i, j int) bool { return ns[i] < ns[j] })
189
190 for _, n := range ns {
191 var leftBracket, rightBracket string
192 if len(rs[n]) > 1 {
193 leftBracket, rightBracket = "[", "]"
194 }
195
196 b = strconv.AppendInt(b, int64(n), 10)
197 b = append(b, ':')
198 b = append(b, leftBracket...)
199 for _, r := range rs[n] {
200 num, typ, n := protowire.ConsumeTag(r)
201 r = r[n:]
202 switch typ {
203 case protowire.VarintType:
204 v, _ := protowire.ConsumeVarint(r)
205 b = strconv.AppendInt(b, int64(v), 10)
206 case protowire.Fixed32Type:
207 v, _ := protowire.ConsumeFixed32(r)
208 b = append(b, fmt.Sprintf("0x%08x", v)...)
209 case protowire.Fixed64Type:
210 v, _ := protowire.ConsumeFixed64(r)
211 b = append(b, fmt.Sprintf("0x%016x", v)...)
212 case protowire.BytesType:
213 v, _ := protowire.ConsumeBytes(r)
214 b = strconv.AppendQuote(b, string(v))
215 case protowire.StartGroupType:
216 v, _ := protowire.ConsumeGroup(num, r)
217 b = append(b, '{')
218 b = appendUnknown(b, v)
219 b = bytes.TrimRight(b, delim())
220 b = append(b, '}')
221 default:
222 panic(fmt.Sprintf("invalid type: %v", typ))
223 }
224 b = append(b, delim()...)
225 }
226 b = bytes.TrimRight(b, delim())
227 b = append(b, rightBracket...)
228 b = append(b, delim()...)
229 }
230 return b
231}
232
233func appendList(b []byte, v protoreflect.List, fd protoreflect.FieldDescriptor) []byte {
234 b = append(b, '[')
235 for i := 0; i < v.Len(); i++ {
236 b = appendValue(b, v.Get(i), fd)
237 b = append(b, delim()...)
238 }
239 b = bytes.TrimRight(b, delim())
240 b = append(b, ']')
241 return b
242}
243
244func appendMap(b []byte, v protoreflect.Map, fd protoreflect.FieldDescriptor) []byte {
245 var ks []protoreflect.MapKey
246 mapsort.Range(v, fd.MapKey().Kind(), func(k protoreflect.MapKey, _ protoreflect.Value) bool {
247 ks = append(ks, k)
248 return true
249 })
250
251 b = append(b, '{')
252 for _, k := range ks {
253 b = appendValue(b, k.Value(), fd.MapKey())
254 b = append(b, ':')
255 b = appendValue(b, v.Get(k), fd.MapValue())
256 b = append(b, delim()...)
257 }
258 b = bytes.TrimRight(b, delim())
259 b = append(b, '}')
260 return b
261}
262
263func delim() string {
264 // Deliberately introduce instability into the message string to
265 // discourage users from depending on it.
266 if detrand.Bool() {
267 return " "
268 }
269 return ", "
270}