blob: d3e29867b5e668d63ffc2f0d6781ef466366c35c [file] [log] [blame]
Joe Tsai27c2a762018-08-01 16:48:18 -07001// 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 text
6
7import (
8 "bytes"
9 "strings"
10
Joe Tsai492a4762018-11-26 17:16:32 -080011 "github.com/golang/protobuf/v2/internal/detrand"
Joe Tsai01ab2962018-09-21 17:44:00 -070012 "github.com/golang/protobuf/v2/internal/errors"
Joe Tsai27c2a762018-08-01 16:48:18 -070013)
14
15// Marshal serializes v as the proto text format, where v must be a Message.
16// In the proto text format, the top-level value is always a message where the
17// delimiters are elided.
18//
19// If indent is a non-empty string, it causes every entry in a List or Message
20// to be preceded by the indent and trailed by a newline.
21//
22// If delims is not the zero value, it controls the delimiter characters used
23// for messages (e.g., "{}" vs "<>").
24//
25// If outputASCII is true, strings will be serialized in such a way that
26// multi-byte UTF-8 sequences are escaped. This property ensures that the
27// overall output is ASCII (as opposed to UTF-8).
28func Marshal(v Value, indent string, delims [2]byte, outputASCII bool) ([]byte, error) {
29 p := encoder{}
30 if len(indent) > 0 {
31 if strings.Trim(indent, " \t") != "" {
32 return nil, errors.New("indent may only be composed of space and tab characters")
33 }
34 p.indent = indent
35 p.newline = "\n"
36 }
37 switch delims {
38 case [2]byte{0, 0}:
39 p.delims = [2]byte{'{', '}'}
40 case [2]byte{'{', '}'}, [2]byte{'<', '>'}:
41 p.delims = delims
42 default:
43 return nil, errors.New("delimiters may only be \"{}\" or \"<>\"")
44 }
45 p.outputASCII = outputASCII
46
47 err := p.marshalMessage(v, false)
48 if !p.nerr.Merge(err) {
49 return nil, err
50 }
51 if len(indent) > 0 {
52 return append(bytes.TrimRight(p.out, "\n"), '\n'), p.nerr.E
53 }
54 return p.out, p.nerr.E
55}
56
57type encoder struct {
58 nerr errors.NonFatal
59 out []byte
60
61 indent string
62 indents []byte
63 newline string // set to "\n" if len(indent) > 0
64 delims [2]byte
65 outputASCII bool
66}
67
68func (p *encoder) marshalList(v Value) error {
69 if v.Type() != List {
70 return errors.New("invalid type %v, expected list", v.Type())
71 }
72 elems := v.List()
73 p.out = append(p.out, '[')
74 p.indents = append(p.indents, p.indent...)
75 if len(elems) > 0 {
76 p.out = append(p.out, p.newline...)
77 }
78 for i, elem := range elems {
79 p.out = append(p.out, p.indents...)
80 if err := p.marshalValue(elem); !p.nerr.Merge(err) {
81 return err
82 }
83 if i < len(elems)-1 {
84 p.out = append(p.out, ',')
85 }
86 p.out = append(p.out, p.newline...)
87 }
88 p.indents = p.indents[:len(p.indents)-len(p.indent)]
89 if len(elems) > 0 {
90 p.out = append(p.out, p.indents...)
91 }
92 p.out = append(p.out, ']')
93 return nil
94}
95
96func (p *encoder) marshalMessage(v Value, emitDelims bool) error {
97 if v.Type() != Message {
98 return errors.New("invalid type %v, expected message", v.Type())
99 }
100 items := v.Message()
101 if emitDelims {
102 p.out = append(p.out, p.delims[0])
103 p.indents = append(p.indents, p.indent...)
104 if len(items) > 0 {
105 p.out = append(p.out, p.newline...)
106 }
107 }
108 for i, item := range items {
109 p.out = append(p.out, p.indents...)
110 if err := p.marshalKey(item[0]); !p.nerr.Merge(err) {
111 return err
112 }
113 p.out = append(p.out, ':')
114 if len(p.indent) > 0 {
115 p.out = append(p.out, ' ')
116 }
Herbie Ongc3f4d482018-11-22 14:29:07 -0800117 // For multi-line output, add a random extra space after key: per message to
118 // make output unstable.
Joe Tsai492a4762018-11-26 17:16:32 -0800119 if len(p.indent) > 0 && detrand.Bool() {
Herbie Ongc3f4d482018-11-22 14:29:07 -0800120 p.out = append(p.out, ' ')
Herbie Ongc3f4d482018-11-22 14:29:07 -0800121 }
122
Joe Tsai27c2a762018-08-01 16:48:18 -0700123 if err := p.marshalValue(item[1]); !p.nerr.Merge(err) {
124 return err
125 }
126 if i < len(items)-1 && len(p.indent) == 0 {
127 p.out = append(p.out, ' ')
128 }
Herbie Ongc3f4d482018-11-22 14:29:07 -0800129 // For single-line output, add a random extra space after a field per message to
130 // make output unstable.
Joe Tsai492a4762018-11-26 17:16:32 -0800131 if len(p.indent) == 0 && detrand.Bool() && i != len(items)-1 {
Herbie Ongc3f4d482018-11-22 14:29:07 -0800132 p.out = append(p.out, ' ')
Herbie Ongc3f4d482018-11-22 14:29:07 -0800133 }
Joe Tsai27c2a762018-08-01 16:48:18 -0700134 p.out = append(p.out, p.newline...)
135 }
136 if emitDelims {
137 p.indents = p.indents[:len(p.indents)-len(p.indent)]
138 if len(items) > 0 {
139 p.out = append(p.out, p.indents...)
140 }
141 p.out = append(p.out, p.delims[1])
142 }
143 return nil
144}
145
146func (p *encoder) marshalKey(v Value) error {
147 switch v.Type() {
148 case String:
149 var err error
150 p.out = append(p.out, '[')
151 if len(urlRegexp.FindString(v.str)) == len(v.str) {
152 p.out = append(p.out, v.str...)
153 } else {
154 err = p.marshalString(v)
155 }
156 p.out = append(p.out, ']')
157 return err
158 case Uint:
159 return p.marshalNumber(v)
160 case Name:
161 s, _ := v.Name()
162 p.out = append(p.out, s...)
163 return nil
164 default:
165 return errors.New("invalid type %v to encode key", v.Type())
166 }
167}
168
169func (p *encoder) marshalValue(v Value) error {
170 switch v.Type() {
Herbie Ong84f09602019-01-17 19:31:47 -0800171 case Bool, Int, Uint, Float32, Float64:
Joe Tsai27c2a762018-08-01 16:48:18 -0700172 return p.marshalNumber(v)
173 case String:
174 return p.marshalString(v)
175 case List:
176 return p.marshalList(v)
177 case Message:
178 return p.marshalMessage(v, true)
179 case Name:
180 s, _ := v.Name()
181 p.out = append(p.out, s...)
182 return nil
183 default:
184 return errors.New("invalid type %v to encode value", v.Type())
185 }
186}