blob: 7a7fe717055e0667f45f3f179f3cd2225f05d572 [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"...)
Joe Tsaib2f4e622020-05-27 16:28:31 -0700164 }
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800165
Joe Tsaib2f4e622020-05-27 16:28:31 -0700166 if genid.WhichFile(md.FullName()) == genid.Wrappers_file {
167 fd := fds.ByNumber(genid.WrapperValue_Value_field_number)
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800168 return appendValue(b, m.Get(fd), fd)
169 }
170
171 return nil
172}
173
174func appendUnknown(b []byte, raw protoreflect.RawFields) []byte {
175 rs := make(map[protoreflect.FieldNumber][]protoreflect.RawFields)
176 for len(raw) > 0 {
177 num, _, n := protowire.ConsumeField(raw)
178 rs[num] = append(rs[num], raw[:n])
179 raw = raw[n:]
180 }
181
182 var ns []protoreflect.FieldNumber
183 for n := range rs {
184 ns = append(ns, n)
185 }
186 sort.Slice(ns, func(i, j int) bool { return ns[i] < ns[j] })
187
188 for _, n := range ns {
189 var leftBracket, rightBracket string
190 if len(rs[n]) > 1 {
191 leftBracket, rightBracket = "[", "]"
192 }
193
194 b = strconv.AppendInt(b, int64(n), 10)
195 b = append(b, ':')
196 b = append(b, leftBracket...)
197 for _, r := range rs[n] {
198 num, typ, n := protowire.ConsumeTag(r)
199 r = r[n:]
200 switch typ {
201 case protowire.VarintType:
202 v, _ := protowire.ConsumeVarint(r)
203 b = strconv.AppendInt(b, int64(v), 10)
204 case protowire.Fixed32Type:
205 v, _ := protowire.ConsumeFixed32(r)
206 b = append(b, fmt.Sprintf("0x%08x", v)...)
207 case protowire.Fixed64Type:
208 v, _ := protowire.ConsumeFixed64(r)
209 b = append(b, fmt.Sprintf("0x%016x", v)...)
210 case protowire.BytesType:
211 v, _ := protowire.ConsumeBytes(r)
212 b = strconv.AppendQuote(b, string(v))
213 case protowire.StartGroupType:
214 v, _ := protowire.ConsumeGroup(num, r)
215 b = append(b, '{')
216 b = appendUnknown(b, v)
217 b = bytes.TrimRight(b, delim())
218 b = append(b, '}')
219 default:
220 panic(fmt.Sprintf("invalid type: %v", typ))
221 }
222 b = append(b, delim()...)
223 }
224 b = bytes.TrimRight(b, delim())
225 b = append(b, rightBracket...)
226 b = append(b, delim()...)
227 }
228 return b
229}
230
231func appendList(b []byte, v protoreflect.List, fd protoreflect.FieldDescriptor) []byte {
232 b = append(b, '[')
233 for i := 0; i < v.Len(); i++ {
234 b = appendValue(b, v.Get(i), fd)
235 b = append(b, delim()...)
236 }
237 b = bytes.TrimRight(b, delim())
238 b = append(b, ']')
239 return b
240}
241
242func appendMap(b []byte, v protoreflect.Map, fd protoreflect.FieldDescriptor) []byte {
243 var ks []protoreflect.MapKey
244 mapsort.Range(v, fd.MapKey().Kind(), func(k protoreflect.MapKey, _ protoreflect.Value) bool {
245 ks = append(ks, k)
246 return true
247 })
248
249 b = append(b, '{')
250 for _, k := range ks {
251 b = appendValue(b, k.Value(), fd.MapKey())
252 b = append(b, ':')
253 b = appendValue(b, v.Get(k), fd.MapValue())
254 b = append(b, delim()...)
255 }
256 b = bytes.TrimRight(b, delim())
257 b = append(b, '}')
258 return b
259}
260
261func delim() string {
262 // Deliberately introduce instability into the message string to
263 // discourage users from depending on it.
264 if detrand.Bool() {
265 return " "
266 }
267 return ", "
268}