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
master
Kevin Wan 2 years ago committed by GitHub
parent b7052854bb
commit dcfc9b79f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -7,9 +7,13 @@ import (
"path" "path"
"strings" "strings"
"github.com/zeromicro/go-zero/core/jsonx"
"github.com/zeromicro/go-zero/core/mapping" "github.com/zeromicro/go-zero/core/mapping"
"github.com/zeromicro/go-zero/internal/encoding"
) )
const distanceBetweenUpperAndLower = 32
var loaders = map[string]func([]byte, interface{}) error{ var loaders = map[string]func([]byte, interface{}) error{
".json": LoadFromJsonBytes, ".json": LoadFromJsonBytes,
".toml": LoadFromTomlBytes, ".toml": LoadFromTomlBytes,
@ -49,7 +53,12 @@ func LoadConfig(file string, v interface{}, opts ...Option) error {
// LoadFromJsonBytes loads config into v from content json bytes. // LoadFromJsonBytes loads config into v from content json bytes.
func LoadFromJsonBytes(content []byte, v interface{}) error { func LoadFromJsonBytes(content []byte, v interface{}) error {
return mapping.UnmarshalJsonBytes(content, v) var m map[string]interface{}
if err := jsonx.Unmarshal(content, &m); err != nil {
return err
}
return mapping.UnmarshalJsonMap(toCamelCaseKeyMap(m), v, mapping.WithCanonicalKeyFunc(toCamelCase))
} }
// LoadConfigFromJsonBytes loads config into v from content json bytes. // LoadConfigFromJsonBytes loads config into v from content json bytes.
@ -60,12 +69,22 @@ func LoadConfigFromJsonBytes(content []byte, v interface{}) error {
// LoadFromTomlBytes loads config into v from content toml bytes. // LoadFromTomlBytes loads config into v from content toml bytes.
func LoadFromTomlBytes(content []byte, v interface{}) error { func LoadFromTomlBytes(content []byte, v interface{}) error {
return mapping.UnmarshalTomlBytes(content, v) b, err := encoding.TomlToJson(content)
if err != nil {
return err
}
return LoadFromJsonBytes(b, v)
} }
// LoadFromYamlBytes loads config into v from content yaml bytes. // LoadFromYamlBytes loads config into v from content yaml bytes.
func LoadFromYamlBytes(content []byte, v interface{}) error { func LoadFromYamlBytes(content []byte, v interface{}) error {
return mapping.UnmarshalYamlBytes(content, v) b, err := encoding.YamlToJson(content)
if err != nil {
return err
}
return LoadFromJsonBytes(b, v)
} }
// LoadConfigFromYamlBytes loads config into v from content yaml bytes. // LoadConfigFromYamlBytes loads config into v from content yaml bytes.
@ -80,3 +99,66 @@ func MustLoad(path string, v interface{}, opts ...Option) {
log.Fatalf("error: config file %s, %s", path, err.Error()) log.Fatalf("error: config file %s, %s", path, err.Error())
} }
} }
func toCamelCase(s string) string {
var buf strings.Builder
buf.Grow(len(s))
var capNext bool
boundary := true
for _, v := range s {
isCap := v >= 'A' && v <= 'Z'
isLow := v >= 'a' && v <= 'z'
if boundary && (isCap || isLow) {
if capNext {
if isLow {
v -= distanceBetweenUpperAndLower
}
} else {
if isCap {
v += distanceBetweenUpperAndLower
}
}
boundary = false
}
if isCap || isLow {
buf.WriteRune(v)
capNext = false
} else if v == ' ' || v == '\t' {
buf.WriteRune(v)
capNext = false
boundary = true
} else if v == '_' {
capNext = true
boundary = true
} else {
buf.WriteRune(v)
capNext = true
}
}
return buf.String()
}
func toCamelCaseInterface(v interface{}) interface{} {
switch vv := v.(type) {
case map[string]interface{}:
return toCamelCaseKeyMap(vv)
case []interface{}:
var arr []interface{}
for _, vvv := range vv {
arr = append(arr, toCamelCaseInterface(vvv))
}
return arr
default:
return v
}
}
func toCamelCaseKeyMap(m map[string]interface{}) map[string]interface{} {
res := make(map[string]interface{})
for k, v := range m {
res[toCamelCase(k)] = toCamelCaseInterface(v)
}
return res
}

@ -56,6 +56,22 @@ func TestConfigJson(t *testing.T) {
} }
} }
func TestLoadFromJsonBytesArray(t *testing.T) {
input := []byte(`{"users": [{"name": "foo"}, {"Name": "bar"}]}`)
var val struct {
Users []struct {
Name string
}
}
assert.NoError(t, LoadFromJsonBytes(input, &val))
var expect []string
for _, user := range val.Users {
expect = append(expect, user.Name)
}
assert.EqualValues(t, []string{"foo", "bar"}, expect)
}
func TestConfigToml(t *testing.T) { func TestConfigToml(t *testing.T) {
text := `a = "foo" text := `a = "foo"
b = 1 b = 1
@ -81,6 +97,65 @@ d = "abcd!@#$112"
assert.Equal(t, "abcd!@#$112", val.D) assert.Equal(t, "abcd!@#$112", val.D)
} }
func TestConfigJsonCanonical(t *testing.T) {
text := []byte(`{"a": "foo", "B": "bar"}`)
var val1 struct {
A string `json:"a"`
B string `json:"b"`
}
var val2 struct {
A string
B string
}
assert.NoError(t, LoadFromJsonBytes(text, &val1))
assert.Equal(t, "foo", val1.A)
assert.Equal(t, "bar", val1.B)
assert.NoError(t, LoadFromJsonBytes(text, &val2))
assert.Equal(t, "foo", val2.A)
assert.Equal(t, "bar", val2.B)
}
func TestConfigTomlCanonical(t *testing.T) {
text := []byte(`a = "foo"
B = "bar"`)
var val1 struct {
A string `json:"a"`
B string `json:"b"`
}
var val2 struct {
A string
B string
}
assert.NoError(t, LoadFromTomlBytes(text, &val1))
assert.Equal(t, "foo", val1.A)
assert.Equal(t, "bar", val1.B)
assert.NoError(t, LoadFromTomlBytes(text, &val2))
assert.Equal(t, "foo", val2.A)
assert.Equal(t, "bar", val2.B)
}
func TestConfigYamlCanonical(t *testing.T) {
text := []byte(`a: foo
B: bar`)
var val1 struct {
A string `json:"a"`
B string `json:"b"`
}
var val2 struct {
A string
B string
}
assert.NoError(t, LoadFromYamlBytes(text, &val1))
assert.Equal(t, "foo", val1.A)
assert.Equal(t, "bar", val1.B)
assert.NoError(t, LoadFromYamlBytes(text, &val2))
assert.Equal(t, "foo", val2.A)
assert.Equal(t, "bar", val2.B)
}
func TestConfigTomlEnv(t *testing.T) { func TestConfigTomlEnv(t *testing.T) {
text := `a = "foo" text := `a = "foo"
b = 1 b = 1
@ -143,6 +218,116 @@ func TestConfigJsonEnv(t *testing.T) {
} }
} }
func TestToCamelCase(t *testing.T) {
tests := []struct {
input string
expect string
}{
{
input: "",
expect: "",
},
{
input: "A",
expect: "a",
},
{
input: "a",
expect: "a",
},
{
input: "hello_world",
expect: "helloWorld",
},
{
input: "Hello_world",
expect: "helloWorld",
},
{
input: "hello_World",
expect: "helloWorld",
},
{
input: "helloWorld",
expect: "helloWorld",
},
{
input: "HelloWorld",
expect: "helloWorld",
},
{
input: "hello World",
expect: "hello world",
},
{
input: "Hello World",
expect: "hello world",
},
{
input: "Hello World",
expect: "hello world",
},
{
input: "Hello World foo_bar",
expect: "hello world fooBar",
},
{
input: "Hello World foo_Bar",
expect: "hello world fooBar",
},
{
input: "Hello World Foo_bar",
expect: "hello world fooBar",
},
{
input: "Hello World Foo_Bar",
expect: "hello world fooBar",
},
{
input: "你好 World Foo_Bar",
expect: "你好 world fooBar",
},
}
for _, test := range tests {
test := test
t.Run(test.input, func(t *testing.T) {
assert.Equal(t, test.expect, toCamelCase(test.input))
})
}
}
func TestLoadFromJsonBytesError(t *testing.T) {
var val struct{}
assert.Error(t, LoadFromJsonBytes([]byte(`hello`), &val))
}
func TestLoadFromTomlBytesError(t *testing.T) {
var val struct{}
assert.Error(t, LoadFromTomlBytes([]byte(`hello`), &val))
}
func TestLoadFromYamlBytesError(t *testing.T) {
var val struct{}
assert.Error(t, LoadFromYamlBytes([]byte(`':hello`), &val))
}
func TestLoadFromYamlBytes(t *testing.T) {
input := []byte(`layer1:
layer2:
layer3: foo`)
var val struct {
Layer1 struct {
Layer2 struct {
Layer3 string
}
}
}
assert.NoError(t, LoadFromYamlBytes(input, &val))
assert.Equal(t, "foo", val.Layer1.Layer2.Layer3)
}
func createTempFile(ext, text string) (string, error) { func createTempFile(ext, text string) (string, error) {
tmpfile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text))+"*"+ext) tmpfile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text))+"*"+ext)
if err != nil { if err != nil {

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

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

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

@ -1,6 +1,7 @@
package mapping package mapping
import ( import (
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -18,7 +19,7 @@ d = "abcd!@#$112"
C string `json:"c"` C string `json:"c"`
D string `json:"d"` 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, "foo", val.A)
assert.Equal(t, 1, val.B) assert.Equal(t, 1, val.B)
assert.Equal(t, "${FOO}", val.C) assert.Equal(t, "${FOO}", val.C)
@ -37,5 +38,12 @@ d = "abcd!@#$112"
C string `json:"c"` C string `json:"c"`
D string `json:"d"` 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))
} }

