blob: b3e13635835f0b1dca267e7a58ce6ef77bcd9bc3 [file] [log] [blame]
package mapstructure
import (
"reflect"
"testing"
)
type Basic struct {
Vstring string
Vint int
Vuint uint
Vbool bool
Vextra string
vsilent bool
Vdata interface{}
}
type Map struct {
Vfoo string
Vother map[string]string
}
type MapOfStruct struct {
Value map[string]Basic
}
type Nested struct {
Vfoo string
Vbar Basic
}
type Slice struct {
Vfoo string
Vbar []string
}
type SliceOfStruct struct {
Value []Basic
}
type Tagged struct {
Value string `mapstructure:"foo"`
}
func TestBasicTypes(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"vstring": "foo",
"vint": 42,
"Vuint": 42,
"vbool": true,
"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.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 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 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 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 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 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 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 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 'int'" {
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{"Vfoo", "Vbar.Vstring", "Vbar.Vuint", "Vbar"}
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 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",
}
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)
}
}
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)
}
}
}