blob: f01cf606b29b233f86afd6a4d7b670e4fe92d1b9 [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 Tsai2ce1ca92020-02-28 18:37:16 -080069 k := string(fd.Name())
70 if fd.IsExtension() {
71 k = string("[" + fd.FullName() + "]")
72 }
73
74 b = append(b, k...)
75 b = append(b, ':')
Joe Tsai92679662020-06-24 14:28:07 -070076 b = appendValue(b, v, fd)
Joe Tsai2ce1ca92020-02-28 18:37:16 -080077 b = append(b, delim()...)
Joe Tsai92679662020-06-24 14:28:07 -070078 return true
79 })
Joe Tsai2ce1ca92020-02-28 18:37:16 -080080 b = appendUnknown(b, m.GetUnknown())
81 b = bytes.TrimRight(b, delim())
82 b = append(b, '}')
83 return b
84}
85
86var protocmpMessageType = reflect.TypeOf(map[string]interface{}(nil))
87
88func appendKnownMessage(b []byte, m protoreflect.Message) []byte {
89 md := m.Descriptor()
Joe Tsai2ce1ca92020-02-28 18:37:16 -080090 fds := md.Fields()
Joe Tsaib2f4e622020-05-27 16:28:31 -070091 switch md.FullName() {
92 case genid.Any_message_fullname:
Joe Tsai2ce1ca92020-02-28 18:37:16 -080093 var msgVal protoreflect.Message
Joe Tsaib2f4e622020-05-27 16:28:31 -070094 url := m.Get(fds.ByNumber(genid.Any_TypeUrl_field_number)).String()
Joe Tsai2ce1ca92020-02-28 18:37:16 -080095 if v := reflect.ValueOf(m); v.Type().ConvertibleTo(protocmpMessageType) {
96 // For protocmp.Message, directly obtain the sub-message value
97 // which is stored in structured form, rather than as raw bytes.
98 m2 := v.Convert(protocmpMessageType).Interface().(map[string]interface{})
Joe Tsaie0b77db2020-05-26 11:21:59 -070099 v, ok := m2[string(genid.Any_Value_field_name)].(proto.Message)
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800100 if !ok {
101 return nil
102 }
103 msgVal = v.ProtoReflect()
104 } else {
Joe Tsaib2f4e622020-05-27 16:28:31 -0700105 val := m.Get(fds.ByNumber(genid.Any_Value_field_number)).Bytes()
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800106 mt, err := protoregistry.GlobalTypes.FindMessageByURL(url)
107 if err != nil {
108 return nil
109 }
110 msgVal = mt.New()
111 err = proto.UnmarshalOptions{AllowPartial: true}.Unmarshal(val, msgVal.Interface())
112 if err != nil {
113 return nil
114 }
115 }
116
117 b = append(b, '{')
118 b = append(b, "["+url+"]"...)
119 b = append(b, ':')
120 b = appendMessage(b, msgVal)
121 b = append(b, '}')
122 return b
123
Joe Tsaib2f4e622020-05-27 16:28:31 -0700124 case genid.Timestamp_message_fullname:
125 secs := m.Get(fds.ByNumber(genid.Timestamp_Seconds_field_number)).Int()
126 nanos := m.Get(fds.ByNumber(genid.Timestamp_Nanos_field_number)).Int()
Joe Tsaif8d77f82020-03-24 11:58:15 -0700127 if nanos < 0 || nanos >= 1e9 {
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800128 return nil
129 }
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800130 t := time.Unix(secs, nanos).UTC()
131 x := t.Format("2006-01-02T15:04:05.000000000") // RFC 3339
132 x = strings.TrimSuffix(x, "000")
133 x = strings.TrimSuffix(x, "000")
134 x = strings.TrimSuffix(x, ".000")
135 return append(b, x+"Z"...)
136
Joe Tsaib2f4e622020-05-27 16:28:31 -0700137 case genid.Duration_message_fullname:
138 secs := m.Get(fds.ByNumber(genid.Duration_Seconds_field_number)).Int()
139 nanos := m.Get(fds.ByNumber(genid.Duration_Nanos_field_number)).Int()
Joe Tsaif8d77f82020-03-24 11:58:15 -0700140 if nanos <= -1e9 || nanos >= 1e9 || (secs > 0 && nanos < 0) || (secs < 0 && nanos > 0) {
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800141 return nil
142 }
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800143 x := fmt.Sprintf("%d.%09d", secs, int64(math.Abs(float64(nanos))))
144 x = strings.TrimSuffix(x, "000")
145 x = strings.TrimSuffix(x, "000")
146 x = strings.TrimSuffix(x, ".000")
147 return append(b, x+"s"...)
148
Joe Tsai69839c72020-05-29 01:20:58 -0700149 case genid.BoolValue_message_fullname,
150 genid.Int32Value_message_fullname,
151 genid.Int64Value_message_fullname,
152 genid.UInt32Value_message_fullname,
153 genid.UInt64Value_message_fullname,
154 genid.FloatValue_message_fullname,
155 genid.DoubleValue_message_fullname,
156 genid.StringValue_message_fullname,
157 genid.BytesValue_message_fullname:
Joe Tsaib2f4e622020-05-27 16:28:31 -0700158 fd := fds.ByNumber(genid.WrapperValue_Value_field_number)
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800159 return appendValue(b, m.Get(fd), fd)
160 }
161
162 return nil
163}
164
165func appendUnknown(b []byte, raw protoreflect.RawFields) []byte {
166 rs := make(map[protoreflect.FieldNumber][]protoreflect.RawFields)
167 for len(raw) > 0 {
168 num, _, n := protowire.ConsumeField(raw)
169 rs[num] = append(rs[num], raw[:n])
170 raw = raw[n:]
171 }
172
173 var ns []protoreflect.FieldNumber
174 for n := range rs {
175 ns = append(ns, n)
176 }
177 sort.Slice(ns, func(i, j int) bool { return ns[i] < ns[j] })
178
179 for _, n := range ns {
180 var leftBracket, rightBracket string
181 if len(rs[n]) > 1 {
182 leftBracket, rightBracket = "[", "]"
183 }
184
185 b = strconv.AppendInt(b, int64(n), 10)
186 b = append(b, ':')
187 b = append(b, leftBracket...)
188 for _, r := range rs[n] {
189 num, typ, n := protowire.ConsumeTag(r)
190 r = r[n:]
191 switch typ {
192 case protowire.VarintType:
193 v, _ := protowire.ConsumeVarint(r)
194 b = strconv.AppendInt(b, int64(v), 10)
195 case protowire.Fixed32Type:
196 v, _ := protowire.ConsumeFixed32(r)
197 b = append(b, fmt.Sprintf("0x%08x", v)...)
198 case protowire.Fixed64Type:
199 v, _ := protowire.ConsumeFixed64(r)
200 b = append(b, fmt.Sprintf("0x%016x", v)...)
201 case protowire.BytesType:
202 v, _ := protowire.ConsumeBytes(r)
203 b = strconv.AppendQuote(b, string(v))
204 case protowire.StartGroupType:
205 v, _ := protowire.ConsumeGroup(num, r)
206 b = append(b, '{')
207 b = appendUnknown(b, v)
208 b = bytes.TrimRight(b, delim())
209 b = append(b, '}')
210 default:
211 panic(fmt.Sprintf("invalid type: %v", typ))
212 }
213 b = append(b, delim()...)
214 }
215 b = bytes.TrimRight(b, delim())
216 b = append(b, rightBracket...)
217 b = append(b, delim()...)
218 }
219 return b
220}
221
222func appendList(b []byte, v protoreflect.List, fd protoreflect.FieldDescriptor) []byte {
223 b = append(b, '[')
224 for i := 0; i < v.Len(); i++ {
225 b = appendValue(b, v.Get(i), fd)
226 b = append(b, delim()...)
227 }
228 b = bytes.TrimRight(b, delim())
229 b = append(b, ']')
230 return b
231}
232
233func appendMap(b []byte, v protoreflect.Map, fd protoreflect.FieldDescriptor) []byte {
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800234 b = append(b, '{')
Joe Tsai92679662020-06-24 14:28:07 -0700235 order.RangeEntries(v, order.GenericKeyOrder, func(k protoreflect.MapKey, v protoreflect.Value) bool {
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800236 b = appendValue(b, k.Value(), fd.MapKey())
237 b = append(b, ':')
Joe Tsai92679662020-06-24 14:28:07 -0700238 b = appendValue(b, v, fd.MapValue())
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800239 b = append(b, delim()...)
Joe Tsai92679662020-06-24 14:28:07 -0700240 return true
241 })
Joe Tsai2ce1ca92020-02-28 18:37:16 -0800242 b = bytes.TrimRight(b, delim())
243 b = append(b, '}')
244 return b
245}
246
247func delim() string {
248 // Deliberately introduce instability into the message string to
249 // discourage users from depending on it.
250 if detrand.Bool() {
251 return " "
252 }
253 return ", "
254}