blob: 67570469aad8a552066bc331597d9eb62cc281f9 [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 (
Herbie Onga3369c52019-04-23 00:24:46 -07008 "regexp"
Joe Tsai27c2a762018-08-01 16:48:18 -07009 "strings"
10
Damien Neile89e6242019-05-13 23:55:40 -070011 "google.golang.org/protobuf/internal/detrand"
12 "google.golang.org/protobuf/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)
Damien Neil8c86fc52019-06-19 09:28:29 -070048 if err != nil {
Joe Tsai27c2a762018-08-01 16:48:18 -070049 return nil, err
50 }
Damien Neil8c86fc52019-06-19 09:28:29 -070051 return p.out, nil
Joe Tsai27c2a762018-08-01 16:48:18 -070052}
53
54type encoder struct {
Damien Neil8c86fc52019-06-19 09:28:29 -070055 out []byte
Joe Tsai27c2a762018-08-01 16:48:18 -070056
57 indent string
58 indents []byte
59 newline string // set to "\n" if len(indent) > 0
60 delims [2]byte
61 outputASCII bool
62}
63
64func (p *encoder) marshalList(v Value) error {
65 if v.Type() != List {
66 return errors.New("invalid type %v, expected list", v.Type())
67 }
68 elems := v.List()
69 p.out = append(p.out, '[')
70 p.indents = append(p.indents, p.indent...)
71 if len(elems) > 0 {
72 p.out = append(p.out, p.newline...)
73 }
74 for i, elem := range elems {
75 p.out = append(p.out, p.indents...)
Damien Neil8c86fc52019-06-19 09:28:29 -070076 if err := p.marshalValue(elem); err != nil {
Joe Tsai27c2a762018-08-01 16:48:18 -070077 return err
78 }
79 if i < len(elems)-1 {
80 p.out = append(p.out, ',')
81 }
82 p.out = append(p.out, p.newline...)
83 }
84 p.indents = p.indents[:len(p.indents)-len(p.indent)]
85 if len(elems) > 0 {
86 p.out = append(p.out, p.indents...)
87 }
88 p.out = append(p.out, ']')
89 return nil
90}
91
92func (p *encoder) marshalMessage(v Value, emitDelims bool) error {
93 if v.Type() != Message {
94 return errors.New("invalid type %v, expected message", v.Type())
95 }
96 items := v.Message()
97 if emitDelims {
98 p.out = append(p.out, p.delims[0])
99 p.indents = append(p.indents, p.indent...)
100 if len(items) > 0 {
101 p.out = append(p.out, p.newline...)
102 }
103 }
104 for i, item := range items {
105 p.out = append(p.out, p.indents...)
Damien Neil8c86fc52019-06-19 09:28:29 -0700106 if err := p.marshalKey(item[0]); err != nil {
Joe Tsai27c2a762018-08-01 16:48:18 -0700107 return err
108 }
109 p.out = append(p.out, ':')
110 if len(p.indent) > 0 {
111 p.out = append(p.out, ' ')
Herbie Ong4eb4d612019-09-06 17:15:54 -0700112 // For multi-line output, add a random extra space after key:
113 // to make output unstable.
114 if detrand.Bool() {
115 p.out = append(p.out, ' ')
116 }
Herbie Ongc3f4d482018-11-22 14:29:07 -0800117 }
118
Damien Neil8c86fc52019-06-19 09:28:29 -0700119 if err := p.marshalValue(item[1]); err != nil {
Joe Tsai27c2a762018-08-01 16:48:18 -0700120 return err
121 }
122 if i < len(items)-1 && len(p.indent) == 0 {
123 p.out = append(p.out, ' ')
Herbie Ong4eb4d612019-09-06 17:15:54 -0700124 // For single-line output, add a random extra space after a field
125 // to make output unstable.
126 if detrand.Bool() {
127 p.out = append(p.out, ' ')
128 }
Herbie Ongc3f4d482018-11-22 14:29:07 -0800129 }
Joe Tsai27c2a762018-08-01 16:48:18 -0700130 p.out = append(p.out, p.newline...)
131 }
132 if emitDelims {
133 p.indents = p.indents[:len(p.indents)-len(p.indent)]
134 if len(items) > 0 {
135 p.out = append(p.out, p.indents...)
136 }
137 p.out = append(p.out, p.delims[1])
138 }
139 return nil
140}
141
Herbie Onga3369c52019-04-23 00:24:46 -0700142// This expression is more liberal than ConsumeAnyTypeUrl in C++.
143// However, the C++ parser does not handle many legal URL strings.
144// The Go implementation is more liberal to be backwards compatible with
145// the historical Go implementation which was overly liberal (and buggy).
146var urlRegexp = regexp.MustCompile(`^[-_a-zA-Z0-9]+([./][-_a-zA-Z0-9]+)*`)
147
Joe Tsai27c2a762018-08-01 16:48:18 -0700148func (p *encoder) marshalKey(v Value) error {
149 switch v.Type() {
150 case String:
151 var err error
152 p.out = append(p.out, '[')
153 if len(urlRegexp.FindString(v.str)) == len(v.str) {
154 p.out = append(p.out, v.str...)
155 } else {
156 err = p.marshalString(v)
157 }
158 p.out = append(p.out, ']')
159 return err
160 case Uint:
161 return p.marshalNumber(v)
162 case Name:
163 s, _ := v.Name()
164 p.out = append(p.out, s...)
165 return nil
166 default:
167 return errors.New("invalid type %v to encode key", v.Type())
168 }
169}
170
171func (p *encoder) marshalValue(v Value) error {
172 switch v.Type() {
Herbie Ong84f09602019-01-17 19:31:47 -0800173 case Bool, Int, Uint, Float32, Float64:
Joe Tsai27c2a762018-08-01 16:48:18 -0700174 return p.marshalNumber(v)
175 case String:
176 return p.marshalString(v)
177 case List:
178 return p.marshalList(v)
179 case Message:
180 return p.marshalMessage(v, true)
181 case Name:
182 s, _ := v.Name()
183 p.out = append(p.out, s...)
184 return nil
185 default:
186 return errors.New("invalid type %v to encode value", v.Type())
187 }
188}