// 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 }