@ -1,101 +1,27 @@
package mapping package mapping
import ( import (
"encoding/json"
"errors"
"io" "io"
"gopkg.in/yaml.v2" "github.com/zeromicro/go-zero/internal/encoding"
)
// 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)
) )
// UnmarshalYamlBytes unmarshals content into v. // UnmarshalYamlBytes unmarshals content into v.
func UnmarshalYamlBytes(content []byte, v interface{}) error { func UnmarshalYamlBytes(content []byte, v interface{}, opts ...UnmarshalOption) error {
return unmarshalYamlBytes(content, v, yamlUnmarshaler) b, err := encoding.YamlToJson(content)
} if err != nil {
// 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 {
return err return err
} }
return unmarshal(unmarshaler, o, v) return UnmarshalJsonBytes(b, v, opts...)
} }
func unmarshalYamlReader(reader io.Reader, v interface{}, unmarshaler *Unmarshaler) error { // UnmarshalYamlReader unmarshals content from reader into v.
var res interface{} func UnmarshalYamlReader(reader io.Reader, v interface{}, opts ...UnmarshalOption) error {
if err := yaml.NewDecoder(reader).Decode(&res); err != nil { b, err := io.ReadAll(reader)
return err if err != nil {
}
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 return err
} }
*out.(*interface{}) = cleanupMapValue(res) return UnmarshalYamlBytes(b, v, opts...)
return nil
} }

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

