568 lines
11 KiB
Go
568 lines
11 KiB
Go
package mapstructure
|
|
|
|
import (
|
|
"errors"
|
|
"math/big"
|
|
"net"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestComposeDecodeHookFunc(t *testing.T) {
|
|
f1 := func(
|
|
f reflect.Kind,
|
|
t reflect.Kind,
|
|
data interface{}) (interface{}, error) {
|
|
return data.(string) + "foo", nil
|
|
}
|
|
|
|
f2 := func(
|
|
f reflect.Kind,
|
|
t reflect.Kind,
|
|
data interface{}) (interface{}, error) {
|
|
return data.(string) + "bar", nil
|
|
}
|
|
|
|
f := ComposeDecodeHookFunc(f1, f2)
|
|
|
|
result, err := DecodeHookExec(
|
|
f, reflect.ValueOf(""), reflect.ValueOf([]byte("")))
|
|
if err != nil {
|
|
t.Fatalf("bad: %s", err)
|
|
}
|
|
if result.(string) != "foobar" {
|
|
t.Fatalf("bad: %#v", result)
|
|
}
|
|
}
|
|
|
|
func TestComposeDecodeHookFunc_err(t *testing.T) {
|
|
f1 := func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error) {
|
|
return nil, errors.New("foo")
|
|
}
|
|
|
|
f2 := func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error) {
|
|
panic("NOPE")
|
|
}
|
|
|
|
f := ComposeDecodeHookFunc(f1, f2)
|
|
|
|
_, err := DecodeHookExec(
|
|
f, reflect.ValueOf(""), reflect.ValueOf([]byte("")))
|
|
if err.Error() != "foo" {
|
|
t.Fatalf("bad: %s", err)
|
|
}
|
|
}
|
|
|
|
func TestComposeDecodeHookFunc_kinds(t *testing.T) {
|
|
var f2From reflect.Kind
|
|
|
|
f1 := func(
|
|
f reflect.Kind,
|
|
t reflect.Kind,
|
|
data interface{}) (interface{}, error) {
|
|
return int(42), nil
|
|
}
|
|
|
|
f2 := func(
|
|
f reflect.Kind,
|
|
t reflect.Kind,
|
|
data interface{}) (interface{}, error) {
|
|
f2From = f
|
|
return data, nil
|
|
}
|
|
|
|
f := ComposeDecodeHookFunc(f1, f2)
|
|
|
|
_, err := DecodeHookExec(
|
|
f, reflect.ValueOf(""), reflect.ValueOf([]byte("")))
|
|
if err != nil {
|
|
t.Fatalf("bad: %s", err)
|
|
}
|
|
if f2From != reflect.Int {
|
|
t.Fatalf("bad: %#v", f2From)
|
|
}
|
|
}
|
|
|
|
func TestOrComposeDecodeHookFunc(t *testing.T) {
|
|
f1 := func(
|
|
f reflect.Kind,
|
|
t reflect.Kind,
|
|
data interface{}) (interface{}, error) {
|
|
return data.(string) + "foo", nil
|
|
}
|
|
|
|
f2 := func(
|
|
f reflect.Kind,
|
|
t reflect.Kind,
|
|
data interface{}) (interface{}, error) {
|
|
return data.(string) + "bar", nil
|
|
}
|
|
|
|
f := OrComposeDecodeHookFunc(f1, f2)
|
|
|
|
result, err := DecodeHookExec(
|
|
f, reflect.ValueOf(""), reflect.ValueOf([]byte("")))
|
|
if err != nil {
|
|
t.Fatalf("bad: %s", err)
|
|
}
|
|
if result.(string) != "foo" {
|
|
t.Fatalf("bad: %#v", result)
|
|
}
|
|
}
|
|
|
|
func TestOrComposeDecodeHookFunc_correctValueIsLast(t *testing.T) {
|
|
f1 := func(
|
|
f reflect.Kind,
|
|
t reflect.Kind,
|
|
data interface{}) (interface{}, error) {
|
|
return nil, errors.New("f1 error")
|
|
}
|
|
|
|
f2 := func(
|
|
f reflect.Kind,
|
|
t reflect.Kind,
|
|
data interface{}) (interface{}, error) {
|
|
return nil, errors.New("f2 error")
|
|
}
|
|
|
|
f3 := func(
|
|
f reflect.Kind,
|
|
t reflect.Kind,
|
|
data interface{}) (interface{}, error) {
|
|
return data.(string) + "bar", nil
|
|
}
|
|
|
|
f := OrComposeDecodeHookFunc(f1, f2, f3)
|
|
|
|
result, err := DecodeHookExec(
|
|
f, reflect.ValueOf(""), reflect.ValueOf([]byte("")))
|
|
if err != nil {
|
|
t.Fatalf("bad: %s", err)
|
|
}
|
|
if result.(string) != "bar" {
|
|
t.Fatalf("bad: %#v", result)
|
|
}
|
|
}
|
|
|
|
func TestOrComposeDecodeHookFunc_err(t *testing.T) {
|
|
f1 := func(
|
|
f reflect.Kind,
|
|
t reflect.Kind,
|
|
data interface{}) (interface{}, error) {
|
|
return nil, errors.New("f1 error")
|
|
}
|
|
|
|
f2 := func(
|
|
f reflect.Kind,
|
|
t reflect.Kind,
|
|
data interface{}) (interface{}, error) {
|
|
return nil, errors.New("f2 error")
|
|
}
|
|
|
|
f := OrComposeDecodeHookFunc(f1, f2)
|
|
|
|
_, err := DecodeHookExec(
|
|
f, reflect.ValueOf(""), reflect.ValueOf([]byte("")))
|
|
if err == nil {
|
|
t.Fatalf("bad: should return an error")
|
|
}
|
|
if err.Error() != "f1 error\nf2 error\n" {
|
|
t.Fatalf("bad: %s", err)
|
|
}
|
|
}
|
|
|
|
func TestComposeDecodeHookFunc_safe_nofuncs(t *testing.T) {
|
|
f := ComposeDecodeHookFunc()
|
|
type myStruct2 struct {
|
|
MyInt int
|
|
}
|
|
|
|
type myStruct1 struct {
|
|
Blah map[string]myStruct2
|
|
}
|
|
|
|
src := &myStruct1{Blah: map[string]myStruct2{
|
|
"test": {
|
|
MyInt: 1,
|
|
},
|
|
}}
|
|
|
|
dst := &myStruct1{}
|
|
dConf := &DecoderConfig{
|
|
Result: dst,
|
|
ErrorUnused: true,
|
|
DecodeHook: f,
|
|
}
|
|
d, err := NewDecoder(dConf)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = d.Decode(src)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestStringToSliceHookFunc(t *testing.T) {
|
|
f := StringToSliceHookFunc(",")
|
|
|
|
strValue := reflect.ValueOf("42")
|
|
sliceValue := reflect.ValueOf([]byte("42"))
|
|
cases := []struct {
|
|
f, t reflect.Value
|
|
result interface{}
|
|
err bool
|
|
}{
|
|
{sliceValue, sliceValue, []byte("42"), false},
|
|
{strValue, strValue, "42", false},
|
|
{
|
|
reflect.ValueOf("foo,bar,baz"),
|
|
sliceValue,
|
|
[]string{"foo", "bar", "baz"},
|
|
false,
|
|
},
|
|
{
|
|
reflect.ValueOf(""),
|
|
sliceValue,
|
|
[]string{},
|
|
false,
|
|
},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
actual, err := DecodeHookExec(f, tc.f, tc.t)
|
|
if tc.err != (err != nil) {
|
|
t.Fatalf("case %d: expected err %#v", i, tc.err)
|
|
}
|
|
if !reflect.DeepEqual(actual, tc.result) {
|
|
t.Fatalf(
|
|
"case %d: expected %#v, got %#v",
|
|
i, tc.result, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStringToTimeDurationHookFunc(t *testing.T) {
|
|
f := StringToTimeDurationHookFunc()
|
|
|
|
timeValue := reflect.ValueOf(time.Duration(5))
|
|
strValue := reflect.ValueOf("")
|
|
cases := []struct {
|
|
f, t reflect.Value
|
|
result interface{}
|
|
err bool
|
|
}{
|
|
{reflect.ValueOf("5s"), timeValue, 5 * time.Second, false},
|
|
{reflect.ValueOf("5"), timeValue, time.Duration(0), true},
|
|
{reflect.ValueOf("5"), strValue, "5", false},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
actual, err := DecodeHookExec(f, tc.f, tc.t)
|
|
if tc.err != (err != nil) {
|
|
t.Fatalf("case %d: expected err %#v", i, tc.err)
|
|
}
|
|
if !reflect.DeepEqual(actual, tc.result) {
|
|
t.Fatalf(
|
|
"case %d: expected %#v, got %#v",
|
|
i, tc.result, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStringToTimeHookFunc(t *testing.T) {
|
|
strValue := reflect.ValueOf("5")
|
|
timeValue := reflect.ValueOf(time.Time{})
|
|
cases := []struct {
|
|
f, t reflect.Value
|
|
layout string
|
|
result interface{}
|
|
err bool
|
|
}{
|
|
{reflect.ValueOf("2006-01-02T15:04:05Z"), timeValue, time.RFC3339,
|
|
time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC), false},
|
|
{strValue, timeValue, time.RFC3339, time.Time{}, true},
|
|
{strValue, strValue, time.RFC3339, "5", false},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
f := StringToTimeHookFunc(tc.layout)
|
|
actual, err := DecodeHookExec(f, tc.f, tc.t)
|
|
if tc.err != (err != nil) {
|
|
t.Fatalf("case %d: expected err %#v", i, tc.err)
|
|
}
|
|
if !reflect.DeepEqual(actual, tc.result) {
|
|
t.Fatalf(
|
|
"case %d: expected %#v, got %#v",
|
|
i, tc.result, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStringToIPHookFunc(t *testing.T) {
|
|
strValue := reflect.ValueOf("5")
|
|
ipValue := reflect.ValueOf(net.IP{})
|
|
cases := []struct {
|
|
f, t reflect.Value
|
|
result interface{}
|
|
err bool
|
|
}{
|
|
{reflect.ValueOf("1.2.3.4"), ipValue,
|
|
net.IPv4(0x01, 0x02, 0x03, 0x04), false},
|
|
{strValue, ipValue, net.IP{}, true},
|
|
{strValue, strValue, "5", false},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
f := StringToIPHookFunc()
|
|
actual, err := DecodeHookExec(f, tc.f, tc.t)
|
|
if tc.err != (err != nil) {
|
|
t.Fatalf("case %d: expected err %#v", i, tc.err)
|
|
}
|
|
if !reflect.DeepEqual(actual, tc.result) {
|
|
t.Fatalf(
|
|
"case %d: expected %#v, got %#v",
|
|
i, tc.result, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStringToIPNetHookFunc(t *testing.T) {
|
|
strValue := reflect.ValueOf("5")
|
|
ipNetValue := reflect.ValueOf(net.IPNet{})
|
|
var nilNet *net.IPNet = nil
|
|
|
|
cases := []struct {
|
|
f, t reflect.Value
|
|
result interface{}
|
|
err bool
|
|
}{
|
|
{reflect.ValueOf("1.2.3.4/24"), ipNetValue,
|
|
&net.IPNet{
|
|
IP: net.IP{0x01, 0x02, 0x03, 0x00},
|
|
Mask: net.IPv4Mask(0xff, 0xff, 0xff, 0x00),
|
|
}, false},
|
|
{strValue, ipNetValue, nilNet, true},
|
|
{strValue, strValue, "5", false},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
f := StringToIPNetHookFunc()
|
|
actual, err := DecodeHookExec(f, tc.f, tc.t)
|
|
if tc.err != (err != nil) {
|
|
t.Fatalf("case %d: expected err %#v", i, tc.err)
|
|
}
|
|
if !reflect.DeepEqual(actual, tc.result) {
|
|
t.Fatalf(
|
|
"case %d: expected %#v, got %#v",
|
|
i, tc.result, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWeaklyTypedHook(t *testing.T) {
|
|
var f DecodeHookFunc = WeaklyTypedHook
|
|
|
|
strValue := reflect.ValueOf("")
|
|
cases := []struct {
|
|
f, t reflect.Value
|
|
result interface{}
|
|
err bool
|
|
}{
|
|
// TO STRING
|
|
{
|
|
reflect.ValueOf(false),
|
|
strValue,
|
|
"0",
|
|
false,
|
|
},
|
|
|
|
{
|
|
reflect.ValueOf(true),
|
|
strValue,
|
|
"1",
|
|
false,
|
|
},
|
|
|
|
{
|
|
reflect.ValueOf(float32(7)),
|
|
strValue,
|
|
"7",
|
|
false,
|
|
},
|
|
|
|
{
|
|
reflect.ValueOf(int(7)),
|
|
strValue,
|
|
"7",
|
|
false,
|
|
},
|
|
|
|
{
|
|
reflect.ValueOf([]uint8("foo")),
|
|
strValue,
|
|
"foo",
|
|
false,
|
|
},
|
|
|
|
{
|
|
reflect.ValueOf(uint(7)),
|
|
strValue,
|
|
"7",
|
|
false,
|
|
},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
actual, err := DecodeHookExec(f, tc.f, tc.t)
|
|
if tc.err != (err != nil) {
|
|
t.Fatalf("case %d: expected err %#v", i, tc.err)
|
|
}
|
|
if !reflect.DeepEqual(actual, tc.result) {
|
|
t.Fatalf(
|
|
"case %d: expected %#v, got %#v",
|
|
i, tc.result, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStructToMapHookFuncTabled(t *testing.T) {
|
|
var f DecodeHookFunc = RecursiveStructToMapHookFunc()
|
|
|
|
type b struct {
|
|
TestKey string
|
|
}
|
|
|
|
type a struct {
|
|
Sub b
|
|
}
|
|
|
|
testStruct := a{
|
|
Sub: b{
|
|
TestKey: "testval",
|
|
},
|
|
}
|
|
|
|
testMap := map[string]interface{}{
|
|
"Sub": map[string]interface{}{
|
|
"TestKey": "testval",
|
|
},
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
receiver interface{}
|
|
input interface{}
|
|
expected interface{}
|
|
err bool
|
|
}{
|
|
{
|
|
"map receiver",
|
|
func() interface{} {
|
|
var res map[string]interface{}
|
|
return &res
|
|
}(),
|
|
testStruct,
|
|
&testMap,
|
|
false,
|
|
},
|
|
{
|
|
"interface receiver",
|
|
func() interface{} {
|
|
var res interface{}
|
|
return &res
|
|
}(),
|
|
testStruct,
|
|
func() interface{} {
|
|
var exp interface{} = testMap
|
|
return &exp
|
|
}(),
|
|
false,
|
|
},
|
|
{
|
|
"slice receiver errors",
|
|
func() interface{} {
|
|
var res []string
|
|
return &res
|
|
}(),
|
|
testStruct,
|
|
new([]string),
|
|
true,
|
|
},
|
|
{
|
|
"slice to slice - no change",
|
|
func() interface{} {
|
|
var res []string
|
|
return &res
|
|
}(),
|
|
[]string{"a", "b"},
|
|
&[]string{"a", "b"},
|
|
false,
|
|
},
|
|
{
|
|
"string to string - no change",
|
|
func() interface{} {
|
|
var res string
|
|
return &res
|
|
}(),
|
|
"test",
|
|
func() *string {
|
|
s := "test"
|
|
return &s
|
|
}(),
|
|
false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
cfg := &DecoderConfig{
|
|
DecodeHook: f,
|
|
Result: tc.receiver,
|
|
}
|
|
|
|
d, err := NewDecoder(cfg)
|
|
if err != nil {
|
|
t.Fatalf("unexpected err %#v", err)
|
|
}
|
|
|
|
err = d.Decode(tc.input)
|
|
if tc.err != (err != nil) {
|
|
t.Fatalf("expected err %#v", err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(tc.expected, tc.receiver) {
|
|
t.Fatalf("expected %#v, got %#v",
|
|
tc.expected, tc.receiver)
|
|
}
|
|
})
|
|
|
|
}
|
|
}
|
|
|
|
func TestTextUnmarshallerHookFunc(t *testing.T) {
|
|
cases := []struct {
|
|
f, t reflect.Value
|
|
result interface{}
|
|
err bool
|
|
}{
|
|
{reflect.ValueOf("42"), reflect.ValueOf(big.Int{}), big.NewInt(42), false},
|
|
{reflect.ValueOf("invalid"), reflect.ValueOf(big.Int{}), nil, true},
|
|
{reflect.ValueOf("5"), reflect.ValueOf("5"), "5", false},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
f := TextUnmarshallerHookFunc()
|
|
actual, err := DecodeHookExec(f, tc.f, tc.t)
|
|
if tc.err != (err != nil) {
|
|
t.Fatalf("case %d: expected err %#v", i, tc.err)
|
|
}
|
|
if !reflect.DeepEqual(actual, tc.result) {
|
|
t.Fatalf(
|
|
"case %d: expected %#v, got %#v",
|
|
i, tc.result, actual)
|
|
}
|
|
}
|
|
}
|