blob: 5d00cc2c8607ff1efdd721a3675b8340a4c8bd9d [file] [log] [blame]
Herbie Ongcddf8192018-11-28 18:25:20 -08001// 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 textpb
6
7import (
8 "sort"
9
10 "github.com/golang/protobuf/v2/internal/encoding/text"
11 "github.com/golang/protobuf/v2/internal/errors"
12 "github.com/golang/protobuf/v2/internal/pragma"
13 "github.com/golang/protobuf/v2/proto"
14 pref "github.com/golang/protobuf/v2/reflect/protoreflect"
15)
16
Herbie Ong800c9902018-12-06 15:28:53 -080017// Marshal writes the given proto.Message in textproto format using default options.
Herbie Ongcddf8192018-11-28 18:25:20 -080018// TODO: may want to describe when Marshal returns error.
19func Marshal(m proto.Message) ([]byte, error) {
20 return MarshalOptions{}.Marshal(m)
21}
22
23// MarshalOptions is a configurable text format marshaler.
24type MarshalOptions struct {
25 pragma.NoUnkeyedLiterals
26
27 // Set Compact to true to have output in a single line with no line breaks.
28 Compact bool
29}
30
Herbie Ong800c9902018-12-06 15:28:53 -080031// Marshal writes the given proto.Message in textproto format using options in MarshalOptions object.
Herbie Ongcddf8192018-11-28 18:25:20 -080032func (o MarshalOptions) Marshal(m proto.Message) ([]byte, error) {
33 var nerr errors.NonFatal
34 var v text.Value
35
Herbie Ong800c9902018-12-06 15:28:53 -080036 var err error
37 v, err = o.marshalMessage(m.ProtoReflect())
38 if !nerr.Merge(err) {
39 return nil, err
Herbie Ongcddf8192018-11-28 18:25:20 -080040 }
41
42 indent := " "
43 if o.Compact {
44 indent = ""
45 }
46 delims := [2]byte{'{', '}'}
47
48 const outputASCII = false
49 b, err := text.Marshal(v, indent, delims, outputASCII)
50 if !nerr.Merge(err) {
51 return nil, err
52 }
53 return b, nerr.E
54}
55
56// marshalMessage converts a protoreflect.Message to a text.Value.
57func (o MarshalOptions) marshalMessage(m pref.Message) (text.Value, error) {
58 var nerr errors.NonFatal
59 var msgFields [][2]text.Value
60
61 // Handle known fields.
62 msgType := m.Type()
63 fieldDescs := msgType.Fields()
64 knownFields := m.KnownFields()
65 size := fieldDescs.Len()
66 for i := 0; i < size; i++ {
Herbie Ong800c9902018-12-06 15:28:53 -080067 fd := fieldDescs.Get(i)
68 num := fd.Number()
Herbie Ongcddf8192018-11-28 18:25:20 -080069
Herbie Ong800c9902018-12-06 15:28:53 -080070 if !knownFields.Has(num) {
71 if fd.Cardinality() == pref.Required {
Herbie Ongcddf8192018-11-28 18:25:20 -080072 // Treat unset required fields as a non-fatal error.
Herbie Ong800c9902018-12-06 15:28:53 -080073 nerr.AppendRequiredNotSet(string(fd.FullName()))
Herbie Ongcddf8192018-11-28 18:25:20 -080074 }
75 continue
76 }
77
Herbie Ong800c9902018-12-06 15:28:53 -080078 tname := text.ValueOf(fd.Name())
79 pval := knownFields.Get(num)
Herbie Ongcddf8192018-11-28 18:25:20 -080080
Herbie Ong800c9902018-12-06 15:28:53 -080081 if fd.Cardinality() == pref.Repeated {
Herbie Ongcddf8192018-11-28 18:25:20 -080082 // Map or repeated fields.
83 var items []text.Value
84 var err error
Herbie Ong800c9902018-12-06 15:28:53 -080085 if fd.IsMap() {
86 items, err = o.marshalMap(pval.Map(), fd)
Herbie Ongcddf8192018-11-28 18:25:20 -080087 if !nerr.Merge(err) {
88 return text.Value{}, err
89 }
90 } else {
Herbie Ong800c9902018-12-06 15:28:53 -080091 items, err = o.marshalList(pval.List(), fd)
Herbie Ongcddf8192018-11-28 18:25:20 -080092 if !nerr.Merge(err) {
93 return text.Value{}, err
94 }
95 }
96
97 // Add each item as key: value field.
98 for _, item := range items {
Herbie Ong800c9902018-12-06 15:28:53 -080099 msgFields = append(msgFields, [2]text.Value{tname, item})
Herbie Ongcddf8192018-11-28 18:25:20 -0800100 }
101 } else {
102 // Required or optional fields.
Herbie Ong800c9902018-12-06 15:28:53 -0800103 tval, err := o.marshalSingular(pval, fd)
Herbie Ongcddf8192018-11-28 18:25:20 -0800104 if !nerr.Merge(err) {
105 return text.Value{}, err
106 }
Herbie Ong800c9902018-12-06 15:28:53 -0800107 msgFields = append(msgFields, [2]text.Value{tname, tval})
Herbie Ongcddf8192018-11-28 18:25:20 -0800108 }
109
110 }
111
112 // TODO: Handle extensions, unknowns and Any.
113
114 return text.ValueOf(msgFields), nerr.E
115}
116
117// marshalSingular converts a non-repeated field value to text.Value.
118// This includes all scalar types, enums, messages, and groups.
119func (o MarshalOptions) marshalSingular(val pref.Value, fd pref.FieldDescriptor) (text.Value, error) {
120 kind := fd.Kind()
121 switch kind {
122 case pref.BoolKind,
123 pref.Int32Kind, pref.Sint32Kind, pref.Uint32Kind,
124 pref.Int64Kind, pref.Sint64Kind, pref.Uint64Kind,
125 pref.Sfixed32Kind, pref.Fixed32Kind,
126 pref.Sfixed64Kind, pref.Fixed64Kind,
127 pref.FloatKind, pref.DoubleKind,
128 pref.StringKind, pref.BytesKind:
129 return text.ValueOf(val.Interface()), nil
130
131 case pref.EnumKind:
132 num := val.Enum()
133 if desc := fd.EnumType().Values().ByNumber(num); desc != nil {
134 return text.ValueOf(desc.Name()), nil
135 }
136 // Use numeric value if there is no enum description.
137 return text.ValueOf(int32(num)), nil
138
139 case pref.MessageKind, pref.GroupKind:
140 return o.marshalMessage(val.Message())
141 }
142
143 return text.Value{}, errors.New("%v has unknown kind: %v", fd.FullName(), kind)
144}
145
146// marshalList converts a protoreflect.List to []text.Value.
147func (o MarshalOptions) marshalList(list pref.List, fd pref.FieldDescriptor) ([]text.Value, error) {
148 var nerr errors.NonFatal
149 size := list.Len()
150 values := make([]text.Value, 0, size)
151
152 for i := 0; i < size; i++ {
153 item := list.Get(i)
154 val, err := o.marshalSingular(item, fd)
155 if !nerr.Merge(err) {
156 // Return already marshaled values.
157 return values, err
158 }
159 values = append(values, val)
160 }
161
162 return values, nerr.E
163}
164
165var (
166 mapKeyName = text.ValueOf(pref.Name("key"))
167 mapValueName = text.ValueOf(pref.Name("value"))
168)
169
170// marshalMap converts a protoreflect.Map to []text.Value.
171func (o MarshalOptions) marshalMap(mmap pref.Map, fd pref.FieldDescriptor) ([]text.Value, error) {
172 var nerr errors.NonFatal
173 // values is a list of messages.
174 values := make([]text.Value, 0, mmap.Len())
175 msgFields := fd.MessageType().Fields()
176 keyType := msgFields.ByNumber(1)
177 valType := msgFields.ByNumber(2)
178
179 mmap.Range(func(key pref.MapKey, val pref.Value) bool {
180 keyTxtVal, err := o.marshalSingular(key.Value(), keyType)
181 if !nerr.Merge(err) {
182 return false
183 }
184 valTxtVal, err := o.marshalSingular(val, valType)
185 if !nerr.Merge(err) {
186 return false
187 }
188 // Map entry (message) contains 2 fields, first field for key and second field for value.
189 msg := text.ValueOf([][2]text.Value{
190 {mapKeyName, keyTxtVal},
191 {mapValueName, valTxtVal},
192 })
193 values = append(values, msg)
194 return true
195 })
196
197 sortMap(keyType.Kind(), values)
198 return values, nerr.E
199}
200
201// sortMap orders list based on value of key field for deterministic output.
202// TODO: Improve sort comparison of text.Value for map keys.
203func sortMap(keyKind pref.Kind, values []text.Value) {
204 less := func(i, j int) bool {
205 mi := values[i].Message()
206 mj := values[j].Message()
207 return mi[0][1].String() < mj[0][1].String()
208 }
209 switch keyKind {
210 case pref.Int32Kind, pref.Sint32Kind, pref.Sfixed32Kind:
211 less = func(i, j int) bool {
212 mi := values[i].Message()
213 mj := values[j].Message()
214 ni, _ := mi[0][1].Int(false)
215 nj, _ := mj[0][1].Int(false)
216 return ni < nj
217 }
218 case pref.Int64Kind, pref.Sint64Kind, pref.Sfixed64Kind:
219 less = func(i, j int) bool {
220 mi := values[i].Message()
221 mj := values[j].Message()
222 ni, _ := mi[0][1].Int(true)
223 nj, _ := mj[0][1].Int(true)
224 return ni < nj
225 }
226
227 case pref.Uint32Kind, pref.Fixed32Kind:
228 less = func(i, j int) bool {
229 mi := values[i].Message()
230 mj := values[j].Message()
231 ni, _ := mi[0][1].Uint(false)
232 nj, _ := mj[0][1].Uint(false)
233 return ni < nj
234 }
235 case pref.Uint64Kind, pref.Fixed64Kind:
236 less = func(i, j int) bool {
237 mi := values[i].Message()
238 mj := values[j].Message()
239 ni, _ := mi[0][1].Uint(true)
240 nj, _ := mj[0][1].Uint(true)
241 return ni < nj
242 }
243 }
244 sort.Slice(values, less)
245}