1048 lines
20 KiB
Go
1048 lines
20 KiB
Go
|
package mapstructure
|
||
|
|
||
|
import (
|
||
|
"reflect"
|
||
|
"sort"
|
||
|
"strings"
|
||
|
"testing"
|
||
|
)
|
||
|
|
||
|
type Basic struct {
|
||
|
Vstring string
|
||
|
Vint int
|
||
|
Vuint uint
|
||
|
Vbool bool
|
||
|
Vfloat float64
|
||
|
Vextra string
|
||
|
vsilent bool
|
||
|
Vdata interface{}
|
||
|
}
|
||
|
|
||
|
type BasicSquash struct {
|
||
|
Test Basic `mapstructure:",squash"`
|
||
|
}
|
||
|
|
||
|
type Embedded struct {
|
||
|
Basic
|
||
|
Vunique string
|
||
|
}
|
||
|
|
||
|
type EmbeddedPointer struct {
|
||
|
*Basic
|
||
|
Vunique string
|
||
|
}
|
||
|
|
||
|
type EmbeddedSquash struct {
|
||
|
Basic `mapstructure:",squash"`
|
||
|
Vunique string
|
||
|
}
|
||
|
|
||
|
type SquashOnNonStructType struct {
|
||
|
InvalidSquashType int `mapstructure:",squash"`
|
||
|
}
|
||
|
|
||
|
type Map struct {
|
||
|
Vfoo string
|
||
|
Vother map[string]string
|
||
|
}
|
||
|
|
||
|
type MapOfStruct struct {
|
||
|
Value map[string]Basic
|
||
|
}
|
||
|
|
||
|
type Nested struct {
|
||
|
Vfoo string
|
||
|
Vbar Basic
|
||
|
}
|
||
|
|
||
|
type NestedPointer struct {
|
||
|
Vfoo string
|
||
|
Vbar *Basic
|
||
|
}
|
||
|
|
||
|
type Slice struct {
|
||
|
Vfoo string
|
||
|
Vbar []string
|
||
|
}
|
||
|
|
||
|
type SliceOfStruct struct {
|
||
|
Value []Basic
|
||
|
}
|
||
|
|
||
|
type Tagged struct {
|
||
|
Extra string `mapstructure:"bar,what,what"`
|
||
|
Value string `mapstructure:"foo"`
|
||
|
}
|
||
|
|
||
|
type TypeConversionResult struct {
|
||
|
IntToFloat float32
|
||
|
IntToUint uint
|
||
|
IntToBool bool
|
||
|
IntToString string
|
||
|
UintToInt int
|
||
|
UintToFloat float32
|
||
|
UintToBool bool
|
||
|
UintToString string
|
||
|
BoolToInt int
|
||
|
BoolToUint uint
|
||
|
BoolToFloat float32
|
||
|
BoolToString string
|
||
|
FloatToInt int
|
||
|
FloatToUint uint
|
||
|
FloatToBool bool
|
||
|
FloatToString string
|
||
|
SliceUint8ToString string
|
||
|
StringToInt int
|
||
|
StringToUint uint
|
||
|
StringToBool bool
|
||
|
StringToFloat float32
|
||
|
SliceToMap map[string]interface{}
|
||
|
MapToSlice []interface{}
|
||
|
}
|
||
|
|
||
|
func TestBasicTypes(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"vstring": "foo",
|
||
|
"vint": 42,
|
||
|
"Vuint": 42,
|
||
|
"vbool": true,
|
||
|
"Vfloat": 42.42,
|
||
|
"vsilent": true,
|
||
|
"vdata": 42,
|
||
|
}
|
||
|
|
||
|
var result Basic
|
||
|
err := Decode(input, &result)
|
||
|
if err != nil {
|
||
|
t.Errorf("got an err: %s", err.Error())
|
||
|
t.FailNow()
|
||
|
}
|
||
|
|
||
|
if result.Vstring != "foo" {
|
||
|
t.Errorf("vstring value should be 'foo': %#v", result.Vstring)
|
||
|
}
|
||
|
|
||
|
if result.Vint != 42 {
|
||
|
t.Errorf("vint value should be 42: %#v", result.Vint)
|
||
|
}
|
||
|
|
||
|
if result.Vuint != 42 {
|
||
|
t.Errorf("vuint value should be 42: %#v", result.Vuint)
|
||
|
}
|
||
|
|
||
|
if result.Vbool != true {
|
||
|
t.Errorf("vbool value should be true: %#v", result.Vbool)
|
||
|
}
|
||
|
|
||
|
if result.Vfloat != 42.42 {
|
||
|
t.Errorf("vfloat value should be 42.42: %#v", result.Vfloat)
|
||
|
}
|
||
|
|
||
|
if result.Vextra != "" {
|
||
|
t.Errorf("vextra value should be empty: %#v", result.Vextra)
|
||
|
}
|
||
|
|
||
|
if result.vsilent != false {
|
||
|
t.Error("vsilent should not be set, it is unexported")
|
||
|
}
|
||
|
|
||
|
if result.Vdata != 42 {
|
||
|
t.Error("vdata should be valid")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestBasic_IntWithFloat(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"vint": float64(42),
|
||
|
}
|
||
|
|
||
|
var result Basic
|
||
|
err := Decode(input, &result)
|
||
|
if err != nil {
|
||
|
t.Fatalf("got an err: %s", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestBasic_Merge(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"vint": 42,
|
||
|
}
|
||
|
|
||
|
var result Basic
|
||
|
result.Vuint = 100
|
||
|
err := Decode(input, &result)
|
||
|
if err != nil {
|
||
|
t.Fatalf("got an err: %s", err)
|
||
|
}
|
||
|
|
||
|
expected := Basic{
|
||
|
Vint: 42,
|
||
|
Vuint: 100,
|
||
|
}
|
||
|
if !reflect.DeepEqual(result, expected) {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestDecode_BasicSquash(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"vstring": "foo",
|
||
|
}
|
||
|
|
||
|
var result BasicSquash
|
||
|
err := Decode(input, &result)
|
||
|
if err != nil {
|
||
|
t.Fatalf("got an err: %s", err.Error())
|
||
|
}
|
||
|
|
||
|
if result.Test.Vstring != "foo" {
|
||
|
t.Errorf("vstring value should be 'foo': %#v", result.Test.Vstring)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestDecode_Embedded(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"vstring": "foo",
|
||
|
"Basic": map[string]interface{}{
|
||
|
"vstring": "innerfoo",
|
||
|
},
|
||
|
"vunique": "bar",
|
||
|
}
|
||
|
|
||
|
var result Embedded
|
||
|
err := Decode(input, &result)
|
||
|
if err != nil {
|
||
|
t.Fatalf("got an err: %s", err.Error())
|
||
|
}
|
||
|
|
||
|
if result.Vstring != "innerfoo" {
|
||
|
t.Errorf("vstring value should be 'innerfoo': %#v", result.Vstring)
|
||
|
}
|
||
|
|
||
|
if result.Vunique != "bar" {
|
||
|
t.Errorf("vunique value should be 'bar': %#v", result.Vunique)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestDecode_EmbeddedPointer(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"vstring": "foo",
|
||
|
"Basic": map[string]interface{}{
|
||
|
"vstring": "innerfoo",
|
||
|
},
|
||
|
"vunique": "bar",
|
||
|
}
|
||
|
|
||
|
var result EmbeddedPointer
|
||
|
err := Decode(input, &result)
|
||
|
if err == nil {
|
||
|
t.Fatal("should get error")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestDecode_EmbeddedSquash(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"vstring": "foo",
|
||
|
"vunique": "bar",
|
||
|
}
|
||
|
|
||
|
var result EmbeddedSquash
|
||
|
err := Decode(input, &result)
|
||
|
if err != nil {
|
||
|
t.Fatalf("got an err: %s", err.Error())
|
||
|
}
|
||
|
|
||
|
if result.Vstring != "foo" {
|
||
|
t.Errorf("vstring value should be 'foo': %#v", result.Vstring)
|
||
|
}
|
||
|
|
||
|
if result.Vunique != "bar" {
|
||
|
t.Errorf("vunique value should be 'bar': %#v", result.Vunique)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestDecode_SquashOnNonStructType(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"InvalidSquashType": 42,
|
||
|
}
|
||
|
|
||
|
var result SquashOnNonStructType
|
||
|
err := Decode(input, &result)
|
||
|
if err == nil {
|
||
|
t.Fatal("unexpected success decoding invalid squash field type")
|
||
|
} else if !strings.Contains(err.Error(), "unsupported type for squash") {
|
||
|
t.Fatalf("unexpected error message for invalid squash field type: %s", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestDecode_DecodeHook(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"vint": "WHAT",
|
||
|
}
|
||
|
|
||
|
decodeHook := func(from reflect.Kind, to reflect.Kind, v interface{}) (interface{}, error) {
|
||
|
if from == reflect.String && to != reflect.String {
|
||
|
return 5, nil
|
||
|
}
|
||
|
|
||
|
return v, nil
|
||
|
}
|
||
|
|
||
|
var result Basic
|
||
|
config := &DecoderConfig{
|
||
|
DecodeHook: decodeHook,
|
||
|
Result: &result,
|
||
|
}
|
||
|
|
||
|
decoder, err := NewDecoder(config)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
err = decoder.Decode(input)
|
||
|
if err != nil {
|
||
|
t.Fatalf("got an err: %s", err)
|
||
|
}
|
||
|
|
||
|
if result.Vint != 5 {
|
||
|
t.Errorf("vint should be 5: %#v", result.Vint)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestDecode_DecodeHookType(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"vint": "WHAT",
|
||
|
}
|
||
|
|
||
|
decodeHook := func(from reflect.Type, to reflect.Type, v interface{}) (interface{}, error) {
|
||
|
if from.Kind() == reflect.String &&
|
||
|
to.Kind() != reflect.String {
|
||
|
return 5, nil
|
||
|
}
|
||
|
|
||
|
return v, nil
|
||
|
}
|
||
|
|
||
|
var result Basic
|
||
|
config := &DecoderConfig{
|
||
|
DecodeHook: decodeHook,
|
||
|
Result: &result,
|
||
|
}
|
||
|
|
||
|
decoder, err := NewDecoder(config)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
err = decoder.Decode(input)
|
||
|
if err != nil {
|
||
|
t.Fatalf("got an err: %s", err)
|
||
|
}
|
||
|
|
||
|
if result.Vint != 5 {
|
||
|
t.Errorf("vint should be 5: %#v", result.Vint)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestDecode_Nil(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
var input interface{} = nil
|
||
|
result := Basic{
|
||
|
Vstring: "foo",
|
||
|
}
|
||
|
|
||
|
err := Decode(input, &result)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if result.Vstring != "foo" {
|
||
|
t.Fatalf("bad: %#v", result.Vstring)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestDecode_NonStruct(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"foo": "bar",
|
||
|
"bar": "baz",
|
||
|
}
|
||
|
|
||
|
var result map[string]string
|
||
|
err := Decode(input, &result)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
if result["foo"] != "bar" {
|
||
|
t.Fatal("foo is not bar")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestDecode_StructMatch(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"vbar": Basic{
|
||
|
Vstring: "foo",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
var result Nested
|
||
|
err := Decode(input, &result)
|
||
|
if err != nil {
|
||
|
t.Fatalf("got an err: %s", err.Error())
|
||
|
}
|
||
|
|
||
|
if result.Vbar.Vstring != "foo" {
|
||
|
t.Errorf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestDecode_TypeConversion(t *testing.T) {
|
||
|
input := map[string]interface{}{
|
||
|
"IntToFloat": 42,
|
||
|
"IntToUint": 42,
|
||
|
"IntToBool": 1,
|
||
|
"IntToString": 42,
|
||
|
"UintToInt": 42,
|
||
|
"UintToFloat": 42,
|
||
|
"UintToBool": 42,
|
||
|
"UintToString": 42,
|
||
|
"BoolToInt": true,
|
||
|
"BoolToUint": true,
|
||
|
"BoolToFloat": true,
|
||
|
"BoolToString": true,
|
||
|
"FloatToInt": 42.42,
|
||
|
"FloatToUint": 42.42,
|
||
|
"FloatToBool": 42.42,
|
||
|
"FloatToString": 42.42,
|
||
|
"SliceUint8ToString": []uint8("foo"),
|
||
|
"StringToInt": "42",
|
||
|
"StringToUint": "42",
|
||
|
"StringToBool": "1",
|
||
|
"StringToFloat": "42.42",
|
||
|
"SliceToMap": []interface{}{},
|
||
|
"MapToSlice": map[string]interface{}{},
|
||
|
}
|
||
|
|
||
|
expectedResultStrict := TypeConversionResult{
|
||
|
IntToFloat: 42.0,
|
||
|
IntToUint: 42,
|
||
|
UintToInt: 42,
|
||
|
UintToFloat: 42,
|
||
|
BoolToInt: 0,
|
||
|
BoolToUint: 0,
|
||
|
BoolToFloat: 0,
|
||
|
FloatToInt: 42,
|
||
|
FloatToUint: 42,
|
||
|
}
|
||
|
|
||
|
expectedResultWeak := TypeConversionResult{
|
||
|
IntToFloat: 42.0,
|
||
|
IntToUint: 42,
|
||
|
IntToBool: true,
|
||
|
IntToString: "42",
|
||
|
UintToInt: 42,
|
||
|
UintToFloat: 42,
|
||
|
UintToBool: true,
|
||
|
UintToString: "42",
|
||
|
BoolToInt: 1,
|
||
|
BoolToUint: 1,
|
||
|
BoolToFloat: 1,
|
||
|
BoolToString: "1",
|
||
|
FloatToInt: 42,
|
||
|
FloatToUint: 42,
|
||
|
FloatToBool: true,
|
||
|
FloatToString: "42.42",
|
||
|
SliceUint8ToString: "foo",
|
||
|
StringToInt: 42,
|
||
|
StringToUint: 42,
|
||
|
StringToBool: true,
|
||
|
StringToFloat: 42.42,
|
||
|
SliceToMap: map[string]interface{}{},
|
||
|
MapToSlice: []interface{}{},
|
||
|
}
|
||
|
|
||
|
// Test strict type conversion
|
||
|
var resultStrict TypeConversionResult
|
||
|
err := Decode(input, &resultStrict)
|
||
|
if err == nil {
|
||
|
t.Errorf("should return an error")
|
||
|
}
|
||
|
if !reflect.DeepEqual(resultStrict, expectedResultStrict) {
|
||
|
t.Errorf("expected %v, got: %v", expectedResultStrict, resultStrict)
|
||
|
}
|
||
|
|
||
|
// Test weak type conversion
|
||
|
var decoder *Decoder
|
||
|
var resultWeak TypeConversionResult
|
||
|
|
||
|
config := &DecoderConfig{
|
||
|
WeaklyTypedInput: true,
|
||
|
Result: &resultWeak,
|
||
|
}
|
||
|
|
||
|
decoder, err = NewDecoder(config)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
err = decoder.Decode(input)
|
||
|
if err != nil {
|
||
|
t.Fatalf("got an err: %s", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(resultWeak, expectedResultWeak) {
|
||
|
t.Errorf("expected \n%#v, got: \n%#v", expectedResultWeak, resultWeak)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestDecoder_ErrorUnused(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"vstring": "hello",
|
||
|
"foo": "bar",
|
||
|
}
|
||
|
|
||
|
var result Basic
|
||
|
config := &DecoderConfig{
|
||
|
ErrorUnused: true,
|
||
|
Result: &result,
|
||
|
}
|
||
|
|
||
|
decoder, err := NewDecoder(config)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
err = decoder.Decode(input)
|
||
|
if err == nil {
|
||
|
t.Fatal("expected error")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestMap(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"vfoo": "foo",
|
||
|
"vother": map[interface{}]interface{}{
|
||
|
"foo": "foo",
|
||
|
"bar": "bar",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
var result Map
|
||
|
err := Decode(input, &result)
|
||
|
if err != nil {
|
||
|
t.Fatalf("got an error: %s", err)
|
||
|
}
|
||
|
|
||
|
if result.Vfoo != "foo" {
|
||
|
t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo)
|
||
|
}
|
||
|
|
||
|
if result.Vother == nil {
|
||
|
t.Fatal("vother should not be nil")
|
||
|
}
|
||
|
|
||
|
if len(result.Vother) != 2 {
|
||
|
t.Error("vother should have two items")
|
||
|
}
|
||
|
|
||
|
if result.Vother["foo"] != "foo" {
|
||
|
t.Errorf("'foo' key should be foo, got: %#v", result.Vother["foo"])
|
||
|
}
|
||
|
|
||
|
if result.Vother["bar"] != "bar" {
|
||
|
t.Errorf("'bar' key should be bar, got: %#v", result.Vother["bar"])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestMapMerge(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"vfoo": "foo",
|
||
|
"vother": map[interface{}]interface{}{
|
||
|
"foo": "foo",
|
||
|
"bar": "bar",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
var result Map
|
||
|
result.Vother = map[string]string{"hello": "world"}
|
||
|
err := Decode(input, &result)
|
||
|
if err != nil {
|
||
|
t.Fatalf("got an error: %s", err)
|
||
|
}
|
||
|
|
||
|
if result.Vfoo != "foo" {
|
||
|
t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo)
|
||
|
}
|
||
|
|
||
|
expected := map[string]string{
|
||
|
"foo": "foo",
|
||
|
"bar": "bar",
|
||
|
"hello": "world",
|
||
|
}
|
||
|
if !reflect.DeepEqual(result.Vother, expected) {
|
||
|
t.Errorf("bad: %#v", result.Vother)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestMapOfStruct(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"value": map[string]interface{}{
|
||
|
"foo": map[string]string{"vstring": "one"},
|
||
|
"bar": map[string]string{"vstring": "two"},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
var result MapOfStruct
|
||
|
err := Decode(input, &result)
|
||
|
if err != nil {
|
||
|
t.Fatalf("got an err: %s", err)
|
||
|
}
|
||
|
|
||
|
if result.Value == nil {
|
||
|
t.Fatal("value should not be nil")
|
||
|
}
|
||
|
|
||
|
if len(result.Value) != 2 {
|
||
|
t.Error("value should have two items")
|
||
|
}
|
||
|
|
||
|
if result.Value["foo"].Vstring != "one" {
|
||
|
t.Errorf("foo value should be 'one', got: %s", result.Value["foo"].Vstring)
|
||
|
}
|
||
|
|
||
|
if result.Value["bar"].Vstring != "two" {
|
||
|
t.Errorf("bar value should be 'two', got: %s", result.Value["bar"].Vstring)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestNestedType(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"vfoo": "foo",
|
||
|
"vbar": map[string]interface{}{
|
||
|
"vstring": "foo",
|
||
|
"vint": 42,
|
||
|
"vbool": true,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
var result Nested
|
||
|
err := Decode(input, &result)
|
||
|
if err != nil {
|
||
|
t.Fatalf("got an err: %s", err.Error())
|
||
|
}
|
||
|
|
||
|
if result.Vfoo != "foo" {
|
||
|
t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo)
|
||
|
}
|
||
|
|
||
|
if result.Vbar.Vstring != "foo" {
|
||
|
t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring)
|
||
|
}
|
||
|
|
||
|
if result.Vbar.Vint != 42 {
|
||
|
t.Errorf("vint value should be 42: %#v", result.Vbar.Vint)
|
||
|
}
|
||
|
|
||
|
if result.Vbar.Vbool != true {
|
||
|
t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool)
|
||
|
}
|
||
|
|
||
|
if result.Vbar.Vextra != "" {
|
||
|
t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestNestedTypePointer(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"vfoo": "foo",
|
||
|
"vbar": &map[string]interface{}{
|
||
|
"vstring": "foo",
|
||
|
"vint": 42,
|
||
|
"vbool": true,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
var result NestedPointer
|
||
|
err := Decode(input, &result)
|
||
|
if err != nil {
|
||
|
t.Fatalf("got an err: %s", err.Error())
|
||
|
}
|
||
|
|
||
|
if result.Vfoo != "foo" {
|
||
|
t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo)
|
||
|
}
|
||
|
|
||
|
if result.Vbar.Vstring != "foo" {
|
||
|
t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring)
|
||
|
}
|
||
|
|
||
|
if result.Vbar.Vint != 42 {
|
||
|
t.Errorf("vint value should be 42: %#v", result.Vbar.Vint)
|
||
|
}
|
||
|
|
||
|
if result.Vbar.Vbool != true {
|
||
|
t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool)
|
||
|
}
|
||
|
|
||
|
if result.Vbar.Vextra != "" {
|
||
|
t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestSlice(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
inputStringSlice := map[string]interface{}{
|
||
|
"vfoo": "foo",
|
||
|
"vbar": []string{"foo", "bar", "baz"},
|
||
|
}
|
||
|
|
||
|
inputStringSlicePointer := map[string]interface{}{
|
||
|
"vfoo": "foo",
|
||
|
"vbar": &[]string{"foo", "bar", "baz"},
|
||
|
}
|
||
|
|
||
|
outputStringSlice := &Slice{
|
||
|
"foo",
|
||
|
[]string{"foo", "bar", "baz"},
|
||
|
}
|
||
|
|
||
|
testSliceInput(t, inputStringSlice, outputStringSlice)
|
||
|
testSliceInput(t, inputStringSlicePointer, outputStringSlice)
|
||
|
}
|
||
|
|
||
|
func TestInvalidSlice(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"vfoo": "foo",
|
||
|
"vbar": 42,
|
||
|
}
|
||
|
|
||
|
result := Slice{}
|
||
|
err := Decode(input, &result)
|
||
|
if err == nil {
|
||
|
t.Errorf("expected failure")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestSliceOfStruct(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"value": []map[string]interface{}{
|
||
|
{"vstring": "one"},
|
||
|
{"vstring": "two"},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
var result SliceOfStruct
|
||
|
err := Decode(input, &result)
|
||
|
if err != nil {
|
||
|
t.Fatalf("got unexpected error: %s", err)
|
||
|
}
|
||
|
|
||
|
if len(result.Value) != 2 {
|
||
|
t.Fatalf("expected two values, got %d", len(result.Value))
|
||
|
}
|
||
|
|
||
|
if result.Value[0].Vstring != "one" {
|
||
|
t.Errorf("first value should be 'one', got: %s", result.Value[0].Vstring)
|
||
|
}
|
||
|
|
||
|
if result.Value[1].Vstring != "two" {
|
||
|
t.Errorf("second value should be 'two', got: %s", result.Value[1].Vstring)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestSliceToMap(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := []map[string]interface{}{
|
||
|
map[string]interface{}{
|
||
|
"foo": "bar",
|
||
|
},
|
||
|
map[string]interface{}{
|
||
|
"bar": "baz",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
var result map[string]interface{}
|
||
|
err := WeakDecode(input, &result)
|
||
|
if err != nil {
|
||
|
t.Fatalf("got an error: %s", err)
|
||
|
}
|
||
|
|
||
|
expected := map[string]interface{}{
|
||
|
"foo": "bar",
|
||
|
"bar": "baz",
|
||
|
}
|
||
|
if !reflect.DeepEqual(result, expected) {
|
||
|
t.Errorf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestInvalidType(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"vstring": 42,
|
||
|
}
|
||
|
|
||
|
var result Basic
|
||
|
err := Decode(input, &result)
|
||
|
if err == nil {
|
||
|
t.Fatal("error should exist")
|
||
|
}
|
||
|
|
||
|
derr, ok := err.(*Error)
|
||
|
if !ok {
|
||
|
t.Fatalf("error should be kind of Error, instead: %#v", err)
|
||
|
}
|
||
|
|
||
|
if derr.Errors[0] != "'Vstring' expected type 'string', got unconvertible type 'int'" {
|
||
|
t.Errorf("got unexpected error: %s", err)
|
||
|
}
|
||
|
|
||
|
inputNegIntUint := map[string]interface{}{
|
||
|
"vuint": -42,
|
||
|
}
|
||
|
|
||
|
err = Decode(inputNegIntUint, &result)
|
||
|
if err == nil {
|
||
|
t.Fatal("error should exist")
|
||
|
}
|
||
|
|
||
|
derr, ok = err.(*Error)
|
||
|
if !ok {
|
||
|
t.Fatalf("error should be kind of Error, instead: %#v", err)
|
||
|
}
|
||
|
|
||
|
if derr.Errors[0] != "cannot parse 'Vuint', -42 overflows uint" {
|
||
|
t.Errorf("got unexpected error: %s", err)
|
||
|
}
|
||
|
|
||
|
inputNegFloatUint := map[string]interface{}{
|
||
|
"vuint": -42.0,
|
||
|
}
|
||
|
|
||
|
err = Decode(inputNegFloatUint, &result)
|
||
|
if err == nil {
|
||
|
t.Fatal("error should exist")
|
||
|
}
|
||
|
|
||
|
derr, ok = err.(*Error)
|
||
|
if !ok {
|
||
|
t.Fatalf("error should be kind of Error, instead: %#v", err)
|
||
|
}
|
||
|
|
||
|
if derr.Errors[0] != "cannot parse 'Vuint', -42.000000 overflows uint" {
|
||
|
t.Errorf("got unexpected error: %s", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestMetadata(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"vfoo": "foo",
|
||
|
"vbar": map[string]interface{}{
|
||
|
"vstring": "foo",
|
||
|
"Vuint": 42,
|
||
|
"foo": "bar",
|
||
|
},
|
||
|
"bar": "nil",
|
||
|
}
|
||
|
|
||
|
var md Metadata
|
||
|
var result Nested
|
||
|
config := &DecoderConfig{
|
||
|
Metadata: &md,
|
||
|
Result: &result,
|
||
|
}
|
||
|
|
||
|
decoder, err := NewDecoder(config)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
err = decoder.Decode(input)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err.Error())
|
||
|
}
|
||
|
|
||
|
expectedKeys := []string{"Vbar", "Vbar.Vstring", "Vbar.Vuint", "Vfoo"}
|
||
|
sort.Strings(md.Keys)
|
||
|
if !reflect.DeepEqual(md.Keys, expectedKeys) {
|
||
|
t.Fatalf("bad keys: %#v", md.Keys)
|
||
|
}
|
||
|
|
||
|
expectedUnused := []string{"Vbar.foo", "bar"}
|
||
|
if !reflect.DeepEqual(md.Unused, expectedUnused) {
|
||
|
t.Fatalf("bad unused: %#v", md.Unused)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestMetadata_Embedded(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"vstring": "foo",
|
||
|
"vunique": "bar",
|
||
|
}
|
||
|
|
||
|
var md Metadata
|
||
|
var result EmbeddedSquash
|
||
|
config := &DecoderConfig{
|
||
|
Metadata: &md,
|
||
|
Result: &result,
|
||
|
}
|
||
|
|
||
|
decoder, err := NewDecoder(config)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
|
||
|
err = decoder.Decode(input)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %s", err.Error())
|
||
|
}
|
||
|
|
||
|
expectedKeys := []string{"Vstring", "Vunique"}
|
||
|
|
||
|
sort.Strings(md.Keys)
|
||
|
if !reflect.DeepEqual(md.Keys, expectedKeys) {
|
||
|
t.Fatalf("bad keys: %#v", md.Keys)
|
||
|
}
|
||
|
|
||
|
expectedUnused := []string{}
|
||
|
if !reflect.DeepEqual(md.Unused, expectedUnused) {
|
||
|
t.Fatalf("bad unused: %#v", md.Unused)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestNonPtrValue(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
err := Decode(map[string]interface{}{}, Basic{})
|
||
|
if err == nil {
|
||
|
t.Fatal("error should exist")
|
||
|
}
|
||
|
|
||
|
if err.Error() != "result must be a pointer" {
|
||
|
t.Errorf("got unexpected error: %s", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestTagged(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"foo": "bar",
|
||
|
"bar": "value",
|
||
|
}
|
||
|
|
||
|
var result Tagged
|
||
|
err := Decode(input, &result)
|
||
|
if err != nil {
|
||
|
t.Fatalf("unexpected error: %s", err)
|
||
|
}
|
||
|
|
||
|
if result.Value != "bar" {
|
||
|
t.Errorf("value should be 'bar', got: %#v", result.Value)
|
||
|
}
|
||
|
|
||
|
if result.Extra != "value" {
|
||
|
t.Errorf("extra should be 'value', got: %#v", result.Extra)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestWeakDecode(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
input := map[string]interface{}{
|
||
|
"foo": "4",
|
||
|
"bar": "value",
|
||
|
}
|
||
|
|
||
|
var result struct {
|
||
|
Foo int
|
||
|
Bar string
|
||
|
}
|
||
|
|
||
|
if err := WeakDecode(input, &result); err != nil {
|
||
|
t.Fatalf("err: %s", err)
|
||
|
}
|
||
|
if result.Foo != 4 {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
if result.Bar != "value" {
|
||
|
t.Fatalf("bad: %#v", result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func testSliceInput(t *testing.T, input map[string]interface{}, expected *Slice) {
|
||
|
var result Slice
|
||
|
err := Decode(input, &result)
|
||
|
if err != nil {
|
||
|
t.Fatalf("got error: %s", err)
|
||
|
}
|
||
|
|
||
|
if result.Vfoo != expected.Vfoo {
|
||
|
t.Errorf("Vfoo expected '%s', got '%s'", expected.Vfoo, result.Vfoo)
|
||
|
}
|
||
|
|
||
|
if result.Vbar == nil {
|
||
|
t.Fatalf("Vbar a slice, got '%#v'", result.Vbar)
|
||
|
}
|
||
|
|
||
|
if len(result.Vbar) != len(expected.Vbar) {
|
||
|
t.Errorf("Vbar length should be %d, got %d", len(expected.Vbar), len(result.Vbar))
|
||
|
}
|
||
|
|
||
|
for i, v := range result.Vbar {
|
||
|
if v != expected.Vbar[i] {
|
||
|
t.Errorf(
|
||
|
"Vbar[%d] should be '%#v', got '%#v'",
|
||
|
i, expected.Vbar[i], v)
|
||
|
}
|
||
|
}
|
||
|
}
|