blob: f7d185a34098691e534a56ac015a0017c153827d [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"
Herbie Ongc3f4d482018-11-22 14:29:07 -08009 "math/rand"
Joe Tsai27c2a762018-08-01 16:48:18 -070010 "strings"
11
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 }
Herbie Ongc3f4d482018-11-22 14:29:07 -0800108 spaceAdded := false
Joe Tsai27c2a762018-08-01 16:48:18 -0700109 for i, item := range items {
110 p.out = append(p.out, p.indents...)
111 if err := p.marshalKey(item[0]); !p.nerr.Merge(err) {
112 return err
113 }
114 p.out = append(p.out, ':')
115 if len(p.indent) > 0 {
116 p.out = append(p.out, ' ')
117 }
Herbie Ongc3f4d482018-11-22 14:29:07 -0800118 // For multi-line output, add a random extra space after key: per message to
119 // make output unstable.
120 if !spaceAdded && len(p.indent) > 0 && rand.Intn(2) == 1 {
121 p.out = append(p.out, ' ')
122 spaceAdded = true
123 }
124
Joe Tsai27c2a762018-08-01 16:48:18 -0700125 if err := p.marshalValue(item[1]); !p.nerr.Merge(err) {
126 return err
127 }
128 if i < len(items)-1 && len(p.indent) == 0 {
129 p.out = append(p.out, ' ')
130 }
Herbie Ongc3f4d482018-11-22 14:29:07 -0800131 // For single-line output, add a random extra space after a field per message to
132 // make output unstable.
133 if !spaceAdded && len(p.indent) == 0 && i != len(items)-1 && rand.Intn(2) == 1 {
134 p.out = append(p.out, ' ')
135 spaceAdded = true
136 }
Joe Tsai27c2a762018-08-01 16:48:18 -0700137 p.out = append(p.out, p.newline...)
138 }
139 if emitDelims {
140 p.indents = p.indents[:len(p.indents)-len(p.indent)]
141 if len(items) > 0 {
142 p.out = append(p.out, p.indents...)
143 }
144 p.out = append(p.out, p.delims[1])
145 }
146 return nil
147}
148
149func (p *encoder) marshalKey(v Value) error {
150 switch v.Type() {
151 case String:
152 var err error
153 p.out = append(p.out, '[')
154 if len(urlRegexp.FindString(v.str)) == len(v.str) {
155 p.out = append(p.out, v.str...)
156 } else {
157 err = p.marshalString(v)
158 }
159 p.out = append(p.out, ']')
160 return err
161 case Uint:
162 return p.marshalNumber(v)
163 case Name:
164 s, _ := v.Name()
165 p.out = append(p.out, s...)
166 return nil
167 default:
168 return errors.New("invalid type %v to encode key", v.Type())
169 }
170}
171
172func (p *encoder) marshalValue(v Value) error {
173 switch v.Type() {
174 case Bool, Int, Uint, Float:
175 return p.marshalNumber(v)
176 case String:
177 return p.marshalString(v)
178 case List:
179 return p.marshalList(v)
180 case Message:
181 return p.marshalMessage(v, true)
182 case Name:
183 s, _ := v.Name()
184 p.out = append(p.out, s...)
185 return nil
186 default:
187 return errors.New("invalid type %v to encode value", v.Type())
188 }
189}