blob: 8d5304dc5b3201879acd8e06eb343e8cf7b7c97d [file] [log] [blame]
khenaidoo7d3c5582021-08-11 18:09:44 -04001// Copyright 2018 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
5package prototext
6
7import (
8 "fmt"
9 "strconv"
10 "unicode/utf8"
11
12 "google.golang.org/protobuf/encoding/protowire"
13 "google.golang.org/protobuf/internal/encoding/messageset"
14 "google.golang.org/protobuf/internal/encoding/text"
15 "google.golang.org/protobuf/internal/errors"
16 "google.golang.org/protobuf/internal/flags"
17 "google.golang.org/protobuf/internal/genid"
18 "google.golang.org/protobuf/internal/order"
19 "google.golang.org/protobuf/internal/pragma"
20 "google.golang.org/protobuf/internal/strs"
21 "google.golang.org/protobuf/proto"
22 "google.golang.org/protobuf/reflect/protoreflect"
23 pref "google.golang.org/protobuf/reflect/protoreflect"
24 "google.golang.org/protobuf/reflect/protoregistry"
25)
26
27const defaultIndent = " "
28
29// Format formats the message as a multiline string.
30// This function is only intended for human consumption and ignores errors.
31// Do not depend on the output being stable. It may change over time across
32// different versions of the program.
33func Format(m proto.Message) string {
34 return MarshalOptions{Multiline: true}.Format(m)
35}
36
37// Marshal writes the given proto.Message in textproto format using default
38// options. Do not depend on the output being stable. It may change over time
39// across different versions of the program.
40func Marshal(m proto.Message) ([]byte, error) {
41 return MarshalOptions{}.Marshal(m)
42}
43
44// MarshalOptions is a configurable text format marshaler.
45type MarshalOptions struct {
46 pragma.NoUnkeyedLiterals
47
48 // Multiline specifies whether the marshaler should format the output in
49 // indented-form with every textual element on a new line.
50 // If Indent is an empty string, then an arbitrary indent is chosen.
51 Multiline bool
52
53 // Indent specifies the set of indentation characters to use in a multiline
54 // formatted output such that every entry is preceded by Indent and
55 // terminated by a newline. If non-empty, then Multiline is treated as true.
56 // Indent can only be composed of space or tab characters.
57 Indent string
58
59 // EmitASCII specifies whether to format strings and bytes as ASCII only
60 // as opposed to using UTF-8 encoding when possible.
61 EmitASCII bool
62
63 // allowInvalidUTF8 specifies whether to permit the encoding of strings
64 // with invalid UTF-8. This is unexported as it is intended to only
65 // be specified by the Format method.
66 allowInvalidUTF8 bool
67
68 // AllowPartial allows messages that have missing required fields to marshal
69 // without returning an error. If AllowPartial is false (the default),
70 // Marshal will return error if there are any missing required fields.
71 AllowPartial bool
72
73 // EmitUnknown specifies whether to emit unknown fields in the output.
74 // If specified, the unmarshaler may be unable to parse the output.
75 // The default is to exclude unknown fields.
76 EmitUnknown bool
77
78 // Resolver is used for looking up types when expanding google.protobuf.Any
79 // messages. If nil, this defaults to using protoregistry.GlobalTypes.
80 Resolver interface {
81 protoregistry.ExtensionTypeResolver
82 protoregistry.MessageTypeResolver
83 }
84}
85
86// Format formats the message as a string.
87// This method is only intended for human consumption and ignores errors.
88// Do not depend on the output being stable. It may change over time across
89// different versions of the program.
90func (o MarshalOptions) Format(m proto.Message) string {
91 if m == nil || !m.ProtoReflect().IsValid() {
92 return "<nil>" // invalid syntax, but okay since this is for debugging
93 }
94 o.allowInvalidUTF8 = true
95 o.AllowPartial = true
96 o.EmitUnknown = true
97 b, _ := o.Marshal(m)
98 return string(b)
99}
100
101// Marshal writes the given proto.Message in textproto format using options in
102// MarshalOptions object. Do not depend on the output being stable. It may
103// change over time across different versions of the program.
104func (o MarshalOptions) Marshal(m proto.Message) ([]byte, error) {
105 return o.marshal(m)
106}
107
108// marshal is a centralized function that all marshal operations go through.
109// For profiling purposes, avoid changing the name of this function or
110// introducing other code paths for marshal that do not go through this.
111func (o MarshalOptions) marshal(m proto.Message) ([]byte, error) {
112 var delims = [2]byte{'{', '}'}
113
114 if o.Multiline && o.Indent == "" {
115 o.Indent = defaultIndent
116 }
117 if o.Resolver == nil {
118 o.Resolver = protoregistry.GlobalTypes
119 }
120
121 internalEnc, err := text.NewEncoder(o.Indent, delims, o.EmitASCII)
122 if err != nil {
123 return nil, err
124 }
125
126 // Treat nil message interface as an empty message,
127 // in which case there is nothing to output.
128 if m == nil {
129 return []byte{}, nil
130 }
131
132 enc := encoder{internalEnc, o}
133 err = enc.marshalMessage(m.ProtoReflect(), false)
134 if err != nil {
135 return nil, err
136 }
137 out := enc.Bytes()
138 if len(o.Indent) > 0 && len(out) > 0 {
139 out = append(out, '\n')
140 }
141 if o.AllowPartial {
142 return out, nil
143 }
144 return out, proto.CheckInitialized(m)
145}
146
147type encoder struct {
148 *text.Encoder
149 opts MarshalOptions
150}
151
152// marshalMessage marshals the given protoreflect.Message.
153func (e encoder) marshalMessage(m pref.Message, inclDelims bool) error {
154 messageDesc := m.Descriptor()
155 if !flags.ProtoLegacy && messageset.IsMessageSet(messageDesc) {
156 return errors.New("no support for proto1 MessageSets")
157 }
158
159 if inclDelims {
160 e.StartMessage()
161 defer e.EndMessage()
162 }
163
164 // Handle Any expansion.
165 if messageDesc.FullName() == genid.Any_message_fullname {
166 if e.marshalAny(m) {
167 return nil
168 }
169 // If unable to expand, continue on to marshal Any as a regular message.
170 }
171
172 // Marshal fields.
173 var err error
174 order.RangeFields(m, order.IndexNameFieldOrder, func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
175 if err = e.marshalField(fd.TextName(), v, fd); err != nil {
176 return false
177 }
178 return true
179 })
180 if err != nil {
181 return err
182 }
183
184 // Marshal unknown fields.
185 if e.opts.EmitUnknown {
186 e.marshalUnknown(m.GetUnknown())
187 }
188
189 return nil
190}
191
192// marshalField marshals the given field with protoreflect.Value.
193func (e encoder) marshalField(name string, val pref.Value, fd pref.FieldDescriptor) error {
194 switch {
195 case fd.IsList():
196 return e.marshalList(name, val.List(), fd)
197 case fd.IsMap():
198 return e.marshalMap(name, val.Map(), fd)
199 default:
200 e.WriteName(name)
201 return e.marshalSingular(val, fd)
202 }
203}
204
205// marshalSingular marshals the given non-repeated field value. This includes
206// all scalar types, enums, messages, and groups.
207func (e encoder) marshalSingular(val pref.Value, fd pref.FieldDescriptor) error {
208 kind := fd.Kind()
209 switch kind {
210 case pref.BoolKind:
211 e.WriteBool(val.Bool())
212
213 case pref.StringKind:
214 s := val.String()
215 if !e.opts.allowInvalidUTF8 && strs.EnforceUTF8(fd) && !utf8.ValidString(s) {
216 return errors.InvalidUTF8(string(fd.FullName()))
217 }
218 e.WriteString(s)
219
220 case pref.Int32Kind, pref.Int64Kind,
221 pref.Sint32Kind, pref.Sint64Kind,
222 pref.Sfixed32Kind, pref.Sfixed64Kind:
223 e.WriteInt(val.Int())
224
225 case pref.Uint32Kind, pref.Uint64Kind,
226 pref.Fixed32Kind, pref.Fixed64Kind:
227 e.WriteUint(val.Uint())
228
229 case pref.FloatKind:
230 // Encoder.WriteFloat handles the special numbers NaN and infinites.
231 e.WriteFloat(val.Float(), 32)
232
233 case pref.DoubleKind:
234 // Encoder.WriteFloat handles the special numbers NaN and infinites.
235 e.WriteFloat(val.Float(), 64)
236
237 case pref.BytesKind:
238 e.WriteString(string(val.Bytes()))
239
240 case pref.EnumKind:
241 num := val.Enum()
242 if desc := fd.Enum().Values().ByNumber(num); desc != nil {
243 e.WriteLiteral(string(desc.Name()))
244 } else {
245 // Use numeric value if there is no enum description.
246 e.WriteInt(int64(num))
247 }
248
249 case pref.MessageKind, pref.GroupKind:
250 return e.marshalMessage(val.Message(), true)
251
252 default:
253 panic(fmt.Sprintf("%v has unknown kind: %v", fd.FullName(), kind))
254 }
255 return nil
256}
257
258// marshalList marshals the given protoreflect.List as multiple name-value fields.
259func (e encoder) marshalList(name string, list pref.List, fd pref.FieldDescriptor) error {
260 size := list.Len()
261 for i := 0; i < size; i++ {
262 e.WriteName(name)
263 if err := e.marshalSingular(list.Get(i), fd); err != nil {
264 return err
265 }
266 }
267 return nil
268}
269
270// marshalMap marshals the given protoreflect.Map as multiple name-value fields.
271func (e encoder) marshalMap(name string, mmap pref.Map, fd pref.FieldDescriptor) error {
272 var err error
273 order.RangeEntries(mmap, order.GenericKeyOrder, func(key pref.MapKey, val pref.Value) bool {
274 e.WriteName(name)
275 e.StartMessage()
276 defer e.EndMessage()
277
278 e.WriteName(string(genid.MapEntry_Key_field_name))
279 err = e.marshalSingular(key.Value(), fd.MapKey())
280 if err != nil {
281 return false
282 }
283
284 e.WriteName(string(genid.MapEntry_Value_field_name))
285 err = e.marshalSingular(val, fd.MapValue())
286 if err != nil {
287 return false
288 }
289 return true
290 })
291 return err
292}
293
294// marshalUnknown parses the given []byte and marshals fields out.
295// This function assumes proper encoding in the given []byte.
296func (e encoder) marshalUnknown(b []byte) {
297 const dec = 10
298 const hex = 16
299 for len(b) > 0 {
300 num, wtype, n := protowire.ConsumeTag(b)
301 b = b[n:]
302 e.WriteName(strconv.FormatInt(int64(num), dec))
303
304 switch wtype {
305 case protowire.VarintType:
306 var v uint64
307 v, n = protowire.ConsumeVarint(b)
308 e.WriteUint(v)
309 case protowire.Fixed32Type:
310 var v uint32
311 v, n = protowire.ConsumeFixed32(b)
312 e.WriteLiteral("0x" + strconv.FormatUint(uint64(v), hex))
313 case protowire.Fixed64Type:
314 var v uint64
315 v, n = protowire.ConsumeFixed64(b)
316 e.WriteLiteral("0x" + strconv.FormatUint(v, hex))
317 case protowire.BytesType:
318 var v []byte
319 v, n = protowire.ConsumeBytes(b)
320 e.WriteString(string(v))
321 case protowire.StartGroupType:
322 e.StartMessage()
323 var v []byte
324 v, n = protowire.ConsumeGroup(num, b)
325 e.marshalUnknown(v)
326 e.EndMessage()
327 default:
328 panic(fmt.Sprintf("prototext: error parsing unknown field wire type: %v", wtype))
329 }
330
331 b = b[n:]
332 }
333}
334
335// marshalAny marshals the given google.protobuf.Any message in expanded form.
336// It returns true if it was able to marshal, else false.
337func (e encoder) marshalAny(any pref.Message) bool {
338 // Construct the embedded message.
339 fds := any.Descriptor().Fields()
340 fdType := fds.ByNumber(genid.Any_TypeUrl_field_number)
341 typeURL := any.Get(fdType).String()
342 mt, err := e.opts.Resolver.FindMessageByURL(typeURL)
343 if err != nil {
344 return false
345 }
346 m := mt.New().Interface()
347
348 // Unmarshal bytes into embedded message.
349 fdValue := fds.ByNumber(genid.Any_Value_field_number)
350 value := any.Get(fdValue)
351 err = proto.UnmarshalOptions{
352 AllowPartial: true,
353 Resolver: e.opts.Resolver,
354 }.Unmarshal(value.Bytes(), m)
355 if err != nil {
356 return false
357 }
358
359 // Get current encoder position. If marshaling fails, reset encoder output
360 // back to this position.
361 pos := e.Snapshot()
362
363 // Field name is the proto field name enclosed in [].
364 e.WriteName("[" + typeURL + "]")
365 err = e.marshalMessage(m.ProtoReflect(), true)
366 if err != nil {
367 e.Reset(pos)
368 return false
369 }
370 return true
371}