feat: accept camelcase for config keys (#2651)

* feat: accept camelcase for config keys

* chore: refactor

* chore: refactor

* chore: add more tests

* chore: refactor

* fix: map elements of array
This commit is contained in:
Kevin Wan
2022-12-08 22:01:36 +08:00
committed by GitHub
parent b7052854bb
commit dcfc9b79f1
10 changed files with 523 additions and 115 deletions

View File

@@ -11,18 +11,26 @@ const jsonTagKey = "json"
var jsonUnmarshaler = NewUnmarshaler(jsonTagKey)
// UnmarshalJsonBytes unmarshals content into v.
func UnmarshalJsonBytes(content []byte, v interface{}) error {
return unmarshalJsonBytes(content, v, jsonUnmarshaler)
func UnmarshalJsonBytes(content []byte, v interface{}, opts ...UnmarshalOption) error {
return unmarshalJsonBytes(content, v, getJsonUnmarshaler(opts...))
}
// UnmarshalJsonMap unmarshals content from m into v.
func UnmarshalJsonMap(m map[string]interface{}, v interface{}) error {
return jsonUnmarshaler.Unmarshal(m, v)
func UnmarshalJsonMap(m map[string]interface{}, v interface{}, opts ...UnmarshalOption) error {
return getJsonUnmarshaler(opts...).Unmarshal(m, v)
}
// UnmarshalJsonReader unmarshals content from reader into v.
func UnmarshalJsonReader(reader io.Reader, v interface{}) error {
return unmarshalJsonReader(reader, v, jsonUnmarshaler)
func UnmarshalJsonReader(reader io.Reader, v interface{}, opts ...UnmarshalOption) error {
return unmarshalJsonReader(reader, v, getJsonUnmarshaler(opts...))
}
func getJsonUnmarshaler(opts ...UnmarshalOption) *Unmarshaler {
if len(opts) > 0 {
return NewUnmarshaler(jsonTagKey, opts...)
}
return jsonUnmarshaler
}
func unmarshalJsonBytes(content []byte, v interface{}, unmarshaler *Unmarshaler) error {

View File

@@ -900,7 +900,9 @@ func TestUnmarshalMap(t *testing.T) {
Any string `json:",optional"`
}
err := UnmarshalJsonMap(m, &v)
err := UnmarshalJsonMap(m, &v, WithCanonicalKeyFunc(func(s string) string {
return s
}))
assert.Nil(t, err)
assert.True(t, len(v.Any) == 0)
})

View File

@@ -1,29 +1,27 @@
package mapping
import (
"bytes"
"encoding/json"
"io"
"github.com/pelletier/go-toml/v2"
"github.com/zeromicro/go-zero/internal/encoding"
)
// UnmarshalTomlBytes unmarshals TOML bytes into the given v.
func UnmarshalTomlBytes(content []byte, v interface{}) error {
return UnmarshalTomlReader(bytes.NewReader(content), v)
func UnmarshalTomlBytes(content []byte, v interface{}, opts ...UnmarshalOption) error {
b, err := encoding.TomlToJson(content)
if err != nil {
return err
}
return UnmarshalJsonBytes(b, v, opts...)
}
// UnmarshalTomlReader unmarshals TOML from the given io.Reader into the given v.
func UnmarshalTomlReader(r io.Reader, v interface{}) error {
var val interface{}
if err := toml.NewDecoder(r).Decode(&val); err != nil {
func UnmarshalTomlReader(r io.Reader, v interface{}, opts ...UnmarshalOption) error {
b, err := io.ReadAll(r)
if err != nil {
return err
}
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(val); err != nil {
return err
}
return UnmarshalJsonReader(&buf, v)
return UnmarshalTomlBytes(b, v, opts...)
}

View File

@@ -1,6 +1,7 @@
package mapping
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
@@ -18,7 +19,7 @@ d = "abcd!@#$112"
C string `json:"c"`
D string `json:"d"`
}
assert.Nil(t, UnmarshalTomlBytes([]byte(input), &val))
assert.NoError(t, UnmarshalTomlReader(strings.NewReader(input), &val))
assert.Equal(t, "foo", val.A)
assert.Equal(t, 1, val.B)
assert.Equal(t, "${FOO}", val.C)
@@ -37,5 +38,12 @@ d = "abcd!@#$112"
C string `json:"c"`
D string `json:"d"`
}
assert.NotNil(t, UnmarshalTomlBytes([]byte(input), &val))
assert.Error(t, UnmarshalTomlReader(strings.NewReader(input), &val))
}
func TestUnmarshalTomlBadReader(t *testing.T) {
var val struct {
A string `json:"a"`
}
assert.Error(t, UnmarshalTomlReader(new(badReader), &val))
}

View File

@@ -1,101 +1,27 @@
package mapping
import (
"encoding/json"
"errors"
"io"
"gopkg.in/yaml.v2"
)
// To make .json & .yaml consistent, we just use json as the tag key.
const yamlTagKey = "json"
var (
// ErrUnsupportedType is an error that indicates the config format is not supported.
ErrUnsupportedType = errors.New("only map-like configs are supported")
yamlUnmarshaler = NewUnmarshaler(yamlTagKey)
"github.com/zeromicro/go-zero/internal/encoding"
)
// UnmarshalYamlBytes unmarshals content into v.
func UnmarshalYamlBytes(content []byte, v interface{}) error {
return unmarshalYamlBytes(content, v, yamlUnmarshaler)
func UnmarshalYamlBytes(content []byte, v interface{}, opts ...UnmarshalOption) error {
b, err := encoding.YamlToJson(content)
if err != nil {
return err
}
return UnmarshalJsonBytes(b, v, opts...)
}
// UnmarshalYamlReader unmarshals content from reader into v.
func UnmarshalYamlReader(reader io.Reader, v interface{}) error {
return unmarshalYamlReader(reader, v, yamlUnmarshaler)
}
func cleanupInterfaceMap(in map[interface{}]interface{}) map[string]interface{} {
res := make(map[string]interface{})
for k, v := range in {
res[Repr(k)] = cleanupMapValue(v)
}
return res
}
func cleanupInterfaceNumber(in interface{}) json.Number {
return json.Number(Repr(in))
}
func cleanupInterfaceSlice(in []interface{}) []interface{} {
res := make([]interface{}, len(in))
for i, v := range in {
res[i] = cleanupMapValue(v)
}
return res
}
func cleanupMapValue(v interface{}) interface{} {
switch v := v.(type) {
case []interface{}:
return cleanupInterfaceSlice(v)
case map[interface{}]interface{}:
return cleanupInterfaceMap(v)
case bool, string:
return v
case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64, float32, float64:
return cleanupInterfaceNumber(v)
default:
return Repr(v)
}
}
func unmarshal(unmarshaler *Unmarshaler, o, v interface{}) error {
if m, ok := o.(map[string]interface{}); ok {
return unmarshaler.Unmarshal(m, v)
}
return ErrUnsupportedType
}
func unmarshalYamlBytes(content []byte, v interface{}, unmarshaler *Unmarshaler) error {
var o interface{}
if err := yamlUnmarshal(content, &o); err != nil {
func UnmarshalYamlReader(reader io.Reader, v interface{}, opts ...UnmarshalOption) error {
b, err := io.ReadAll(reader)
if err != nil {
return err
}
return unmarshal(unmarshaler, o, v)
}
func unmarshalYamlReader(reader io.Reader, v interface{}, unmarshaler *Unmarshaler) error {
var res interface{}
if err := yaml.NewDecoder(reader).Decode(&res); err != nil {
return err
}
return unmarshal(unmarshaler, cleanupMapValue(res), v)
}
// yamlUnmarshal YAML to map[string]interface{} instead of map[interface{}]interface{}.
func yamlUnmarshal(in []byte, out interface{}) error {
var res interface{}
if err := yaml.Unmarshal(in, &res); err != nil {
return err
}
*out.(*interface{}) = cleanupMapValue(res)
return nil
return UnmarshalYamlBytes(b, v, opts...)
}

View File

@@ -934,9 +934,8 @@ func TestUnmarshalYamlReaderError(t *testing.T) {
err := UnmarshalYamlReader(reader, &v)
assert.NotNil(t, err)
reader = strings.NewReader("chenquan")
err = UnmarshalYamlReader(reader, &v)
assert.ErrorIs(t, err, ErrUnsupportedType)
reader = strings.NewReader("foo")
assert.Error(t, UnmarshalYamlReader(reader, &v))
}
func TestUnmarshalYamlBadReader(t *testing.T) {
@@ -1012,6 +1011,13 @@ func TestUnmarshalYamlMapRune(t *testing.T) {
assert.Equal(t, rune(3), v.Machine["node3"])
}
func TestUnmarshalYamlBadInput(t *testing.T) {
var v struct {
Any string
}
assert.Error(t, UnmarshalYamlBytes([]byte("':foo"), &v))
}
type badReader struct{}
func (b *badReader) Read(_ []byte) (n int, err error) {