...

Source file src/github.com/mitchellh/mapstructure/mapstructure_bugs_test.go

Documentation: github.com/mitchellh/mapstructure

     1  package mapstructure
     2  
     3  import (
     4  	"reflect"
     5  	"testing"
     6  	"time"
     7  )
     8  
     9  // GH-1, GH-10, GH-96
    10  func TestDecode_NilValue(t *testing.T) {
    11  	t.Parallel()
    12  
    13  	tests := []struct {
    14  		name       string
    15  		in         interface{}
    16  		target     interface{}
    17  		out        interface{}
    18  		metaKeys   []string
    19  		metaUnused []string
    20  	}{
    21  		{
    22  			"all nil",
    23  			&map[string]interface{}{
    24  				"vfoo":   nil,
    25  				"vother": nil,
    26  			},
    27  			&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
    28  			&Map{Vfoo: "", Vother: nil},
    29  			[]string{"Vfoo", "Vother"},
    30  			[]string{},
    31  		},
    32  		{
    33  			"partial nil",
    34  			&map[string]interface{}{
    35  				"vfoo":   "baz",
    36  				"vother": nil,
    37  			},
    38  			&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
    39  			&Map{Vfoo: "baz", Vother: nil},
    40  			[]string{"Vfoo", "Vother"},
    41  			[]string{},
    42  		},
    43  		{
    44  			"partial decode",
    45  			&map[string]interface{}{
    46  				"vother": nil,
    47  			},
    48  			&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
    49  			&Map{Vfoo: "foo", Vother: nil},
    50  			[]string{"Vother"},
    51  			[]string{},
    52  		},
    53  		{
    54  			"unused values",
    55  			&map[string]interface{}{
    56  				"vbar":   "bar",
    57  				"vfoo":   nil,
    58  				"vother": nil,
    59  			},
    60  			&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
    61  			&Map{Vfoo: "", Vother: nil},
    62  			[]string{"Vfoo", "Vother"},
    63  			[]string{"vbar"},
    64  		},
    65  		{
    66  			"map interface all nil",
    67  			&map[interface{}]interface{}{
    68  				"vfoo":   nil,
    69  				"vother": nil,
    70  			},
    71  			&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
    72  			&Map{Vfoo: "", Vother: nil},
    73  			[]string{"Vfoo", "Vother"},
    74  			[]string{},
    75  		},
    76  		{
    77  			"map interface partial nil",
    78  			&map[interface{}]interface{}{
    79  				"vfoo":   "baz",
    80  				"vother": nil,
    81  			},
    82  			&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
    83  			&Map{Vfoo: "baz", Vother: nil},
    84  			[]string{"Vfoo", "Vother"},
    85  			[]string{},
    86  		},
    87  		{
    88  			"map interface partial decode",
    89  			&map[interface{}]interface{}{
    90  				"vother": nil,
    91  			},
    92  			&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
    93  			&Map{Vfoo: "foo", Vother: nil},
    94  			[]string{"Vother"},
    95  			[]string{},
    96  		},
    97  		{
    98  			"map interface unused values",
    99  			&map[interface{}]interface{}{
   100  				"vbar":   "bar",
   101  				"vfoo":   nil,
   102  				"vother": nil,
   103  			},
   104  			&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
   105  			&Map{Vfoo: "", Vother: nil},
   106  			[]string{"Vfoo", "Vother"},
   107  			[]string{"vbar"},
   108  		},
   109  	}
   110  
   111  	for _, tc := range tests {
   112  		t.Run(tc.name, func(t *testing.T) {
   113  			config := &DecoderConfig{
   114  				Metadata:   new(Metadata),
   115  				Result:     tc.target,
   116  				ZeroFields: true,
   117  			}
   118  
   119  			decoder, err := NewDecoder(config)
   120  			if err != nil {
   121  				t.Fatalf("should not error: %s", err)
   122  			}
   123  
   124  			err = decoder.Decode(tc.in)
   125  			if err != nil {
   126  				t.Fatalf("should not error: %s", err)
   127  			}
   128  
   129  			if !reflect.DeepEqual(tc.out, tc.target) {
   130  				t.Fatalf("%q: TestDecode_NilValue() expected: %#v, got: %#v", tc.name, tc.out, tc.target)
   131  			}
   132  
   133  			if !reflect.DeepEqual(tc.metaKeys, config.Metadata.Keys) {
   134  				t.Fatalf("%q: Metadata.Keys mismatch expected: %#v, got: %#v", tc.name, tc.metaKeys, config.Metadata.Keys)
   135  			}
   136  
   137  			if !reflect.DeepEqual(tc.metaUnused, config.Metadata.Unused) {
   138  				t.Fatalf("%q: Metadata.Unused mismatch expected: %#v, got: %#v", tc.name, tc.metaUnused, config.Metadata.Unused)
   139  			}
   140  		})
   141  	}
   142  }
   143  
   144  // #48
   145  func TestNestedTypePointerWithDefaults(t *testing.T) {
   146  	t.Parallel()
   147  
   148  	input := map[string]interface{}{
   149  		"vfoo": "foo",
   150  		"vbar": map[string]interface{}{
   151  			"vstring": "foo",
   152  			"vint":    42,
   153  			"vbool":   true,
   154  		},
   155  	}
   156  
   157  	result := NestedPointer{
   158  		Vbar: &Basic{
   159  			Vuint: 42,
   160  		},
   161  	}
   162  	err := Decode(input, &result)
   163  	if err != nil {
   164  		t.Fatalf("got an err: %s", err.Error())
   165  	}
   166  
   167  	if result.Vfoo != "foo" {
   168  		t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo)
   169  	}
   170  
   171  	if result.Vbar.Vstring != "foo" {
   172  		t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring)
   173  	}
   174  
   175  	if result.Vbar.Vint != 42 {
   176  		t.Errorf("vint value should be 42: %#v", result.Vbar.Vint)
   177  	}
   178  
   179  	if result.Vbar.Vbool != true {
   180  		t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool)
   181  	}
   182  
   183  	if result.Vbar.Vextra != "" {
   184  		t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra)
   185  	}
   186  
   187  	// this is the error
   188  	if result.Vbar.Vuint != 42 {
   189  		t.Errorf("vuint value should be 42: %#v", result.Vbar.Vuint)
   190  	}
   191  
   192  }
   193  
   194  type NestedSlice struct {
   195  	Vfoo   string
   196  	Vbars  []Basic
   197  	Vempty []Basic
   198  }
   199  
   200  // #48
   201  func TestNestedTypeSliceWithDefaults(t *testing.T) {
   202  	t.Parallel()
   203  
   204  	input := map[string]interface{}{
   205  		"vfoo": "foo",
   206  		"vbars": []map[string]interface{}{
   207  			{"vstring": "foo", "vint": 42, "vbool": true},
   208  			{"vint": 42, "vbool": true},
   209  		},
   210  		"vempty": []map[string]interface{}{
   211  			{"vstring": "foo", "vint": 42, "vbool": true},
   212  			{"vint": 42, "vbool": true},
   213  		},
   214  	}
   215  
   216  	result := NestedSlice{
   217  		Vbars: []Basic{
   218  			{Vuint: 42},
   219  			{Vstring: "foo"},
   220  		},
   221  	}
   222  	err := Decode(input, &result)
   223  	if err != nil {
   224  		t.Fatalf("got an err: %s", err.Error())
   225  	}
   226  
   227  	if result.Vfoo != "foo" {
   228  		t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo)
   229  	}
   230  
   231  	if result.Vbars[0].Vstring != "foo" {
   232  		t.Errorf("vstring value should be 'foo': %#v", result.Vbars[0].Vstring)
   233  	}
   234  	// this is the error
   235  	if result.Vbars[0].Vuint != 42 {
   236  		t.Errorf("vuint value should be 42: %#v", result.Vbars[0].Vuint)
   237  	}
   238  }
   239  
   240  // #48 workaround
   241  func TestNestedTypeWithDefaults(t *testing.T) {
   242  	t.Parallel()
   243  
   244  	input := map[string]interface{}{
   245  		"vfoo": "foo",
   246  		"vbar": map[string]interface{}{
   247  			"vstring": "foo",
   248  			"vint":    42,
   249  			"vbool":   true,
   250  		},
   251  	}
   252  
   253  	result := Nested{
   254  		Vbar: Basic{
   255  			Vuint: 42,
   256  		},
   257  	}
   258  	err := Decode(input, &result)
   259  	if err != nil {
   260  		t.Fatalf("got an err: %s", err.Error())
   261  	}
   262  
   263  	if result.Vfoo != "foo" {
   264  		t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo)
   265  	}
   266  
   267  	if result.Vbar.Vstring != "foo" {
   268  		t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring)
   269  	}
   270  
   271  	if result.Vbar.Vint != 42 {
   272  		t.Errorf("vint value should be 42: %#v", result.Vbar.Vint)
   273  	}
   274  
   275  	if result.Vbar.Vbool != true {
   276  		t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool)
   277  	}
   278  
   279  	if result.Vbar.Vextra != "" {
   280  		t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra)
   281  	}
   282  
   283  	// this is the error
   284  	if result.Vbar.Vuint != 42 {
   285  		t.Errorf("vuint value should be 42: %#v", result.Vbar.Vuint)
   286  	}
   287  
   288  }
   289  
   290  // #67 panic() on extending slices (decodeSlice with disabled ZeroValues)
   291  func TestDecodeSliceToEmptySliceWOZeroing(t *testing.T) {
   292  	t.Parallel()
   293  
   294  	type TestStruct struct {
   295  		Vfoo []string
   296  	}
   297  
   298  	decode := func(m interface{}, rawVal interface{}) error {
   299  		config := &DecoderConfig{
   300  			Metadata:   nil,
   301  			Result:     rawVal,
   302  			ZeroFields: false,
   303  		}
   304  
   305  		decoder, err := NewDecoder(config)
   306  		if err != nil {
   307  			return err
   308  		}
   309  
   310  		return decoder.Decode(m)
   311  	}
   312  
   313  	{
   314  		input := map[string]interface{}{
   315  			"vfoo": []string{"1"},
   316  		}
   317  
   318  		result := &TestStruct{}
   319  
   320  		err := decode(input, &result)
   321  		if err != nil {
   322  			t.Fatalf("got an err: %s", err.Error())
   323  		}
   324  	}
   325  
   326  	{
   327  		input := map[string]interface{}{
   328  			"vfoo": []string{"1"},
   329  		}
   330  
   331  		result := &TestStruct{
   332  			Vfoo: []string{},
   333  		}
   334  
   335  		err := decode(input, &result)
   336  		if err != nil {
   337  			t.Fatalf("got an err: %s", err.Error())
   338  		}
   339  	}
   340  
   341  	{
   342  		input := map[string]interface{}{
   343  			"vfoo": []string{"2", "3"},
   344  		}
   345  
   346  		result := &TestStruct{
   347  			Vfoo: []string{"1"},
   348  		}
   349  
   350  		err := decode(input, &result)
   351  		if err != nil {
   352  			t.Fatalf("got an err: %s", err.Error())
   353  		}
   354  	}
   355  }
   356  
   357  // #70
   358  func TestNextSquashMapstructure(t *testing.T) {
   359  	data := &struct {
   360  		Level1 struct {
   361  			Level2 struct {
   362  				Foo string
   363  			} `mapstructure:",squash"`
   364  		} `mapstructure:",squash"`
   365  	}{}
   366  	err := Decode(map[interface{}]interface{}{"foo": "baz"}, &data)
   367  	if err != nil {
   368  		t.Fatalf("should not error: %s", err)
   369  	}
   370  	if data.Level1.Level2.Foo != "baz" {
   371  		t.Fatal("value should be baz")
   372  	}
   373  }
   374  
   375  type ImplementsInterfacePointerReceiver struct {
   376  	Name string
   377  }
   378  
   379  func (i *ImplementsInterfacePointerReceiver) DoStuff() {}
   380  
   381  type ImplementsInterfaceValueReceiver string
   382  
   383  func (i ImplementsInterfaceValueReceiver) DoStuff() {}
   384  
   385  // GH-140 Type error when using DecodeHook to decode into interface
   386  func TestDecode_DecodeHookInterface(t *testing.T) {
   387  	t.Parallel()
   388  
   389  	type Interface interface {
   390  		DoStuff()
   391  	}
   392  	type DecodeIntoInterface struct {
   393  		Test Interface
   394  	}
   395  
   396  	testData := map[string]string{"test": "test"}
   397  
   398  	stringToPointerInterfaceDecodeHook := func(from, to reflect.Type, data interface{}) (interface{}, error) {
   399  		if from.Kind() != reflect.String {
   400  			return data, nil
   401  		}
   402  
   403  		if to != reflect.TypeOf((*Interface)(nil)).Elem() {
   404  			return data, nil
   405  		}
   406  		// Ensure interface is satisfied
   407  		var impl Interface = &ImplementsInterfacePointerReceiver{data.(string)}
   408  		return impl, nil
   409  	}
   410  
   411  	stringToValueInterfaceDecodeHook := func(from, to reflect.Type, data interface{}) (interface{}, error) {
   412  		if from.Kind() != reflect.String {
   413  			return data, nil
   414  		}
   415  
   416  		if to != reflect.TypeOf((*Interface)(nil)).Elem() {
   417  			return data, nil
   418  		}
   419  		// Ensure interface is satisfied
   420  		var impl Interface = ImplementsInterfaceValueReceiver(data.(string))
   421  		return impl, nil
   422  	}
   423  
   424  	{
   425  		decodeInto := new(DecodeIntoInterface)
   426  
   427  		decoder, _ := NewDecoder(&DecoderConfig{
   428  			DecodeHook: stringToPointerInterfaceDecodeHook,
   429  			Result:     decodeInto,
   430  		})
   431  
   432  		err := decoder.Decode(testData)
   433  		if err != nil {
   434  			t.Fatalf("Decode returned error: %s", err)
   435  		}
   436  
   437  		expected := &ImplementsInterfacePointerReceiver{"test"}
   438  		if !reflect.DeepEqual(decodeInto.Test, expected) {
   439  			t.Fatalf("expected: %#v (%T), got: %#v (%T)", decodeInto.Test, decodeInto.Test, expected, expected)
   440  		}
   441  	}
   442  
   443  	{
   444  		decodeInto := new(DecodeIntoInterface)
   445  
   446  		decoder, _ := NewDecoder(&DecoderConfig{
   447  			DecodeHook: stringToValueInterfaceDecodeHook,
   448  			Result:     decodeInto,
   449  		})
   450  
   451  		err := decoder.Decode(testData)
   452  		if err != nil {
   453  			t.Fatalf("Decode returned error: %s", err)
   454  		}
   455  
   456  		expected := ImplementsInterfaceValueReceiver("test")
   457  		if !reflect.DeepEqual(decodeInto.Test, expected) {
   458  			t.Fatalf("expected: %#v (%T), got: %#v (%T)", decodeInto.Test, decodeInto.Test, expected, expected)
   459  		}
   460  	}
   461  }
   462  
   463  // #103 Check for data type before trying to access its composants prevent a panic error
   464  // in decodeSlice
   465  func TestDecodeBadDataTypeInSlice(t *testing.T) {
   466  	t.Parallel()
   467  
   468  	input := map[string]interface{}{
   469  		"Toto": "titi",
   470  	}
   471  	result := []struct {
   472  		Toto string
   473  	}{}
   474  
   475  	if err := Decode(input, &result); err == nil {
   476  		t.Error("An error was expected, got nil")
   477  	}
   478  }
   479  
   480  // #202 Ensure that intermediate maps in the struct -> struct decode process are settable
   481  // and not just the elements within them.
   482  func TestDecodeIntermediateMapsSettable(t *testing.T) {
   483  	type Timestamp struct {
   484  		Seconds int64
   485  		Nanos   int32
   486  	}
   487  
   488  	type TsWrapper struct {
   489  		Timestamp *Timestamp
   490  	}
   491  
   492  	type TimeWrapper struct {
   493  		Timestamp time.Time
   494  	}
   495  
   496  	input := TimeWrapper{
   497  		Timestamp: time.Unix(123456789, 987654),
   498  	}
   499  
   500  	expected := TsWrapper{
   501  		Timestamp: &Timestamp{
   502  			Seconds: 123456789,
   503  			Nanos:   987654,
   504  		},
   505  	}
   506  
   507  	timePtrType := reflect.TypeOf((*time.Time)(nil))
   508  	mapStrInfType := reflect.TypeOf((map[string]interface{})(nil))
   509  
   510  	var actual TsWrapper
   511  	decoder, err := NewDecoder(&DecoderConfig{
   512  		Result: &actual,
   513  		DecodeHook: func(from, to reflect.Type, data interface{}) (interface{}, error) {
   514  			if from == timePtrType && to == mapStrInfType {
   515  				ts := data.(*time.Time)
   516  				nanos := ts.UnixNano()
   517  
   518  				seconds := nanos / 1000000000
   519  				nanos = nanos % 1000000000
   520  
   521  				return &map[string]interface{}{
   522  					"Seconds": seconds,
   523  					"Nanos":   int32(nanos),
   524  				}, nil
   525  			}
   526  			return data, nil
   527  		},
   528  	})
   529  
   530  	if err != nil {
   531  		t.Fatalf("failed to create decoder: %v", err)
   532  	}
   533  
   534  	if err := decoder.Decode(&input); err != nil {
   535  		t.Fatalf("failed to decode input: %v", err)
   536  	}
   537  
   538  	if !reflect.DeepEqual(expected, actual) {
   539  		t.Fatalf("expected: %#[1]v (%[1]T), got: %#[2]v (%[2]T)", expected, actual)
   540  	}
   541  }
   542  
   543  // GH-206: decodeInt throws an error for an empty string
   544  func TestDecode_weakEmptyStringToInt(t *testing.T) {
   545  	input := map[string]interface{}{
   546  		"StringToInt":   "",
   547  		"StringToUint":  "",
   548  		"StringToBool":  "",
   549  		"StringToFloat": "",
   550  	}
   551  
   552  	expectedResultWeak := TypeConversionResult{
   553  		StringToInt:   0,
   554  		StringToUint:  0,
   555  		StringToBool:  false,
   556  		StringToFloat: 0,
   557  	}
   558  
   559  	// Test weak type conversion
   560  	var resultWeak TypeConversionResult
   561  	err := WeakDecode(input, &resultWeak)
   562  	if err != nil {
   563  		t.Fatalf("got an err: %s", err)
   564  	}
   565  
   566  	if !reflect.DeepEqual(resultWeak, expectedResultWeak) {
   567  		t.Errorf("expected \n%#v, got: \n%#v", expectedResultWeak, resultWeak)
   568  	}
   569  }
   570  
   571  // GH-228: Squash cause *time.Time set to zero
   572  func TestMapSquash(t *testing.T) {
   573  	type AA struct {
   574  		T *time.Time
   575  	}
   576  	type A struct {
   577  		AA
   578  	}
   579  
   580  	v := time.Now()
   581  	in := &AA{
   582  		T: &v,
   583  	}
   584  	out := &A{}
   585  	d, err := NewDecoder(&DecoderConfig{
   586  		Squash: true,
   587  		Result: out,
   588  	})
   589  	if err != nil {
   590  		t.Fatalf("err: %s", err)
   591  	}
   592  	if err := d.Decode(in); err != nil {
   593  		t.Fatalf("err: %s", err)
   594  	}
   595  
   596  	// these failed
   597  	if !v.Equal(*out.T) {
   598  		t.Fatal("expected equal")
   599  	}
   600  	if out.T.IsZero() {
   601  		t.Fatal("expected false")
   602  	}
   603  }
   604  
   605  // GH-238: Empty key name when decoding map from struct with only omitempty flag
   606  func TestMapOmitEmptyWithEmptyFieldnameInTag(t *testing.T) {
   607  	type Struct struct {
   608  		Username string `mapstructure:",omitempty"`
   609  		Age      int    `mapstructure:",omitempty"`
   610  	}
   611  
   612  	s := Struct{
   613  		Username: "Joe",
   614  	}
   615  	var m map[string]interface{}
   616  
   617  	if err := Decode(s, &m); err != nil {
   618  		t.Fatal(err)
   619  	}
   620  
   621  	if len(m) != 1 {
   622  		t.Fatalf("fail: %#v", m)
   623  	}
   624  	if m["Username"] != "Joe" {
   625  		t.Fatalf("fail: %#v", m)
   626  	}
   627  }
   628  

View as plain text