| // 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. |
| v, err := ptext.UnmarshalString(`"` + s + `"`) |
| if err != nil { |
| return nil, false |
| } |
| return []byte(v), true |
| } |
| |
| // 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 |
| } |