blob: a8a8031581366da6075bb9915f9558e799906d03 [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 Tsai92679662020-06-24 14:28:07 -070023 "google.golang.org/protobuf/internal/order"
Joe Tsai2ce1ca92020-02-28 18:37:16 -080024 "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
Joe Tsai2ce1ca92020-02-28 18:37:16 -080067 b = append(b, '{')
Joe Tsai92679662020-06-24 14:28:07 -070068 order.RangeFields(m, order.IndexNameFieldOrder, func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
Joe Tsaie14d6b32020-06-24 16:47:32 -070069 b = append(b, fd.TextName()...)
Joe Tsai2ce1ca92020-02-28 18:37:16 -080070 b = append(b, ':')
Joe Tsai92679662020-06-24 14:28:07 -070071 b = appendValue(b, v, fd)
Joe Tsai2ce1ca92020-02-28 18:37:16 -080072 b = append(b, delim()...)
Joe Tsai92679662020-06-24 14:28:07 -070073 return true
74 })
Joe Tsai2ce1ca92020-02-28 18:37:16 -080075 b = appendUnknown(b, m.GetUnknown())
76 b = bytes.TrimRight(b, delim())
77 b = append(b, '}')
78 return b
79}
80
81var protocmpMessageType = reflect.TypeOf(map[string]interface{}(nil))
82
83func appendKnownMessage(b []byte, m protoreflect.Message) []byte {
84 md := m.Descriptor()
Joe Tsai2ce1ca92020-02-28 18:37:16 -080085 fds := md.Fields()
Joe Tsaib2f4e622020-05-27 16:28:31 -070086 switch md.FullName() {
87 case genid.Any_message_fullname:
Joe Tsai2ce1ca92020-02-28 18:37:16 -080088 var msgVal protoreflect.Message
Joe Tsaib2f4e622020-05-27 16:28:31 -070089 url := m.Get(fds.ByNumber(genid.Any_TypeUrl_field_number)).String()
Joe Tsai2ce1ca92020-02-28 18:37:16 -080090 if v := reflect.ValueOf(m); v.Type().ConvertibleTo(protocmpMessageType) {
91 // For protocmp.Message, directly obtain the sub-message value
92 // which is stored in structured form, rather than as raw bytes.
93 m2 := v.Convert(protocmpMessageType).Interface().(map[string]interface{})
Joe Tsaie0b77db2020-05-26 11:21:59 -070094 v, ok := m2[string(genid.Any_Value_field_name)].(proto.Message)
Joe Tsai2ce1ca92020-02-28 18:37:16 -080095 if !ok {
96 return nil
97 }
98 msgVal = v.ProtoReflect()
99 } else {
Joe Tsaib2f4e622020-05-27 16:28:31 -0700100 val := m.Get(fds.ByNumber(genid.Any_Value_field_number)).Bytes()
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800101 mt, err := protoregistry.GlobalTypes.FindMessageByURL(url)
102 if err != nil {
103 return nil
104 }
105 msgVal = mt.New()
106 err = proto.UnmarshalOptions{AllowPartial: true}.Unmarshal(val, msgVal.Interface())
107 if err != nil {
108 return nil
109 }
110 }
111
112 b = append(b, '{')
113 b = append(b, "["+url+"]"...)
114 b = append(b, ':')
115 b = appendMessage(b, msgVal)
116 b = append(b, '}')
117 return b
118
Joe Tsaib2f4e622020-05-27 16:28:31 -0700119 case genid.Timestamp_message_fullname:
120 secs := m.Get(fds.ByNumber(genid.Timestamp_Seconds_field_number)).Int()
121 nanos := m.Get(fds.ByNumber(genid.Timestamp_Nanos_field_number)).Int()
Joe Tsaif8d77f82020-03-24 11:58:15 -0700122 if nanos < 0 || nanos >= 1e9 {
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800123 return nil
124 }
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800125 t := time.Unix(secs, nanos).UTC()
126 x := t.Format("2006-01-02T15:04:05.000000000") // RFC 3339
127 x = strings.TrimSuffix(x, "000")
128 x = strings.TrimSuffix(x, "000")
129 x = strings.TrimSuffix(x, ".000")
130 return append(b, x+"Z"...)
131
Joe Tsaib2f4e622020-05-27 16:28:31 -0700132 case genid.Duration_message_fullname:
133 secs := m.Get(fds.ByNumber(genid.Duration_Seconds_field_number)).Int()
134 nanos := m.Get(fds.ByNumber(genid.Duration_Nanos_field_number)).Int()
Joe Tsaif8d77f82020-03-24 11:58:15 -0700135 if nanos <= -1e9 || nanos >= 1e9 || (secs > 0 && nanos < 0) || (secs < 0 && nanos > 0) {
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800136 return nil
137 }
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800138 x := fmt.Sprintf("%d.%09d", secs, int64(math.Abs(float64(nanos))))
139 x = strings.TrimSuffix(x, "000")
140 x = strings.TrimSuffix(x, "000")
141 x = strings.TrimSuffix(x, ".000")
142 return append(b, x+"s"...)
143
Joe Tsai69839c72020-05-29 01:20:58 -0700144 case genid.BoolValue_message_fullname,
145 genid.Int32Value_message_fullname,
146 genid.Int64Value_message_fullname,
147 genid.UInt32Value_message_fullname,
148 genid.UInt64Value_message_fullname,
149 genid.FloatValue_message_fullname,
150 genid.DoubleValue_message_fullname,
151 genid.StringValue_message_fullname,
152 genid.BytesValue_message_fullname:
Joe Tsaib2f4e622020-05-27 16:28:31 -0700153 fd := fds.ByNumber(genid.WrapperValue_Value_field_number)
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800154 return appendValue(b, m.Get(fd), fd)
155 }
156
157 return nil
158}
159
160func appendUnknown(b []byte, raw protoreflect.RawFields) []byte {
161 rs := make(map[protoreflect.FieldNumber][]protoreflect.RawFields)
162 for len(raw) > 0 {
163 num, _, n := protowire.ConsumeField(raw)
164 rs[num] = append(rs[num], raw[:n])
165 raw = raw[n:]
166 }
167
168 var ns []protoreflect.FieldNumber
169 for n := range rs {
170 ns = append(ns, n)
171 }
172 sort.Slice(ns, func(i, j int) bool { return ns[i] < ns[j] })
173
174 for _, n := range ns {
175 var leftBracket, rightBracket string
176 if len(rs[n]) > 1 {
177 leftBracket, rightBracket = "[", "]"
178 }
179
180 b = strconv.AppendInt(b, int64(n), 10)
181 b = append(b, ':')
182 b = append(b, leftBracket...)
183 for _, r := range rs[n] {
184 num, typ, n := protowire.ConsumeTag(r)
185 r = r[n:]
186 switch typ {
187 case protowire.VarintType:
188 v, _ := protowire.ConsumeVarint(r)
189 b = strconv.AppendInt(b, int64(v), 10)
190 case protowire.Fixed32Type:
191 v, _ := protowire.ConsumeFixed32(r)
192 b = append(b, fmt.Sprintf("0x%08x", v)...)
193 case protowire.Fixed64Type:
194 v, _ := protowire.ConsumeFixed64(r)
195 b = append(b, fmt.Sprintf("0x%016x", v)...)
196 case protowire.BytesType:
197 v, _ := protowire.ConsumeBytes(r)
198 b = strconv.AppendQuote(b, string(v))
199 case protowire.StartGroupType:
200 v, _ := protowire.ConsumeGroup(num, r)
201 b = append(b, '{')
202 b = appendUnknown(b, v)
203 b = bytes.TrimRight(b, delim())
204 b = append(b, '}')
205 default:
206 panic(fmt.Sprintf("invalid type: %v", typ))
207 }
208 b = append(b, delim()...)
209 }
210 b = bytes.TrimRight(b, delim())
211 b = append(b, rightBracket...)
212 b = append(b, delim()...)
213 }
214 return b
215}
216
217func appendList(b []byte, v protoreflect.List, fd protoreflect.FieldDescriptor) []byte {
218 b = append(b, '[')
219 for i := 0; i < v.Len(); i++ {
220 b = appendValue(b, v.Get(i), fd)
221 b = append(b, delim()...)
222 }
223 b = bytes.TrimRight(b, delim())
224 b = append(b, ']')
225 return b
226}
227
228func appendMap(b []byte, v protoreflect.Map, fd protoreflect.FieldDescriptor) []byte {
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800229 b = append(b, '{')
Joe Tsai92679662020-06-24 14:28:07 -0700230 order.RangeEntries(v, order.GenericKeyOrder, func(k protoreflect.MapKey, v protoreflect.Value) bool {
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800231 b = appendValue(b, k.Value(), fd.MapKey())
232 b = append(b, ':')
Joe Tsai92679662020-06-24 14:28:07 -0700233 b = appendValue(b, v, fd.MapValue())
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800234 b = append(b, delim()...)
Joe Tsai92679662020-06-24 14:28:07 -0700235 return true
236 })
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800237 b = bytes.TrimRight(b, delim())
238 b = append(b, '}')
239 return b
240}
241
242func delim() string {
243 // Deliberately introduce instability into the message string to
244 // discourage users from depending on it.
245 if detrand.Bool() {
246 return " "
247 }
248 return ", "
249}