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) } } }