blob: a15df02281aaab1c204406be21757f2860d7cdae [file] [log] [blame]
Joe Tsaic9899da2018-12-06 18:34:53 -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
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
Damien Neile89e6242019-05-13 23:55:40 -070017 ptext "google.golang.org/protobuf/internal/encoding/text"
18 errors "google.golang.org/protobuf/internal/errors"
19 pref "google.golang.org/protobuf/reflect/protoreflect"
Joe Tsaic9899da2018-12-06 18:34:53 -080020)
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 using the Descriptor format on an enum kind, a Value of type string
38// representing the enum identifier is returned. It is the caller's
39// responsibility to verify that the identifier is valid.
40func Unmarshal(s string, k pref.Kind, f Format) (pref.Value, error) {
41 switch k {
42 case pref.BoolKind:
43 if f == GoTag {
44 switch s {
45 case "1":
46 return pref.ValueOf(true), nil
47 case "0":
48 return pref.ValueOf(false), nil
49 }
50 } else {
51 switch s {
52 case "true":
53 return pref.ValueOf(true), nil
54 case "false":
55 return pref.ValueOf(false), nil
56 }
57 }
58 case pref.EnumKind:
59 if f == GoTag {
60 // Go tags used the numeric form of the enum value.
61 if n, err := strconv.ParseInt(s, 10, 32); err == nil {
62 return pref.ValueOf(pref.EnumNumber(n)), nil
63 }
64 } else {
65 // Descriptor default_value used the enum identifier.
66 if pref.Name(s).IsValid() {
67 return pref.ValueOf(s), nil
68 }
69 }
70 case pref.Int32Kind, pref.Sint32Kind, pref.Sfixed32Kind:
71 if v, err := strconv.ParseInt(s, 10, 32); err == nil {
72 return pref.ValueOf(int32(v)), nil
73 }
74 case pref.Int64Kind, pref.Sint64Kind, pref.Sfixed64Kind:
75 if v, err := strconv.ParseInt(s, 10, 64); err == nil {
76 return pref.ValueOf(int64(v)), nil
77 }
78 case pref.Uint32Kind, pref.Fixed32Kind:
79 if v, err := strconv.ParseUint(s, 10, 32); err == nil {
80 return pref.ValueOf(uint32(v)), nil
81 }
82 case pref.Uint64Kind, pref.Fixed64Kind:
83 if v, err := strconv.ParseUint(s, 10, 64); err == nil {
84 return pref.ValueOf(uint64(v)), nil
85 }
86 case pref.FloatKind, pref.DoubleKind:
87 var v float64
88 var err error
89 switch s {
90 case "-inf":
91 v = math.Inf(-1)
92 case "inf":
93 v = math.Inf(+1)
94 case "nan":
95 v = math.NaN()
96 default:
97 v, err = strconv.ParseFloat(s, 64)
98 }
99 if err == nil {
100 if k == pref.FloatKind {
101 return pref.ValueOf(float32(v)), nil
102 } else {
103 return pref.ValueOf(float64(v)), nil
104 }
105 }
106 case pref.StringKind:
107 // String values are already unescaped and can be used as is.
108 return pref.ValueOf(s), nil
109 case pref.BytesKind:
110 if b, ok := unmarshalBytes(s); ok {
111 return pref.ValueOf(b), nil
112 }
113 }
114 return pref.Value{}, errors.New("invalid default value for %v: %q", k, s)
115}
116
117// Marshal serializes v as the default string according to the given kind k.
118// Enums are serialized in numeric form regardless of format chosen.
119func Marshal(v pref.Value, k pref.Kind, f Format) (string, error) {
120 switch k {
121 case pref.BoolKind:
122 if f == GoTag {
123 if v.Bool() {
124 return "1", nil
125 } else {
126 return "0", nil
127 }
128 } else {
129 if v.Bool() {
130 return "true", nil
131 } else {
132 return "false", nil
133 }
134 }
135 case pref.EnumKind:
136 return strconv.FormatInt(int64(v.Enum()), 10), nil
137 case pref.Int32Kind, pref.Sint32Kind, pref.Sfixed32Kind, pref.Int64Kind, pref.Sint64Kind, pref.Sfixed64Kind:
138 return strconv.FormatInt(v.Int(), 10), nil
139 case pref.Uint32Kind, pref.Fixed32Kind, pref.Uint64Kind, pref.Fixed64Kind:
140 return strconv.FormatUint(v.Uint(), 10), nil
141 case pref.FloatKind, pref.DoubleKind:
142 f := v.Float()
143 switch {
144 case math.IsInf(f, -1):
145 return "-inf", nil
146 case math.IsInf(f, +1):
147 return "inf", nil
148 case math.IsNaN(f):
149 return "nan", nil
150 default:
151 if k == pref.FloatKind {
152 return strconv.FormatFloat(f, 'g', -1, 32), nil
153 } else {
154 return strconv.FormatFloat(f, 'g', -1, 64), nil
155 }
156 }
157 case pref.StringKind:
158 // String values are serialized as is without any escaping.
159 return v.String(), nil
160 case pref.BytesKind:
161 if s, ok := marshalBytes(v.Bytes()); ok {
162 return s, nil
163 }
164 }
165 return "", errors.New("invalid default value for %v: %v", k, v)
166}
167
168// unmarshalBytes deserializes bytes by applying C unescaping.
169func unmarshalBytes(s string) ([]byte, bool) {
170 // Bytes values use the same escaping as the text format,
171 // however they lack the surrounding double quotes.
172 // TODO: Export unmarshalString in the text package to avoid this hack.
173 v, err := ptext.Unmarshal([]byte(`["` + s + `"]:0`))
174 if err == nil && len(v.Message()) == 1 {
175 s := v.Message()[0][0].String()
176 return []byte(s), true
177 }
178 return nil, false
179}
180
181// marshalBytes serializes bytes by using C escaping.
182// To match the exact output of protoc, this is identical to the
183// CEscape function in strutil.cc of the protoc source code.
184func marshalBytes(b []byte) (string, bool) {
185 var s []byte
186 for _, c := range b {
187 switch c {
188 case '\n':
189 s = append(s, `\n`...)
190 case '\r':
191 s = append(s, `\r`...)
192 case '\t':
193 s = append(s, `\t`...)
194 case '"':
195 s = append(s, `\"`...)
196 case '\'':
197 s = append(s, `\'`...)
198 case '\\':
199 s = append(s, `\\`...)
200 default:
201 if printableASCII := c >= 0x20 && c <= 0x7e; printableASCII {
202 s = append(s, c)
203 } else {
204 s = append(s, fmt.Sprintf(`\%03o`, c)...)
205 }
206 }
207 }
208 return string(s), true
209}