|
|
@@ -0,0 +1,209 @@
|
|
|
+// Copyright 2018 The Go Authors. All rights reserved.
|
|
|
+// Use of this source code is governed by a BSD-style
|
|
|
+// license that can be found in the LICENSE file.
|
|
|
+
|
|
|
+// Package defval marshals and unmarshals textual forms of default values.
|
|
|
+//
|
|
|
+// This package handles both the form historically used in Go struct field tags
|
|
|
+// and also the form used by google.protobuf.FieldDescriptorProto.default_value
|
|
|
+// since they differ in superficial ways.
|
|
|
+package defval
|
|
|
+
|
|
|
+import (
|
|
|
+ "fmt"
|
|
|
+ "math"
|
|
|
+ "strconv"
|
|
|
+
|
|
|
+ ptext "github.com/golang/protobuf/v2/internal/encoding/text"
|
|
|
+ errors "github.com/golang/protobuf/v2/internal/errors"
|
|
|
+ pref "github.com/golang/protobuf/v2/reflect/protoreflect"
|
|
|
+)
|
|
|
+
|
|
|
+// Format is the serialization format used to represent the default value.
|
|
|
+type Format int
|
|
|
+
|
|
|
+const (
|
|
|
+ _ Format = iota
|
|
|
+
|
|
|
+ // Descriptor uses the serialization format that protoc uses with the
|
|
|
+ // google.protobuf.FieldDescriptorProto.default_value field.
|
|
|
+ Descriptor
|
|
|
+
|
|
|
+ // GoTag uses the historical serialization format in Go struct field tags.
|
|
|
+ GoTag
|
|
|
+)
|
|
|
+
|
|
|
+// Unmarshal deserializes the default string s according to the given kind k.
|
|
|
+// When using the Descriptor format on an enum kind, a Value of type string
|
|
|
+// representing the enum identifier is returned. It is the caller's
|
|
|
+// responsibility to verify that the identifier is valid.
|
|
|
+func Unmarshal(s string, k pref.Kind, f Format) (pref.Value, error) {
|
|
|
+ switch k {
|
|
|
+ case pref.BoolKind:
|
|
|
+ if f == GoTag {
|
|
|
+ switch s {
|
|
|
+ case "1":
|
|
|
+ return pref.ValueOf(true), nil
|
|
|
+ case "0":
|
|
|
+ return pref.ValueOf(false), nil
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ switch s {
|
|
|
+ case "true":
|
|
|
+ return pref.ValueOf(true), nil
|
|
|
+ case "false":
|
|
|
+ return pref.ValueOf(false), nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ case pref.EnumKind:
|
|
|
+ if f == GoTag {
|
|
|
+ // Go tags used the numeric form of the enum value.
|
|
|
+ if n, err := strconv.ParseInt(s, 10, 32); err == nil {
|
|
|
+ return pref.ValueOf(pref.EnumNumber(n)), nil
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // Descriptor default_value used the enum identifier.
|
|
|
+ if pref.Name(s).IsValid() {
|
|
|
+ return pref.ValueOf(s), nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ case pref.Int32Kind, pref.Sint32Kind, pref.Sfixed32Kind:
|
|
|
+ if v, err := strconv.ParseInt(s, 10, 32); err == nil {
|
|
|
+ return pref.ValueOf(int32(v)), nil
|
|
|
+ }
|
|
|
+ case pref.Int64Kind, pref.Sint64Kind, pref.Sfixed64Kind:
|
|
|
+ if v, err := strconv.ParseInt(s, 10, 64); err == nil {
|
|
|
+ return pref.ValueOf(int64(v)), nil
|
|
|
+ }
|
|
|
+ case pref.Uint32Kind, pref.Fixed32Kind:
|
|
|
+ if v, err := strconv.ParseUint(s, 10, 32); err == nil {
|
|
|
+ return pref.ValueOf(uint32(v)), nil
|
|
|
+ }
|
|
|
+ case pref.Uint64Kind, pref.Fixed64Kind:
|
|
|
+ if v, err := strconv.ParseUint(s, 10, 64); err == nil {
|
|
|
+ return pref.ValueOf(uint64(v)), nil
|
|
|
+ }
|
|
|
+ case pref.FloatKind, pref.DoubleKind:
|
|
|
+ var v float64
|
|
|
+ var err error
|
|
|
+ switch s {
|
|
|
+ case "-inf":
|
|
|
+ v = math.Inf(-1)
|
|
|
+ case "inf":
|
|
|
+ v = math.Inf(+1)
|
|
|
+ case "nan":
|
|
|
+ v = math.NaN()
|
|
|
+ default:
|
|
|
+ v, err = strconv.ParseFloat(s, 64)
|
|
|
+ }
|
|
|
+ if err == nil {
|
|
|
+ if k == pref.FloatKind {
|
|
|
+ return pref.ValueOf(float32(v)), nil
|
|
|
+ } else {
|
|
|
+ return pref.ValueOf(float64(v)), nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ case pref.StringKind:
|
|
|
+ // String values are already unescaped and can be used as is.
|
|
|
+ return pref.ValueOf(s), nil
|
|
|
+ case pref.BytesKind:
|
|
|
+ if b, ok := unmarshalBytes(s); ok {
|
|
|
+ return pref.ValueOf(b), nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return pref.Value{}, errors.New("invalid default value for %v: %q", k, s)
|
|
|
+}
|
|
|
+
|
|
|
+// Marshal serializes v as the default string according to the given kind k.
|
|
|
+// Enums are serialized in numeric form regardless of format chosen.
|
|
|
+func Marshal(v pref.Value, k pref.Kind, f Format) (string, error) {
|
|
|
+ switch k {
|
|
|
+ case pref.BoolKind:
|
|
|
+ if f == GoTag {
|
|
|
+ if v.Bool() {
|
|
|
+ return "1", nil
|
|
|
+ } else {
|
|
|
+ return "0", nil
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if v.Bool() {
|
|
|
+ return "true", nil
|
|
|
+ } else {
|
|
|
+ return "false", nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ case pref.EnumKind:
|
|
|
+ return strconv.FormatInt(int64(v.Enum()), 10), nil
|
|
|
+ case pref.Int32Kind, pref.Sint32Kind, pref.Sfixed32Kind, pref.Int64Kind, pref.Sint64Kind, pref.Sfixed64Kind:
|
|
|
+ return strconv.FormatInt(v.Int(), 10), nil
|
|
|
+ case pref.Uint32Kind, pref.Fixed32Kind, pref.Uint64Kind, pref.Fixed64Kind:
|
|
|
+ return strconv.FormatUint(v.Uint(), 10), nil
|
|
|
+ case pref.FloatKind, pref.DoubleKind:
|
|
|
+ f := v.Float()
|
|
|
+ switch {
|
|
|
+ case math.IsInf(f, -1):
|
|
|
+ return "-inf", nil
|
|
|
+ case math.IsInf(f, +1):
|
|
|
+ return "inf", nil
|
|
|
+ case math.IsNaN(f):
|
|
|
+ return "nan", nil
|
|
|
+ default:
|
|
|
+ if k == pref.FloatKind {
|
|
|
+ return strconv.FormatFloat(f, 'g', -1, 32), nil
|
|
|
+ } else {
|
|
|
+ return strconv.FormatFloat(f, 'g', -1, 64), nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ case pref.StringKind:
|
|
|
+ // String values are serialized as is without any escaping.
|
|
|
+ return v.String(), nil
|
|
|
+ case pref.BytesKind:
|
|
|
+ if s, ok := marshalBytes(v.Bytes()); ok {
|
|
|
+ return s, nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return "", errors.New("invalid default value for %v: %v", k, v)
|
|
|
+}
|
|
|
+
|
|
|
+// unmarshalBytes deserializes bytes by applying C unescaping.
|
|
|
+func unmarshalBytes(s string) ([]byte, bool) {
|
|
|
+ // Bytes values use the same escaping as the text format,
|
|
|
+ // however they lack the surrounding double quotes.
|
|
|
+ // TODO: Export unmarshalString in the text package to avoid this hack.
|
|
|
+ v, err := ptext.Unmarshal([]byte(`["` + s + `"]:0`))
|
|
|
+ if err == nil && len(v.Message()) == 1 {
|
|
|
+ s := v.Message()[0][0].String()
|
|
|
+ return []byte(s), true
|
|
|
+ }
|
|
|
+ return nil, false
|
|
|
+}
|
|
|
+
|
|
|
+// marshalBytes serializes bytes by using C escaping.
|
|
|
+// To match the exact output of protoc, this is identical to the
|
|
|
+// CEscape function in strutil.cc of the protoc source code.
|
|
|
+func marshalBytes(b []byte) (string, bool) {
|
|
|
+ var s []byte
|
|
|
+ for _, c := range b {
|
|
|
+ switch c {
|
|
|
+ case '\n':
|
|
|
+ s = append(s, `\n`...)
|
|
|
+ case '\r':
|
|
|
+ s = append(s, `\r`...)
|
|
|
+ case '\t':
|
|
|
+ s = append(s, `\t`...)
|
|
|
+ case '"':
|
|
|
+ s = append(s, `\"`...)
|
|
|
+ case '\'':
|
|
|
+ s = append(s, `\'`...)
|
|
|
+ case '\\':
|
|
|
+ s = append(s, `\\`...)
|
|
|
+ default:
|
|
|
+ if printableASCII := c >= 0x20 && c <= 0x7e; printableASCII {
|
|
|
+ s = append(s, c)
|
|
|
+ } else {
|
|
|
+ s = append(s, fmt.Sprintf(`\%03o`, c)...)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return string(s), true
|
|
|
+}
|