You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1308 lines
23 KiB
Go
1308 lines
23 KiB
Go
package conf
|
|
|
|
import (
|
|
"os"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/zeromicro/go-zero/core/fs"
|
|
"github.com/zeromicro/go-zero/core/hash"
|
|
)
|
|
|
|
var dupErr conflictKeyError
|
|
|
|
func TestLoadConfig_notExists(t *testing.T) {
|
|
assert.NotNil(t, Load("not_a_file", nil))
|
|
}
|
|
|
|
func TestLoadConfig_notRecogFile(t *testing.T) {
|
|
filename, err := fs.TempFilenameWithText("hello")
|
|
assert.Nil(t, err)
|
|
defer os.Remove(filename)
|
|
assert.NotNil(t, LoadConfig(filename, nil))
|
|
}
|
|
|
|
func TestConfigJson(t *testing.T) {
|
|
tests := []string{
|
|
".json",
|
|
".yaml",
|
|
".yml",
|
|
}
|
|
text := `{
|
|
"a": "foo",
|
|
"b": 1,
|
|
"c": "${FOO}",
|
|
"d": "abcd!@#$112"
|
|
}`
|
|
t.Setenv("FOO", "2")
|
|
|
|
for _, test := range tests {
|
|
test := test
|
|
t.Run(test, func(t *testing.T) {
|
|
tmpfile, err := createTempFile(test, text)
|
|
assert.Nil(t, err)
|
|
defer os.Remove(tmpfile)
|
|
|
|
var val struct {
|
|
A string `json:"a"`
|
|
B int `json:"b"`
|
|
C string `json:"c"`
|
|
D string `json:"d"`
|
|
}
|
|
MustLoad(tmpfile, &val)
|
|
assert.Equal(t, "foo", val.A)
|
|
assert.Equal(t, 1, val.B)
|
|
assert.Equal(t, "${FOO}", val.C)
|
|
assert.Equal(t, "abcd!@#$112", val.D)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadFromJsonBytesArray(t *testing.T) {
|
|
input := []byte(`{"users": [{"name": "foo"}, {"Name": "bar"}]}`)
|
|
var val struct {
|
|
Users []struct {
|
|
Name string
|
|
}
|
|
}
|
|
|
|
assert.NoError(t, LoadConfigFromJsonBytes(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) {
|
|
text := `a = "foo"
|
|
b = 1
|
|
c = "${FOO}"
|
|
d = "abcd!@#$112"
|
|
`
|
|
t.Setenv("FOO", "2")
|
|
tmpfile, err := createTempFile(".toml", text)
|
|
assert.Nil(t, err)
|
|
defer os.Remove(tmpfile)
|
|
|
|
var val struct {
|
|
A string `json:"a"`
|
|
B int `json:"b"`
|
|
C string `json:"c"`
|
|
D string `json:"d"`
|
|
}
|
|
MustLoad(tmpfile, &val)
|
|
assert.Equal(t, "foo", val.A)
|
|
assert.Equal(t, 1, val.B)
|
|
assert.Equal(t, "${FOO}", val.C)
|
|
assert.Equal(t, "abcd!@#$112", val.D)
|
|
}
|
|
|
|
func TestConfigOptional(t *testing.T) {
|
|
text := `a = "foo"
|
|
b = 1
|
|
c = "FOO"
|
|
d = "abcd"
|
|
`
|
|
tmpfile, err := createTempFile(".toml", text)
|
|
assert.Nil(t, err)
|
|
defer os.Remove(tmpfile)
|
|
|
|
var val struct {
|
|
A string `json:"a"`
|
|
B int `json:"b,optional"`
|
|
C string `json:"c,optional=B"`
|
|
D string `json:"d,optional=b"`
|
|
}
|
|
if assert.NoError(t, Load(tmpfile, &val)) {
|
|
assert.Equal(t, "foo", val.A)
|
|
assert.Equal(t, 1, val.B)
|
|
assert.Equal(t, "FOO", val.C)
|
|
assert.Equal(t, "abcd", val.D)
|
|
}
|
|
}
|
|
|
|
func TestConfigWithLower(t *testing.T) {
|
|
text := `a = "foo"
|
|
b = 1
|
|
`
|
|
tmpfile, err := createTempFile(".toml", text)
|
|
assert.Nil(t, err)
|
|
defer os.Remove(tmpfile)
|
|
|
|
var val struct {
|
|
A string `json:"a"`
|
|
b int
|
|
}
|
|
if assert.NoError(t, Load(tmpfile, &val)) {
|
|
assert.Equal(t, "foo", val.A)
|
|
assert.Equal(t, 0, val.b)
|
|
}
|
|
}
|
|
|
|
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, LoadConfigFromYamlBytes(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) {
|
|
text := `a = "foo"
|
|
b = 1
|
|
c = "${FOO}"
|
|
d = "abcd!@#112"
|
|
`
|
|
t.Setenv("FOO", "2")
|
|
tmpfile, err := createTempFile(".toml", text)
|
|
assert.Nil(t, err)
|
|
defer os.Remove(tmpfile)
|
|
|
|
var val struct {
|
|
A string `json:"a"`
|
|
B int `json:"b"`
|
|
C string `json:"c"`
|
|
D string `json:"d"`
|
|
}
|
|
|
|
MustLoad(tmpfile, &val, UseEnv())
|
|
assert.Equal(t, "foo", val.A)
|
|
assert.Equal(t, 1, val.B)
|
|
assert.Equal(t, "2", val.C)
|
|
assert.Equal(t, "abcd!@#112", val.D)
|
|
}
|
|
|
|
func TestConfigJsonEnv(t *testing.T) {
|
|
tests := []string{
|
|
".json",
|
|
".yaml",
|
|
".yml",
|
|
}
|
|
text := `{
|
|
"a": "foo",
|
|
"b": 1,
|
|
"c": "${FOO}",
|
|
"d": "abcd!@#$a12 3"
|
|
}`
|
|
t.Setenv("FOO", "2")
|
|
for _, test := range tests {
|
|
test := test
|
|
t.Run(test, func(t *testing.T) {
|
|
tmpfile, err := createTempFile(test, text)
|
|
assert.Nil(t, err)
|
|
defer os.Remove(tmpfile)
|
|
|
|
var val struct {
|
|
A string `json:"a"`
|
|
B int `json:"b"`
|
|
C string `json:"c"`
|
|
D string `json:"d"`
|
|
}
|
|
MustLoad(tmpfile, &val, UseEnv())
|
|
assert.Equal(t, "foo", val.A)
|
|
assert.Equal(t, 1, val.B)
|
|
assert.Equal(t, "2", val.C)
|
|
assert.Equal(t, "abcd!@# 3", val.D)
|
|
})
|
|
}
|
|
}
|
|
|
|
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: "hello_world",
|
|
},
|
|
{
|
|
input: "Hello_world",
|
|
expect: "hello_world",
|
|
},
|
|
{
|
|
input: "hello_World",
|
|
expect: "hello_world",
|
|
},
|
|
{
|
|
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 foo_bar",
|
|
},
|
|
{
|
|
input: "Hello World foo_Bar",
|
|
expect: "hello world foo_bar",
|
|
},
|
|
{
|
|
input: "Hello World Foo_bar",
|
|
expect: "hello world foo_bar",
|
|
},
|
|
{
|
|
input: "Hello World Foo_Bar",
|
|
expect: "hello world foo_bar",
|
|
},
|
|
{
|
|
input: "Hello.World Foo_Bar",
|
|
expect: "hello.world foo_bar",
|
|
},
|
|
{
|
|
input: "你好 World Foo_Bar",
|
|
expect: "你好 world foo_bar",
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
test := test
|
|
t.Run(test.input, func(t *testing.T) {
|
|
assert.Equal(t, test.expect, toLowerCase(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 TestLoadFromYamlBytesTerm(t *testing.T) {
|
|
input := []byte(`layer1:
|
|
layer2:
|
|
tls_conf: foo`)
|
|
var val struct {
|
|
Layer1 struct {
|
|
Layer2 struct {
|
|
Layer3 string `json:"tls_conf"`
|
|
}
|
|
}
|
|
}
|
|
|
|
assert.NoError(t, LoadFromYamlBytes(input, &val))
|
|
assert.Equal(t, "foo", val.Layer1.Layer2.Layer3)
|
|
}
|
|
|
|
func TestLoadFromYamlBytesLayers(t *testing.T) {
|
|
input := []byte(`layer1:
|
|
layer2:
|
|
layer3: foo`)
|
|
var val struct {
|
|
Value string `json:"Layer1.Layer2.Layer3"`
|
|
}
|
|
|
|
assert.NoError(t, LoadFromYamlBytes(input, &val))
|
|
assert.Equal(t, "foo", val.Value)
|
|
}
|
|
|
|
func TestLoadFromYamlItemOverlay(t *testing.T) {
|
|
type (
|
|
Redis struct {
|
|
Host string
|
|
Port int
|
|
}
|
|
|
|
RedisKey struct {
|
|
Redis
|
|
Key string
|
|
}
|
|
|
|
Server struct {
|
|
Redis RedisKey
|
|
}
|
|
|
|
TestConfig struct {
|
|
Server
|
|
Redis Redis
|
|
}
|
|
)
|
|
|
|
input := []byte(`Redis:
|
|
Host: localhost
|
|
Port: 6379
|
|
Key: test
|
|
`)
|
|
|
|
var c TestConfig
|
|
assert.ErrorAs(t, LoadFromYamlBytes(input, &c), &dupErr)
|
|
}
|
|
|
|
func TestLoadFromYamlItemOverlayReverse(t *testing.T) {
|
|
type (
|
|
Redis struct {
|
|
Host string
|
|
Port int
|
|
}
|
|
|
|
RedisKey struct {
|
|
Redis
|
|
Key string
|
|
}
|
|
|
|
Server struct {
|
|
Redis Redis
|
|
}
|
|
|
|
TestConfig struct {
|
|
Redis RedisKey
|
|
Server
|
|
}
|
|
)
|
|
|
|
input := []byte(`Redis:
|
|
Host: localhost
|
|
Port: 6379
|
|
Key: test
|
|
`)
|
|
|
|
var c TestConfig
|
|
assert.ErrorAs(t, LoadFromYamlBytes(input, &c), &dupErr)
|
|
}
|
|
|
|
func TestLoadFromYamlItemOverlayWithMap(t *testing.T) {
|
|
type (
|
|
Redis struct {
|
|
Host string
|
|
Port int
|
|
}
|
|
|
|
RedisKey struct {
|
|
Redis
|
|
Key string
|
|
}
|
|
|
|
Server struct {
|
|
Redis RedisKey
|
|
}
|
|
|
|
TestConfig struct {
|
|
Server
|
|
Redis map[string]interface{}
|
|
}
|
|
)
|
|
|
|
input := []byte(`Redis:
|
|
Host: localhost
|
|
Port: 6379
|
|
Key: test
|
|
`)
|
|
|
|
var c TestConfig
|
|
assert.ErrorAs(t, LoadFromYamlBytes(input, &c), &dupErr)
|
|
}
|
|
|
|
func TestUnmarshalJsonBytesMap(t *testing.T) {
|
|
input := []byte(`{"foo":{"/mtproto.RPCTos": "bff.bff","bar":"baz"}}`)
|
|
|
|
var val struct {
|
|
Foo map[string]string
|
|
}
|
|
|
|
assert.NoError(t, LoadFromJsonBytes(input, &val))
|
|
assert.Equal(t, "bff.bff", val.Foo["/mtproto.RPCTos"])
|
|
assert.Equal(t, "baz", val.Foo["bar"])
|
|
}
|
|
|
|
func TestUnmarshalJsonBytesMapWithSliceElements(t *testing.T) {
|
|
input := []byte(`{"foo":{"/mtproto.RPCTos": ["bff.bff", "any"],"bar":["baz", "qux"]}}`)
|
|
|
|
var val struct {
|
|
Foo map[string][]string
|
|
}
|
|
|
|
assert.NoError(t, LoadFromJsonBytes(input, &val))
|
|
assert.EqualValues(t, []string{"bff.bff", "any"}, val.Foo["/mtproto.RPCTos"])
|
|
assert.EqualValues(t, []string{"baz", "qux"}, val.Foo["bar"])
|
|
}
|
|
|
|
func TestUnmarshalJsonBytesMapWithSliceOfStructs(t *testing.T) {
|
|
input := []byte(`{"foo":{
|
|
"/mtproto.RPCTos": [{"bar": "any"}],
|
|
"bar":[{"bar": "qux"}, {"bar": "ever"}]}}`)
|
|
|
|
var val struct {
|
|
Foo map[string][]struct {
|
|
Bar string
|
|
}
|
|
}
|
|
|
|
assert.NoError(t, LoadFromJsonBytes(input, &val))
|
|
assert.Equal(t, 1, len(val.Foo["/mtproto.RPCTos"]))
|
|
assert.Equal(t, "any", val.Foo["/mtproto.RPCTos"][0].Bar)
|
|
assert.Equal(t, 2, len(val.Foo["bar"]))
|
|
assert.Equal(t, "qux", val.Foo["bar"][0].Bar)
|
|
assert.Equal(t, "ever", val.Foo["bar"][1].Bar)
|
|
}
|
|
|
|
func TestUnmarshalJsonBytesWithAnonymousField(t *testing.T) {
|
|
type (
|
|
Int int
|
|
|
|
InnerConf struct {
|
|
Name string
|
|
}
|
|
|
|
Conf struct {
|
|
Int
|
|
InnerConf
|
|
}
|
|
)
|
|
|
|
var (
|
|
input = []byte(`{"Name": "hello", "int": 3}`)
|
|
c Conf
|
|
)
|
|
assert.NoError(t, LoadFromJsonBytes(input, &c))
|
|
assert.Equal(t, "hello", c.Name)
|
|
assert.Equal(t, Int(3), c.Int)
|
|
}
|
|
|
|
func TestUnmarshalJsonBytesWithMapValueOfStruct(t *testing.T) {
|
|
type (
|
|
Value struct {
|
|
Name string
|
|
}
|
|
|
|
Config struct {
|
|
Items map[string]Value
|
|
}
|
|
)
|
|
|
|
var inputs = [][]byte{
|
|
[]byte(`{"Items": {"Key":{"Name": "foo"}}}`),
|
|
[]byte(`{"Items": {"Key":{"Name": "foo"}}}`),
|
|
[]byte(`{"items": {"key":{"name": "foo"}}}`),
|
|
[]byte(`{"items": {"key":{"name": "foo"}}}`),
|
|
}
|
|
for _, input := range inputs {
|
|
var c Config
|
|
if assert.NoError(t, LoadFromJsonBytes(input, &c)) {
|
|
assert.Equal(t, 1, len(c.Items))
|
|
for _, v := range c.Items {
|
|
assert.Equal(t, "foo", v.Name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUnmarshalJsonBytesWithMapTypeValueOfStruct(t *testing.T) {
|
|
type (
|
|
Value struct {
|
|
Name string
|
|
}
|
|
|
|
Map map[string]Value
|
|
|
|
Config struct {
|
|
Map
|
|
}
|
|
)
|
|
|
|
var inputs = [][]byte{
|
|
[]byte(`{"Map": {"Key":{"Name": "foo"}}}`),
|
|
[]byte(`{"Map": {"Key":{"Name": "foo"}}}`),
|
|
[]byte(`{"map": {"key":{"name": "foo"}}}`),
|
|
[]byte(`{"map": {"key":{"name": "foo"}}}`),
|
|
}
|
|
for _, input := range inputs {
|
|
var c Config
|
|
if assert.NoError(t, LoadFromJsonBytes(input, &c)) {
|
|
assert.Equal(t, 1, len(c.Map))
|
|
for _, v := range c.Map {
|
|
assert.Equal(t, "foo", v.Name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_FieldOverwrite(t *testing.T) {
|
|
t.Run("normal", func(t *testing.T) {
|
|
type Base struct {
|
|
Name string
|
|
}
|
|
|
|
type St1 struct {
|
|
Base
|
|
Name2 string
|
|
}
|
|
|
|
type St2 struct {
|
|
Base
|
|
Name2 string
|
|
}
|
|
|
|
type St3 struct {
|
|
*Base
|
|
Name2 string
|
|
}
|
|
|
|
type St4 struct {
|
|
*Base
|
|
Name2 *string
|
|
}
|
|
|
|
validate := func(val any) {
|
|
input := []byte(`{"Name": "hello", "Name2": "world"}`)
|
|
assert.NoError(t, LoadFromJsonBytes(input, val))
|
|
}
|
|
|
|
validate(&St1{})
|
|
validate(&St2{})
|
|
validate(&St3{})
|
|
validate(&St4{})
|
|
})
|
|
|
|
t.Run("Inherit Override", func(t *testing.T) {
|
|
type Base struct {
|
|
Name string
|
|
}
|
|
|
|
type St1 struct {
|
|
Base
|
|
Name string
|
|
}
|
|
|
|
type St2 struct {
|
|
Base
|
|
Name int
|
|
}
|
|
|
|
type St3 struct {
|
|
*Base
|
|
Name int
|
|
}
|
|
|
|
type St4 struct {
|
|
*Base
|
|
Name *string
|
|
}
|
|
|
|
validate := func(val any) {
|
|
input := []byte(`{"Name": "hello"}`)
|
|
err := LoadFromJsonBytes(input, val)
|
|
assert.ErrorAs(t, err, &dupErr)
|
|
assert.Equal(t, newConflictKeyError("name").Error(), err.Error())
|
|
}
|
|
|
|
validate(&St1{})
|
|
validate(&St2{})
|
|
validate(&St3{})
|
|
validate(&St4{})
|
|
})
|
|
|
|
t.Run("Inherit more", func(t *testing.T) {
|
|
type Base1 struct {
|
|
Name string
|
|
}
|
|
|
|
type St0 struct {
|
|
Base1
|
|
Name string
|
|
}
|
|
|
|
type St1 struct {
|
|
St0
|
|
Name string
|
|
}
|
|
|
|
type St2 struct {
|
|
St0
|
|
Name int
|
|
}
|
|
|
|
type St3 struct {
|
|
*St0
|
|
Name int
|
|
}
|
|
|
|
type St4 struct {
|
|
*St0
|
|
Name *int
|
|
}
|
|
|
|
validate := func(val any) {
|
|
input := []byte(`{"Name": "hello"}`)
|
|
err := LoadFromJsonBytes(input, val)
|
|
assert.ErrorAs(t, err, &dupErr)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
validate(&St0{})
|
|
validate(&St1{})
|
|
validate(&St2{})
|
|
validate(&St3{})
|
|
validate(&St4{})
|
|
})
|
|
}
|
|
|
|
func TestFieldOverwriteComplicated(t *testing.T) {
|
|
t.Run("double maps", func(t *testing.T) {
|
|
type (
|
|
Base1 struct {
|
|
Values map[string]string
|
|
}
|
|
Base2 struct {
|
|
Values map[string]string
|
|
}
|
|
Config struct {
|
|
Base1
|
|
Base2
|
|
}
|
|
)
|
|
|
|
var c Config
|
|
input := []byte(`{"Values": {"Key": "Value"}}`)
|
|
assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
|
|
})
|
|
|
|
t.Run("merge children", func(t *testing.T) {
|
|
type (
|
|
Inner1 struct {
|
|
Name string
|
|
}
|
|
Inner2 struct {
|
|
Age int
|
|
}
|
|
Base1 struct {
|
|
Inner Inner1
|
|
}
|
|
Base2 struct {
|
|
Inner Inner2
|
|
}
|
|
Config struct {
|
|
Base1
|
|
Base2
|
|
}
|
|
)
|
|
|
|
var c Config
|
|
input := []byte(`{"Inner": {"Name": "foo", "Age": 10}}`)
|
|
if assert.NoError(t, LoadFromJsonBytes(input, &c)) {
|
|
assert.Equal(t, "foo", c.Base1.Inner.Name)
|
|
assert.Equal(t, 10, c.Base2.Inner.Age)
|
|
}
|
|
})
|
|
|
|
t.Run("overwritten maps", func(t *testing.T) {
|
|
type (
|
|
Inner struct {
|
|
Map map[string]string
|
|
}
|
|
Config struct {
|
|
Map map[string]string
|
|
Inner
|
|
}
|
|
)
|
|
|
|
var c Config
|
|
input := []byte(`{"Inner": {"Map": {"Key": "Value"}}}`)
|
|
assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
|
|
})
|
|
|
|
t.Run("overwritten nested maps", func(t *testing.T) {
|
|
type (
|
|
Inner struct {
|
|
Map map[string]string
|
|
}
|
|
Middle1 struct {
|
|
Map map[string]string
|
|
Inner
|
|
}
|
|
Middle2 struct {
|
|
Map map[string]string
|
|
Inner
|
|
}
|
|
Config struct {
|
|
Middle1
|
|
Middle2
|
|
}
|
|
)
|
|
|
|
var c Config
|
|
input := []byte(`{"Middle1": {"Inner": {"Map": {"Key": "Value"}}}}`)
|
|
assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
|
|
})
|
|
|
|
t.Run("overwritten outer/inner maps", func(t *testing.T) {
|
|
type (
|
|
Inner struct {
|
|
Map map[string]string
|
|
}
|
|
Middle struct {
|
|
Inner
|
|
Map map[string]string
|
|
}
|
|
Config struct {
|
|
Middle
|
|
}
|
|
)
|
|
|
|
var c Config
|
|
input := []byte(`{"Middle": {"Inner": {"Map": {"Key": "Value"}}}}`)
|
|
assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
|
|
})
|
|
|
|
t.Run("overwritten anonymous maps", func(t *testing.T) {
|
|
type (
|
|
Inner struct {
|
|
Map map[string]string
|
|
}
|
|
Middle struct {
|
|
Inner
|
|
Map map[string]string
|
|
}
|
|
Elem map[string]Middle
|
|
Config struct {
|
|
Elem
|
|
}
|
|
)
|
|
|
|
var c Config
|
|
input := []byte(`{"Elem": {"Key": {"Inner": {"Map": {"Key": "Value"}}}}}`)
|
|
assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
|
|
})
|
|
|
|
t.Run("overwritten primitive and map", func(t *testing.T) {
|
|
type (
|
|
Inner struct {
|
|
Value string
|
|
}
|
|
Elem map[string]Inner
|
|
Named struct {
|
|
Elem string
|
|
}
|
|
Config struct {
|
|
Named
|
|
Elem
|
|
}
|
|
)
|
|
|
|
var c Config
|
|
input := []byte(`{"Elem": {"Key": {"Value": "Value"}}}`)
|
|
assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
|
|
})
|
|
|
|
t.Run("overwritten map and slice", func(t *testing.T) {
|
|
type (
|
|
Inner struct {
|
|
Value string
|
|
}
|
|
Elem []Inner
|
|
Named struct {
|
|
Elem string
|
|
}
|
|
Config struct {
|
|
Named
|
|
Elem
|
|
}
|
|
)
|
|
|
|
var c Config
|
|
input := []byte(`{"Elem": {"Key": {"Value": "Value"}}}`)
|
|
assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
|
|
})
|
|
|
|
t.Run("overwritten map and string", func(t *testing.T) {
|
|
type (
|
|
Elem string
|
|
Named struct {
|
|
Elem string
|
|
}
|
|
Config struct {
|
|
Named
|
|
Elem
|
|
}
|
|
)
|
|
|
|
var c Config
|
|
input := []byte(`{"Elem": {"Key": {"Value": "Value"}}}`)
|
|
assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
|
|
})
|
|
}
|
|
|
|
func TestLoadNamedFieldOverwritten(t *testing.T) {
|
|
t.Run("overwritten named struct", func(t *testing.T) {
|
|
type (
|
|
Elem string
|
|
Named struct {
|
|
Elem string
|
|
}
|
|
Base struct {
|
|
Named
|
|
Elem
|
|
}
|
|
Config struct {
|
|
Val Base
|
|
}
|
|
)
|
|
|
|
var c Config
|
|
input := []byte(`{"Val": {"Elem": {"Key": {"Value": "Value"}}}}`)
|
|
assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
|
|
})
|
|
|
|
t.Run("overwritten named []struct", func(t *testing.T) {
|
|
type (
|
|
Elem string
|
|
Named struct {
|
|
Elem string
|
|
}
|
|
Base struct {
|
|
Named
|
|
Elem
|
|
}
|
|
Config struct {
|
|
Vals []Base
|
|
}
|
|
)
|
|
|
|
var c Config
|
|
input := []byte(`{"Vals": [{"Elem": {"Key": {"Value": "Value"}}}]}`)
|
|
assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
|
|
})
|
|
|
|
t.Run("overwritten named map[string]struct", func(t *testing.T) {
|
|
type (
|
|
Elem string
|
|
Named struct {
|
|
Elem string
|
|
}
|
|
Base struct {
|
|
Named
|
|
Elem
|
|
}
|
|
Config struct {
|
|
Vals map[string]Base
|
|
}
|
|
)
|
|
|
|
var c Config
|
|
input := []byte(`{"Vals": {"Key": {"Elem": {"Key": {"Value": "Value"}}}}}`)
|
|
assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
|
|
})
|
|
|
|
t.Run("overwritten named *struct", func(t *testing.T) {
|
|
type (
|
|
Elem string
|
|
Named struct {
|
|
Elem string
|
|
}
|
|
Base struct {
|
|
Named
|
|
Elem
|
|
}
|
|
Config struct {
|
|
Vals *Base
|
|
}
|
|
)
|
|
|
|
var c Config
|
|
input := []byte(`{"Vals": [{"Elem": {"Key": {"Value": "Value"}}}]}`)
|
|
assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
|
|
})
|
|
|
|
t.Run("overwritten named struct", func(t *testing.T) {
|
|
type (
|
|
Named struct {
|
|
Elem string
|
|
}
|
|
Base struct {
|
|
Named
|
|
Elem Named
|
|
}
|
|
Config struct {
|
|
Val Base
|
|
}
|
|
)
|
|
|
|
var c Config
|
|
input := []byte(`{"Val": {"Elem": "Value"}}`)
|
|
assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
|
|
})
|
|
|
|
t.Run("overwritten named struct", func(t *testing.T) {
|
|
type Config struct {
|
|
Val chan int
|
|
}
|
|
|
|
var c Config
|
|
input := []byte(`{"Val": 1}`)
|
|
assert.Error(t, LoadFromJsonBytes(input, &c))
|
|
})
|
|
}
|
|
|
|
func TestLoadLowerMemberShouldNotConflict(t *testing.T) {
|
|
type (
|
|
Redis struct {
|
|
db uint
|
|
}
|
|
|
|
Config struct {
|
|
db uint
|
|
Redis
|
|
}
|
|
)
|
|
|
|
var c Config
|
|
assert.NoError(t, LoadFromJsonBytes([]byte(`{}`), &c))
|
|
assert.Zero(t, c.db)
|
|
assert.Zero(t, c.Redis.db)
|
|
}
|
|
|
|
func TestFillDefaultUnmarshal(t *testing.T) {
|
|
t.Run("nil", func(t *testing.T) {
|
|
type St struct{}
|
|
err := FillDefault(St{})
|
|
assert.Error(t, err)
|
|
})
|
|
|
|
t.Run("not nil", func(t *testing.T) {
|
|
type St struct{}
|
|
err := FillDefault(&St{})
|
|
assert.NoError(t, err)
|
|
})
|
|
|
|
t.Run("default", func(t *testing.T) {
|
|
type St struct {
|
|
A string `json:",default=a"`
|
|
B string
|
|
}
|
|
var st St
|
|
err := FillDefault(&st)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, st.A, "a")
|
|
})
|
|
|
|
t.Run("env", func(t *testing.T) {
|
|
type St struct {
|
|
A string `json:",default=a"`
|
|
B string
|
|
C string `json:",env=TEST_C"`
|
|
}
|
|
t.Setenv("TEST_C", "c")
|
|
|
|
var st St
|
|
err := FillDefault(&st)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, st.A, "a")
|
|
assert.Equal(t, st.C, "c")
|
|
})
|
|
|
|
t.Run("has value", func(t *testing.T) {
|
|
type St struct {
|
|
A string `json:",default=a"`
|
|
B string
|
|
}
|
|
var st = St{
|
|
A: "b",
|
|
}
|
|
err := FillDefault(&st)
|
|
assert.Error(t, err)
|
|
})
|
|
}
|
|
|
|
func TestConfigWithJsonTag(t *testing.T) {
|
|
t.Run("map with value", func(t *testing.T) {
|
|
var input = []byte(`[Value]
|
|
[Value.first]
|
|
Email = "foo"
|
|
[Value.second]
|
|
Email = "bar"`)
|
|
|
|
type Value struct {
|
|
Email string
|
|
}
|
|
|
|
type Config struct {
|
|
ValueMap map[string]Value `json:"Value"`
|
|
}
|
|
|
|
var c Config
|
|
if assert.NoError(t, LoadFromTomlBytes(input, &c)) {
|
|
assert.Len(t, c.ValueMap, 2)
|
|
}
|
|
})
|
|
|
|
t.Run("map with ptr value", func(t *testing.T) {
|
|
var input = []byte(`[Value]
|
|
[Value.first]
|
|
Email = "foo"
|
|
[Value.second]
|
|
Email = "bar"`)
|
|
|
|
type Value struct {
|
|
Email string
|
|
}
|
|
|
|
type Config struct {
|
|
ValueMap map[string]*Value `json:"Value"`
|
|
}
|
|
|
|
var c Config
|
|
if assert.NoError(t, LoadFromTomlBytes(input, &c)) {
|
|
assert.Len(t, c.ValueMap, 2)
|
|
}
|
|
})
|
|
|
|
t.Run("map with optional", func(t *testing.T) {
|
|
var input = []byte(`[Value]
|
|
[Value.first]
|
|
Email = "foo"
|
|
[Value.second]
|
|
Email = "bar"`)
|
|
|
|
type Value struct {
|
|
Email string
|
|
}
|
|
|
|
type Config struct {
|
|
Value map[string]Value `json:",optional"`
|
|
}
|
|
|
|
var c Config
|
|
if assert.NoError(t, LoadFromTomlBytes(input, &c)) {
|
|
assert.Len(t, c.Value, 2)
|
|
}
|
|
})
|
|
|
|
t.Run("map with empty tag", func(t *testing.T) {
|
|
var input = []byte(`[Value]
|
|
[Value.first]
|
|
Email = "foo"
|
|
[Value.second]
|
|
Email = "bar"`)
|
|
|
|
type Value struct {
|
|
Email string
|
|
}
|
|
|
|
type Config struct {
|
|
Value map[string]Value `json:" "`
|
|
}
|
|
|
|
var c Config
|
|
if assert.NoError(t, LoadFromTomlBytes(input, &c)) {
|
|
assert.Len(t, c.Value, 2)
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test_getFullName(t *testing.T) {
|
|
assert.Equal(t, "a.b", getFullName("a", "b"))
|
|
assert.Equal(t, "a", getFullName("", "a"))
|
|
}
|
|
|
|
func Test_buildFieldsInfo(t *testing.T) {
|
|
type ParentSt struct {
|
|
Name string
|
|
M map[string]int
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
t reflect.Type
|
|
ok bool
|
|
containsKey string
|
|
}{
|
|
{
|
|
name: "normal",
|
|
t: reflect.TypeOf(struct{ A string }{}),
|
|
ok: true,
|
|
},
|
|
{
|
|
name: "struct anonymous",
|
|
t: reflect.TypeOf(struct {
|
|
ParentSt
|
|
Name string
|
|
}{}),
|
|
ok: false,
|
|
containsKey: newConflictKeyError("name").Error(),
|
|
},
|
|
{
|
|
name: "struct ptr anonymous",
|
|
t: reflect.TypeOf(struct {
|
|
*ParentSt
|
|
Name string
|
|
}{}),
|
|
ok: false,
|
|
containsKey: newConflictKeyError("name").Error(),
|
|
},
|
|
{
|
|
name: "more struct anonymous",
|
|
t: reflect.TypeOf(struct {
|
|
Value struct {
|
|
ParentSt
|
|
Name string
|
|
}
|
|
}{}),
|
|
ok: false,
|
|
containsKey: newConflictKeyError("value.name").Error(),
|
|
},
|
|
{
|
|
name: "map anonymous",
|
|
t: reflect.TypeOf(struct {
|
|
ParentSt
|
|
M string
|
|
}{}),
|
|
ok: false,
|
|
containsKey: newConflictKeyError("m").Error(),
|
|
},
|
|
{
|
|
name: "map more anonymous",
|
|
t: reflect.TypeOf(struct {
|
|
Value struct {
|
|
ParentSt
|
|
M string
|
|
}
|
|
}{}),
|
|
ok: false,
|
|
containsKey: newConflictKeyError("value.m").Error(),
|
|
},
|
|
{
|
|
name: "struct slice anonymous",
|
|
t: reflect.TypeOf([]struct {
|
|
ParentSt
|
|
Name string
|
|
}{}),
|
|
ok: false,
|
|
containsKey: newConflictKeyError("name").Error(),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
_, err := buildFieldsInfo(tt.t, "")
|
|
if tt.ok {
|
|
assert.NoError(t, err)
|
|
} else {
|
|
assert.Error(t, err)
|
|
assert.Equal(t, err.Error(), tt.containsKey)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func createTempFile(ext, text string) (string, error) {
|
|
tmpFile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text))+"*"+ext)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if err := os.WriteFile(tmpFile.Name(), []byte(text), os.ModeTemporary); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
filename := tmpFile.Name()
|
|
if err = tmpFile.Close(); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return filename, nil
|
|
}
|