blob: fdd9b13f2fcfff920328fd36823e6de0d9f200d0 [file] [log] [blame]
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +00001// 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
5// Package defval marshals and unmarshals textual forms of default values.
6//
7// This package handles both the form historically used in Go struct field tags
8// and also the form used by google.protobuf.FieldDescriptorProto.default_value
9// since they differ in superficial ways.
10package defval
11
12import (
13 "fmt"
14 "math"
15 "strconv"
16
17 ptext "google.golang.org/protobuf/internal/encoding/text"
18 errors "google.golang.org/protobuf/internal/errors"
19 pref "google.golang.org/protobuf/reflect/protoreflect"
20)
21
22// Format is the serialization format used to represent the default value.
23type Format int
24
25const (
26 _ Format = iota
27
28 // Descriptor uses the serialization format that protoc uses with the
29 // google.protobuf.FieldDescriptorProto.default_value field.
30 Descriptor
31
32 // GoTag uses the historical serialization format in Go struct field tags.
33 GoTag
34)
35
36// Unmarshal deserializes the default string s according to the given kind k.
37// When k is an enum, a list of enum value descriptors must be provided.
38func Unmarshal(s string, k pref.Kind, evs pref.EnumValueDescriptors, f Format) (pref.Value, pref.EnumValueDescriptor, error) {
39 switch k {
40 case pref.BoolKind:
41 if f == GoTag {
42 switch s {
43 case "1":
44 return pref.ValueOfBool(true), nil, nil
45 case "0":
46 return pref.ValueOfBool(false), nil, nil
47 }
48 } else {
49 switch s {
50 case "true":
51 return pref.ValueOfBool(true), nil, nil
52 case "false":
53 return pref.ValueOfBool(false), nil, nil
54 }
55 }
56 case pref.EnumKind:
57 if f == GoTag {
58 // Go tags use the numeric form of the enum value.
59 if n, err := strconv.ParseInt(s, 10, 32); err == nil {
60 if ev := evs.ByNumber(pref.EnumNumber(n)); ev != nil {
61 return pref.ValueOfEnum(ev.Number()), ev, nil
62 }
63 }
64 } else {
65 // Descriptor default_value use the enum identifier.
66 ev := evs.ByName(pref.Name(s))
67 if ev != nil {
68 return pref.ValueOfEnum(ev.Number()), ev, nil
69 }
70 }
71 case pref.Int32Kind, pref.Sint32Kind, pref.Sfixed32Kind:
72 if v, err := strconv.ParseInt(s, 10, 32); err == nil {
73 return pref.ValueOfInt32(int32(v)), nil, nil
74 }
75 case pref.Int64Kind, pref.Sint64Kind, pref.Sfixed64Kind:
76 if v, err := strconv.ParseInt(s, 10, 64); err == nil {
77 return pref.ValueOfInt64(int64(v)), nil, nil
78 }
79 case pref.Uint32Kind, pref.Fixed32Kind:
80 if v, err := strconv.ParseUint(s, 10, 32); err == nil {
81 return pref.ValueOfUint32(uint32(v)), nil, nil
82 }
83 case pref.Uint64Kind, pref.Fixed64Kind:
84 if v, err := strconv.ParseUint(s, 10, 64); err == nil {
85 return pref.ValueOfUint64(uint64(v)), nil, nil
86 }
87 case pref.FloatKind, pref.DoubleKind:
88 var v float64
89 var err error
90 switch s {
91 case "-inf":
92 v = math.Inf(-1)
93 case "inf":
94 v = math.Inf(+1)
95 case "nan":
96 v = math.NaN()
97 default:
98 v, err = strconv.ParseFloat(s, 64)
99 }
100 if err == nil {
101 if k == pref.FloatKind {
102 return pref.ValueOfFloat32(float32(v)), nil, nil
103 } else {
104 return pref.ValueOfFloat64(float64(v)), nil, nil
105 }
106 }
107 case pref.StringKind:
108 // String values are already unescaped and can be used as is.
109 return pref.ValueOfString(s), nil, nil
110 case pref.BytesKind:
111 if b, ok := unmarshalBytes(s); ok {
112 return pref.ValueOfBytes(b), nil, nil
113 }
114 }
115 return pref.Value{}, nil, errors.New("could not parse value for %v: %q", k, s)
116}
117
118// Marshal serializes v as the default string according to the given kind k.
119// When specifying the Descriptor format for an enum kind, the associated
120// enum value descriptor must be provided.
121func Marshal(v pref.Value, ev pref.EnumValueDescriptor, k pref.Kind, f Format) (string, error) {
122 switch k {
123 case pref.BoolKind:
124 if f == GoTag {
125 if v.Bool() {
126 return "1", nil
127 } else {
128 return "0", nil
129 }
130 } else {
131 if v.Bool() {
132 return "true", nil
133 } else {
134 return "false", nil
135 }
136 }
137 case pref.EnumKind:
138 if f == GoTag {
139 return strconv.FormatInt(int64(v.Enum()), 10), nil
140 } else {
141 return string(ev.Name()), nil
142 }
143 case pref.Int32Kind, pref.Sint32Kind, pref.Sfixed32Kind, pref.Int64Kind, pref.Sint64Kind, pref.Sfixed64Kind:
144 return strconv.FormatInt(v.Int(), 10), nil
145 case pref.Uint32Kind, pref.Fixed32Kind, pref.Uint64Kind, pref.Fixed64Kind:
146 return strconv.FormatUint(v.Uint(), 10), nil
147 case pref.FloatKind, pref.DoubleKind:
148 f := v.Float()
149 switch {
150 case math.IsInf(f, -1):
151 return "-inf", nil
152 case math.IsInf(f, +1):
153 return "inf", nil
154 case math.IsNaN(f):
155 return "nan", nil
156 default:
157 if k == pref.FloatKind {
158 return strconv.FormatFloat(f, 'g', -1, 32), nil
159 } else {
160 return strconv.FormatFloat(f, 'g', -1, 64), nil
161 }
162 }
163 case pref.StringKind:
164 // String values are serialized as is without any escaping.
165 return v.String(), nil
166 case pref.BytesKind:
167 if s, ok := marshalBytes(v.Bytes()); ok {
168 return s, nil
169 }
170 }
171 return "", errors.New("could not format value for %v: %v", k, v)
172}
173
174// unmarshalBytes deserializes bytes by applying C unescaping.
175func unmarshalBytes(s string) ([]byte, bool) {
176 // Bytes values use the same escaping as the text format,
177 // however they lack the surrounding double quotes.
178 v, err := ptext.UnmarshalString(`"` + s + `"`)
179 if err != nil {
180 return nil, false
181 }
182 return []byte(v), true
183}
184
185// marshalBytes serializes bytes by using C escaping.
186// To match the exact output of protoc, this is identical to the
187// CEscape function in strutil.cc of the protoc source code.
188func marshalBytes(b []byte) (string, bool) {
189 var s []byte
190 for _, c := range b {
191 switch c {
192 case '\n':
193 s = append(s, `\n`...)
194 case '\r':
195 s = append(s, `\r`...)
196 case '\t':
197 s = append(s, `\t`...)
198 case '"':
199 s = append(s, `\"`...)
200 case '\'':
201 s = append(s, `\'`...)
202 case '\\':
203 s = append(s, `\\`...)
204 default:
205 if printableASCII := c >= 0x20 && c <= 0x7e; printableASCII {
206 s = append(s, c)
207 } else {
208 s = append(s, fmt.Sprintf(`\%03o`, c)...)
209 }
210 }
211 }
212 return string(s), true
213}