@ -0,0 +1,75 @@
package encoding
import (
"bytes"
"encoding/json"
"github.com/pelletier/go-toml/v2"
"github.com/zeromicro/go-zero/core/lang"
"gopkg.in/yaml.v2"
)
func TomlToJson(data []byte) ([]byte, error) {
var val interface{}
if err := toml.NewDecoder(bytes.NewReader(data)).Decode(&val); err != nil {
return nil, err
}
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(val); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func YamlToJson(data []byte) ([]byte, error) {
var val interface{}
if err := yaml.Unmarshal(data, &val); err != nil {
return nil, err
}
val = toStringKeyMap(val)
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(val); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func convertKeyToString(in map[interface{}]interface{}) map[string]interface{} {
res := make(map[string]interface{})
for k, v := range in {
res[lang.Repr(k)] = toStringKeyMap(v)
}
return res
}
func convertNumberToJsonNumber(in interface{}) json.Number {
return json.Number(lang.Repr(in))
}
func convertSlice(in []interface{}) []interface{} {
res := make([]interface{}, len(in))
for i, v := range in {
res[i] = toStringKeyMap(v)
}
return res
}
func toStringKeyMap(v interface{}) interface{} {
switch v := v.(type) {
case []interface{}:
return convertSlice(v)
case map[interface{}]interface{}:
return convertKeyToString(v)
case bool, string:
return v
case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64, float32, float64:
return convertNumberToJsonNumber(v)
default:
return lang.Repr(v)
}
}

@ -0,0 +1,118 @@
package encoding
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestTomlToJson(t *testing.T) {
tests := []struct {
input string
expect string
}{
{
input: "a = \"foo\"\nb = 1\nc = \"${FOO}\"\nd = \"abcd!@#$112\"",
expect: "{\"a\":\"foo\",\"b\":1,\"c\":\"${FOO}\",\"d\":\"abcd!@#$112\"}\n",
},
{
input: "a = \"foo\"\nb = 1\nc = \"${FOO}\"\nd = \"abcd!@#$112\"",
expect: "{\"a\":\"foo\",\"b\":1,\"c\":\"${FOO}\",\"d\":\"abcd!@#$112\"}\n",
},
{
input: "a = \"foo\"\nb = 1\nc = \"${FOO}\"\nd = \"abcd!@#$112\"",
expect: "{\"a\":\"foo\",\"b\":1,\"c\":\"${FOO}\",\"d\":\"abcd!@#$112\"}\n",
},
{
input: "a = \"foo\"\nb = 1\nc = \"${FOO}\"\nd = \"abcd!@#$112\"",
expect: "{\"a\":\"foo\",\"b\":1,\"c\":\"${FOO}\",\"d\":\"abcd!@#$112\"}\n",
},
{
input: "a = \"foo\"\nb = 1\nc = \"${FOO}\"\nd = \"abcd!@#$112\"",
expect: "{\"a\":\"foo\",\"b\":1,\"c\":\"${FOO}\",\"d\":\"abcd!@#$112\"}\n",
},
{
input: "a = \"foo\"\nb = 1\nc = \"${FOO}\"\nd = \"abcd!@#$112\"\n",
expect: "{\"a\":\"foo\",\"b\":1,\"c\":\"${FOO}\",\"d\":\"abcd!@#$112\"}\n",
},
{
input: "a = \"foo\"\nb = 1\nc = \"${FOO}\"\nd = \"abcd!@#$112\"\n",
expect: "{\"a\":\"foo\",\"b\":1,\"c\":\"${FOO}\",\"d\":\"abcd!@#$112\"}\n",
},
}
for _, test := range tests {
test := test
t.Run(test.input, func(t *testing.T) {
t.Parallel()
got, err := TomlToJson([]byte(test.input))
assert.NoError(t, err)
assert.Equal(t, test.expect, string(got))
})
}
}
func TestTomlToJsonError(t *testing.T) {
_, err := TomlToJson([]byte("foo"))
assert.Error(t, err)
}
func TestYamlToJson(t *testing.T) {
tests := []struct {
input string
expect string
}{
{
input: "a: foo\nb: 1\nc: ${FOO}\nd: abcd!@#$112",
expect: "{\"a\":\"foo\",\"b\":1,\"c\":\"${FOO}\",\"d\":\"abcd!@#$112\"}\n",
},
{
input: "a: foo\nb: 1\nc: ${FOO}\nd: abcd!@#$112",
expect: "{\"a\":\"foo\",\"b\":1,\"c\":\"${FOO}\",\"d\":\"abcd!@#$112\"}\n",
},
{
input: "a: foo\nb: 1\nc: ${FOO}\nd: abcd!@#$112",
expect: "{\"a\":\"foo\",\"b\":1,\"c\":\"${FOO}\",\"d\":\"abcd!@#$112\"}\n",
},
{
input: "a: foo\nb: 1\nc: ${FOO}\nd: abcd!@#$112",
expect: "{\"a\":\"foo\",\"b\":1,\"c\":\"${FOO}\",\"d\":\"abcd!@#$112\"}\n",
},
{
input: "a: foo\nb: 1\nc: ${FOO}\nd: abcd!@#$112",
expect: "{\"a\":\"foo\",\"b\":1,\"c\":\"${FOO}\",\"d\":\"abcd!@#$112\"}\n",
},
{
input: "a: foo\nb: 1\nc: ${FOO}\nd: abcd!@#$112\n",
expect: "{\"a\":\"foo\",\"b\":1,\"c\":\"${FOO}\",\"d\":\"abcd!@#$112\"}\n",
},
{
input: "a: foo\nb: 1\nc: ${FOO}\nd: abcd!@#$112\n",
expect: "{\"a\":\"foo\",\"b\":1,\"c\":\"${FOO}\",\"d\":\"abcd!@#$112\"}\n",
},
}
for _, test := range tests {
test := test
t.Run(test.input, func(t *testing.T) {
t.Parallel()
got, err := YamlToJson([]byte(test.input))
assert.NoError(t, err)
assert.Equal(t, test.expect, string(got))
})
}
}
func TestYamlToJsonError(t *testing.T) {
_, err := YamlToJson([]byte("':foo"))
assert.Error(t, err)
}
func TestYamlToJsonSlice(t *testing.T) {
b, err := YamlToJson([]byte(`foo:
- bar
- baz`))
assert.NoError(t, err)
assert.Equal(t, `{"foo":["bar","baz"]}
`, string(b))
}
Loading…
Cancel
Save