// 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 "google.golang.org/protobuf/internal/encoding/text" errors "google.golang.org/protobuf/internal/errors" pref "google.golang.org/protobuf/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 k is an enum, a list of enum value descriptors must be provided. func Unmarshal(s string, k pref.Kind, evs pref.EnumValueDescriptors, f Format) (pref.Value, pref.EnumValueDescriptor, error) { switch k { case pref.BoolKind: if f == GoTag { switch s { case "1": return pref.ValueOfBool(true), nil, nil case "0": return pref.ValueOfBool(false), nil, nil } } else { switch s { case "true": return pref.ValueOfBool(true), nil, nil case "false": return pref.ValueOfBool(false), nil, nil } } case pref.EnumKind: if f == GoTag { // Go tags use the numeric form of the enum value. if n, err := strconv.ParseInt(s, 10, 32); err == nil { if ev := evs.ByNumber(pref.EnumNumber(n)); ev != nil { return pref.ValueOfEnum(ev.Number()), ev, nil } } } else { // Descriptor default_value use the enum identifier. ev := evs.ByName(pref.Name(s)) if ev != nil { return pref.ValueOfEnum(ev.Number()), ev, nil } } case pref.Int32Kind, pref.Sint32Kind, pref.Sfixed32Kind: if v, err := strconv.ParseInt(s, 10, 32); err == nil { return pref.ValueOfInt32(int32(v)), nil, nil } case pref.Int64Kind, pref.Sint64Kind, pref.Sfixed64Kind: if v, err := strconv.ParseInt(s, 10, 64); err == nil { return pref.ValueOfInt64(int64(v)), nil, nil } case pref.Uint32Kind, pref.Fixed32Kind: if v, err := strconv.ParseUint(s, 10, 32); err == nil { return pref.ValueOfUint32(uint32(v)), nil, nil } case pref.Uint64Kind, pref.Fixed64Kind: if v, err := strconv.ParseUint(s, 10, 64); err == nil { return pref.ValueOfUint64(uint64(v)), nil, 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.ValueOfFloat32(float32(v)), nil, nil } else { return pref.ValueOfFloat64(float64(v)), nil, nil } } case pref.StringKind: // String values are already unescaped and can be used as is. return pref.ValueOfString(s), nil, nil case pref.BytesKind: if b, ok := unmarshalBytes(s); ok { return pref.ValueOfBytes(b), nil, nil } } return pref.Value{}, nil, errors.New("could not parse value for %v: %q", k, s) } // Marshal serializes v as the default string according to the given kind k. // When specifying the Descriptor format for an enum kind, the associated // enum value descriptor must be provided. func Marshal(v pref.Value, ev pref.EnumValueDescriptor, 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: if f == GoTag { return strconv.FormatInt(int64(v.Enum()), 10), nil } else { return string(ev.Name()), 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("could not format 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 }