github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/mapstruct/bugs_test.go (about)

     1  package mapstruct
     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 := &Config{
   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  type NestedSlice struct {
   194  	Vfoo   string
   195  	Vbars  []Basic
   196  	Vempty []Basic
   197  }
   198  
   199  // #48
   200  func TestNestedTypeSliceWithDefaults(t *testing.T) {
   201  	t.Parallel()
   202  
   203  	input := map[string]interface{}{
   204  		"vfoo": "foo",
   205  		"vbars": []map[string]interface{}{
   206  			{"vstring": "foo", "vint": 42, "vbool": true},
   207  			{"vint": 42, "vbool": true},
   208  		},
   209  		"vempty": []map[string]interface{}{
   210  			{"vstring": "foo", "vint": 42, "vbool": true},
   211  			{"vint": 42, "vbool": true},
   212  		},
   213  	}
   214  
   215  	result := NestedSlice{
   216  		Vbars: []Basic{
   217  			{Vuint: 42},
   218  			{Vstring: "foo"},
   219  		},
   220  	}
   221  	err := Decode(input, &result)
   222  	if err != nil {
   223  		t.Fatalf("got an err: %s", err.Error())
   224  	}
   225  
   226  	if result.Vfoo != "foo" {
   227  		t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo)
   228  	}
   229  
   230  	if result.Vbars[0].Vstring != "foo" {
   231  		t.Errorf("vstring value should be 'foo': %#v", result.Vbars[0].Vstring)
   232  	}
   233  	// this is the error
   234  	if result.Vbars[0].Vuint != 42 {
   235  		t.Errorf("vuint value should be 42: %#v", result.Vbars[0].Vuint)
   236  	}
   237  }
   238  
   239  // #48 workaround
   240  func TestNestedTypeWithDefaults(t *testing.T) {
   241  	t.Parallel()
   242  
   243  	input := map[string]interface{}{
   244  		"vfoo": "foo",
   245  		"vbar": map[string]interface{}{
   246  			"vstring": "foo",
   247  			"vint":    42,
   248  			"vbool":   true,
   249  		},
   250  	}
   251  
   252  	result := Nested{
   253  		Vbar: Basic{
   254  			Vuint: 42,
   255  		},
   256  	}
   257  	err := Decode(input, &result)
   258  	if err != nil {
   259  		t.Fatalf("got an err: %s", err.Error())
   260  	}
   261  
   262  	if result.Vfoo != "foo" {
   263  		t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo)
   264  	}
   265  
   266  	if result.Vbar.Vstring != "foo" {
   267  		t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring)
   268  	}
   269  
   270  	if result.Vbar.Vint != 42 {
   271  		t.Errorf("vint value should be 42: %#v", result.Vbar.Vint)
   272  	}
   273  
   274  	if result.Vbar.Vbool != true {
   275  		t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool)
   276  	}
   277  
   278  	if result.Vbar.Vextra != "" {
   279  		t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra)
   280  	}
   281  
   282  	// this is the error
   283  	if result.Vbar.Vuint != 42 {
   284  		t.Errorf("vuint value should be 42: %#v", result.Vbar.Vuint)
   285  	}
   286  }
   287  
   288  // #67 panic() on extending slices (decodeSlice with disabled ZeroValues)
   289  func TestDecodeSliceToEmptySliceWOZeroing(t *testing.T) {
   290  	t.Parallel()
   291  
   292  	type TestStruct struct {
   293  		Vfoo []string
   294  	}
   295  
   296  	decode := func(m interface{}, rawVal interface{}) error {
   297  		config := &Config{
   298  			Metadata:   nil,
   299  			Result:     rawVal,
   300  			ZeroFields: false,
   301  		}
   302  
   303  		decoder, err := NewDecoder(config)
   304  		if err != nil {
   305  			return err
   306  		}
   307  
   308  		return decoder.Decode(m)
   309  	}
   310  
   311  	{
   312  		input := map[string]interface{}{
   313  			"vfoo": []string{"1"},
   314  		}
   315  
   316  		result := &TestStruct{}
   317  
   318  		err := decode(input, &result)
   319  		if err != nil {
   320  			t.Fatalf("got an err: %s", err.Error())
   321  		}
   322  	}
   323  
   324  	{
   325  		input := map[string]interface{}{
   326  			"vfoo": []string{"1"},
   327  		}
   328  
   329  		result := &TestStruct{
   330  			Vfoo: []string{},
   331  		}
   332  
   333  		err := decode(input, &result)
   334  		if err != nil {
   335  			t.Fatalf("got an err: %s", err.Error())
   336  		}
   337  	}
   338  
   339  	{
   340  		input := map[string]interface{}{
   341  			"vfoo": []string{"2", "3"},
   342  		}
   343  
   344  		result := &TestStruct{
   345  			Vfoo: []string{"1"},
   346  		}
   347  
   348  		err := decode(input, &result)
   349  		if err != nil {
   350  			t.Fatalf("got an err: %s", err.Error())
   351  		}
   352  	}
   353  }
   354  
   355  // #70
   356  func TestNextSquashmapstruct(t *testing.T) {
   357  	data := &struct {
   358  		Level1 struct {
   359  			Level2 struct {
   360  				Foo string
   361  			} `mapstruct:",squash"`
   362  		} `mapstruct:",squash"`
   363  	}{}
   364  	err := Decode(map[interface{}]interface{}{"foo": "baz"}, &data)
   365  	if err != nil {
   366  		t.Fatalf("should not error: %s", err)
   367  	}
   368  	if data.Level1.Level2.Foo != "baz" {
   369  		t.Fatal("value should be baz")
   370  	}
   371  }
   372  
   373  type ImplementsInterfacePointerReceiver struct {
   374  	Name string
   375  }
   376  
   377  func (i *ImplementsInterfacePointerReceiver) DoStuff() {}
   378  
   379  type ImplementsInterfaceValueReceiver string
   380  
   381  func (i ImplementsInterfaceValueReceiver) DoStuff() {}
   382  
   383  // GH-140 Type error when using Hook to decode into interface
   384  func TestDecode_DecodeHookInterface(t *testing.T) {
   385  	t.Parallel()
   386  
   387  	type Interface interface {
   388  		DoStuff()
   389  	}
   390  	type DecodeIntoInterface struct {
   391  		Test Interface
   392  	}
   393  
   394  	testData := map[string]string{"test": "test"}
   395  
   396  	stringToPointerInterfaceDecodeHook := func(from, to reflect.Type, data interface{}) (interface{}, error) {
   397  		if from.Kind() != reflect.String {
   398  			return data, nil
   399  		}
   400  
   401  		if to != reflect.TypeOf((*Interface)(nil)).Elem() {
   402  			return data, nil
   403  		}
   404  		// Ensure interface is satisfied
   405  		var impl Interface = &ImplementsInterfacePointerReceiver{data.(string)}
   406  		return impl, nil
   407  	}
   408  
   409  	stringToValueInterfaceDecodeHook := func(from, to reflect.Type, data interface{}) (interface{}, error) {
   410  		if from.Kind() != reflect.String {
   411  			return data, nil
   412  		}
   413  
   414  		if to != reflect.TypeOf((*Interface)(nil)).Elem() {
   415  			return data, nil
   416  		}
   417  		// Ensure interface is satisfied
   418  		var impl Interface = ImplementsInterfaceValueReceiver(data.(string))
   419  		return impl, nil
   420  	}
   421  
   422  	{
   423  		decodeInto := new(DecodeIntoInterface)
   424  
   425  		decoder, _ := NewDecoder(&Config{
   426  			Hook:   stringToPointerInterfaceDecodeHook,
   427  			Result: decodeInto,
   428  		})
   429  
   430  		err := decoder.Decode(testData)
   431  		if err != nil {
   432  			t.Fatalf("Decode returned error: %s", err)
   433  		}
   434  
   435  		expected := &ImplementsInterfacePointerReceiver{"test"}
   436  		if !reflect.DeepEqual(decodeInto.Test, expected) {
   437  			t.Fatalf("expected: %#v (%T), got: %#v (%T)", decodeInto.Test, decodeInto.Test, expected, expected)
   438  		}
   439  	}
   440  
   441  	{
   442  		decodeInto := new(DecodeIntoInterface)
   443  
   444  		decoder, _ := NewDecoder(&Config{
   445  			Hook:   stringToValueInterfaceDecodeHook,
   446  			Result: decodeInto,
   447  		})
   448  
   449  		err := decoder.Decode(testData)
   450  		if err != nil {
   451  			t.Fatalf("Decode returned error: %s", err)
   452  		}
   453  
   454  		expected := ImplementsInterfaceValueReceiver("test")
   455  		if !reflect.DeepEqual(decodeInto.Test, expected) {
   456  			t.Fatalf("expected: %#v (%T), got: %#v (%T)", decodeInto.Test, decodeInto.Test, expected, expected)
   457  		}
   458  	}
   459  }
   460  
   461  // #103 Check for data type before trying to access its composants prevent a panic error
   462  // in decodeSlice
   463  func TestDecodeBadDataTypeInSlice(t *testing.T) {
   464  	t.Parallel()
   465  
   466  	input := map[string]interface{}{
   467  		"Toto": "titi",
   468  	}
   469  	result := []struct {
   470  		Toto string
   471  	}{}
   472  
   473  	if err := Decode(input, &result); err == nil {
   474  		t.Error("An error was expected, got nil")
   475  	}
   476  }
   477  
   478  // #202 Ensure that intermediate maps in the struct -> struct decode process are settable
   479  // and not just the elements within them.
   480  func TestDecodeIntermeidateMapsSettable(t *testing.T) {
   481  	type Timestamp struct {
   482  		Seconds int64
   483  		Nanos   int32
   484  	}
   485  
   486  	type TsWrapper struct {
   487  		Timestamp *Timestamp
   488  	}
   489  
   490  	type TimeWrapper struct {
   491  		Timestamp time.Time
   492  	}
   493  
   494  	input := TimeWrapper{
   495  		Timestamp: time.Unix(123456789, 987654),
   496  	}
   497  
   498  	expected := TsWrapper{
   499  		Timestamp: &Timestamp{
   500  			Seconds: 123456789,
   501  			Nanos:   987654,
   502  		},
   503  	}
   504  
   505  	timePtrType := reflect.TypeOf((*time.Time)(nil))
   506  	mapStrInfType := reflect.TypeOf((map[string]interface{})(nil))
   507  
   508  	var actual TsWrapper
   509  	decoder, err := NewDecoder(&Config{
   510  		Result: &actual,
   511  		Hook: func(from, to reflect.Type, data interface{}) (interface{}, error) {
   512  			if from == timePtrType && to == mapStrInfType {
   513  				ts := data.(*time.Time)
   514  				nanos := ts.UnixNano()
   515  
   516  				seconds := nanos / 1000000000
   517  				nanos = nanos % 1000000000
   518  
   519  				return &map[string]interface{}{
   520  					"Seconds": seconds,
   521  					"Nanos":   int32(nanos),
   522  				}, nil
   523  			}
   524  			return data, nil
   525  		},
   526  	})
   527  	if err != nil {
   528  		t.Fatalf("failed to create decoder: %v", err)
   529  	}
   530  
   531  	if err := decoder.Decode(&input); err != nil {
   532  		t.Fatalf("failed to decode input: %v", err)
   533  	}
   534  
   535  	if !reflect.DeepEqual(expected, actual) {
   536  		t.Fatalf("expected: %#[1]v (%[1]T), got: %#[2]v (%[2]T)", expected, actual)
   537  	}
   538  }
   539  
   540  // GH-206: decodeInt throws an error for an empty string
   541  func TestDecode_weakEmptyStringToInt(t *testing.T) {
   542  	input := map[string]interface{}{
   543  		"StringToInt":   "",
   544  		"StringToUint":  "",
   545  		"StringToBool":  "",
   546  		"StringToFloat": "",
   547  	}
   548  
   549  	expectedResultWeak := TypeConversionResult{
   550  		StringToInt:   0,
   551  		StringToUint:  0,
   552  		StringToBool:  false,
   553  		StringToFloat: 0,
   554  	}
   555  
   556  	// Test weak type conversion
   557  	var resultWeak TypeConversionResult
   558  	err := Decode(input, &resultWeak, WithWeakType(true))
   559  	if err != nil {
   560  		t.Fatalf("got an err: %s", err)
   561  	}
   562  
   563  	if !reflect.DeepEqual(resultWeak, expectedResultWeak) {
   564  		t.Errorf("expected \n%#v, got: \n%#v", expectedResultWeak, resultWeak)
   565  	}
   566  }
   567  
   568  // GH-228: Squash cause *time.Time set to zero
   569  func TestMapSquash(t *testing.T) {
   570  	type AA struct {
   571  		T *time.Time
   572  	}
   573  	type A struct {
   574  		AA
   575  	}
   576  
   577  	v := time.Now()
   578  	in := &AA{
   579  		T: &v,
   580  	}
   581  	out := &A{}
   582  	d, err := NewDecoder(&Config{
   583  		Squash: true,
   584  		Result: out,
   585  	})
   586  	if err != nil {
   587  		t.Fatalf("err: %s", err)
   588  	}
   589  	if err := d.Decode(in); err != nil {
   590  		t.Fatalf("err: %s", err)
   591  	}
   592  
   593  	// these failed
   594  	if !v.Equal(*out.T) {
   595  		t.Fatal("expected equal")
   596  	}
   597  	if out.T.IsZero() {
   598  		t.Fatal("expected false")
   599  	}
   600  }