github.com/sacloud/iaas-api-go@v1.12.0/mapconv/mapconv_test.go (about)

     1  // Copyright 2022-2023 The sacloud/iaas-api-go Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package mapconv
    16  
    17  import (
    18  	"errors"
    19  	"strconv"
    20  	"strings"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/sacloud/iaas-api-go/types"
    25  	"github.com/stretchr/testify/require"
    26  )
    27  
    28  type dummyFrom struct {
    29  	A          string `mapconv:"ValueA.A"`
    30  	B          string `mapconv:"ValueA.ValueB.B"`
    31  	C          string `mapconv:"ValueA.ValueB.ValueC.C"`
    32  	Ignore     string `mapconv:"-"`
    33  	Pointer    *time.Time
    34  	Slice      []string
    35  	NoTag      string
    36  	Bool       bool
    37  	unexported string
    38  }
    39  
    40  type dummyTo struct {
    41  	ValueA *struct {
    42  		A      string
    43  		ValueB *struct {
    44  			B      string
    45  			ValueC *struct {
    46  				C string
    47  			}
    48  		}
    49  	}
    50  	Ignore  string
    51  	Pointer *time.Time
    52  	Slice   []string
    53  	NoTag   string
    54  	Bool    bool
    55  }
    56  
    57  func TestConvertTo(t *testing.T) {
    58  	zeroTime := time.Unix(0, 0)
    59  	tests := []struct {
    60  		input  *dummyFrom
    61  		output *dummyTo
    62  		err    error
    63  	}{
    64  		{
    65  			input: &dummyFrom{
    66  				A:          "A",
    67  				B:          "B",
    68  				C:          "C",
    69  				Ignore:     "ignored",
    70  				Pointer:    &zeroTime,
    71  				Slice:      []string{"a", "b", "c"},
    72  				NoTag:      "NoTag",
    73  				Bool:       true,
    74  				unexported: "unexported",
    75  			},
    76  			output: &dummyTo{
    77  				ValueA: &struct {
    78  					A      string
    79  					ValueB *struct {
    80  						B      string
    81  						ValueC *struct {
    82  							C string
    83  						}
    84  					}
    85  				}{
    86  					A: "A",
    87  					ValueB: &struct {
    88  						B      string
    89  						ValueC *struct {
    90  							C string
    91  						}
    92  					}{
    93  						B: "B",
    94  						ValueC: &struct {
    95  							C string
    96  						}{
    97  							C: "C",
    98  						},
    99  					},
   100  				},
   101  				Pointer: &zeroTime,
   102  				Slice:   []string{"a", "b", "c"},
   103  				NoTag:   "NoTag",
   104  				Bool:    true,
   105  			},
   106  		},
   107  	}
   108  
   109  	for _, tt := range tests {
   110  		output := &dummyTo{}
   111  		err := ConvertTo(tt.input, output)
   112  		require.Equal(t, tt.err, err)
   113  		if err == nil {
   114  			require.EqualValues(t, tt.output.ValueA, output.ValueA)
   115  			require.EqualValues(t, tt.output.Pointer.String(), output.Pointer.String())
   116  			require.EqualValues(t, tt.output.Slice, output.Slice)
   117  			require.EqualValues(t, tt.output.NoTag, output.NoTag)
   118  		}
   119  	}
   120  }
   121  
   122  func TestConvertFrom(t *testing.T) {
   123  	tests := []struct {
   124  		output *dummyFrom
   125  		input  *dummyTo
   126  		err    error
   127  	}{
   128  		{
   129  			output: &dummyFrom{
   130  				A:     "A",
   131  				B:     "B",
   132  				C:     "C",
   133  				NoTag: "NoTag",
   134  				Bool:  true,
   135  			},
   136  			input: &dummyTo{
   137  				ValueA: &struct {
   138  					A      string
   139  					ValueB *struct {
   140  						B      string
   141  						ValueC *struct {
   142  							C string
   143  						}
   144  					}
   145  				}{
   146  					A: "A",
   147  					ValueB: &struct {
   148  						B      string
   149  						ValueC *struct {
   150  							C string
   151  						}
   152  					}{
   153  						B: "B",
   154  						ValueC: &struct {
   155  							C string
   156  						}{
   157  							C: "C",
   158  						},
   159  					},
   160  				},
   161  				NoTag: "NoTag",
   162  				Bool:  true,
   163  			},
   164  		},
   165  	}
   166  
   167  	for _, tt := range tests {
   168  		output := &dummyFrom{}
   169  		err := ConvertFrom(tt.input, output)
   170  		require.Equal(t, tt.err, err)
   171  		if err == nil {
   172  			require.Equal(t, tt.output, output)
   173  		}
   174  	}
   175  }
   176  
   177  type dummySlice struct {
   178  	Slice []*dummySliceInner `json:",omitempty"`
   179  }
   180  
   181  type dummySliceInner struct {
   182  	Value string             `json:",omitempty"`
   183  	Slice []*dummySliceInner `json:",omitempty"`
   184  }
   185  
   186  type dummyExtractInnerSlice struct {
   187  	Values       []string `json:",omitempty" mapconv:"[]Slice.Value"`
   188  	NestedValues []string `json:",omitempty" mapconv:"[]Slice.[]Slice.Value"`
   189  }
   190  
   191  func TestExtractInnerSlice(t *testing.T) {
   192  	tests := []struct {
   193  		input  *dummySlice
   194  		expect *dummyExtractInnerSlice
   195  	}{
   196  		{
   197  			input: &dummySlice{
   198  				Slice: []*dummySliceInner{
   199  					{Value: "value1"},
   200  					{Value: "value2"},
   201  					{
   202  						Value: "value3",
   203  						Slice: []*dummySliceInner{
   204  							{Value: "value4"},
   205  							{Value: "value5"},
   206  						},
   207  					},
   208  				},
   209  			},
   210  			expect: &dummyExtractInnerSlice{
   211  				Values:       []string{"value1", "value2", "value3"},
   212  				NestedValues: []string{"value4", "value5"},
   213  			},
   214  		},
   215  	}
   216  
   217  	for _, tt := range tests {
   218  		output := &dummyExtractInnerSlice{}
   219  		err := ConvertFrom(tt.input, output)
   220  
   221  		require.NoError(t, err)
   222  		require.Equal(t, tt.expect, output)
   223  	}
   224  }
   225  
   226  func TestInsertInnerSlice(t *testing.T) {
   227  	tests := []struct {
   228  		input  *dummyExtractInnerSlice
   229  		output *dummySlice
   230  	}{
   231  		{
   232  			input: &dummyExtractInnerSlice{
   233  				Values:       []string{"value1", "value2", "value3"},
   234  				NestedValues: []string{"value4", "value5"},
   235  			},
   236  			output: &dummySlice{
   237  				Slice: []*dummySliceInner{
   238  					{Value: "value1"},
   239  					{Value: "value2"},
   240  					{Value: "value3"},
   241  					{
   242  						Slice: []*dummySliceInner{
   243  							{Value: "value4"},
   244  						},
   245  					},
   246  					{
   247  						Slice: []*dummySliceInner{
   248  							{Value: "value5"},
   249  						},
   250  					},
   251  				},
   252  			},
   253  		},
   254  	}
   255  
   256  	for _, tt := range tests {
   257  		output := &dummySlice{}
   258  		err := ConvertTo(tt.input, output)
   259  
   260  		require.NoError(t, err)
   261  		require.Equal(t, tt.output, output)
   262  	}
   263  }
   264  
   265  type hasDefaultSource struct {
   266  	Field string `mapconv:"Field,default=default-value"`
   267  }
   268  
   269  type hasDefaultDest struct {
   270  	Field string
   271  }
   272  
   273  func TestDefaultValue(t *testing.T) {
   274  	tests := []struct {
   275  		input  *hasDefaultSource
   276  		output *hasDefaultDest
   277  	}{
   278  		{
   279  			input: &hasDefaultSource{},
   280  			output: &hasDefaultDest{
   281  				Field: "default-value",
   282  			},
   283  		},
   284  	}
   285  
   286  	for _, tt := range tests {
   287  		output := &hasDefaultDest{}
   288  		err := ConvertTo(tt.input, output)
   289  		require.NoError(t, err)
   290  		require.Equal(t, tt.output, output)
   291  	}
   292  }
   293  
   294  type multipleSource struct {
   295  	Field string `mapconv:"Field1/Field2"`
   296  }
   297  
   298  type multipleDest struct {
   299  	Field1 string
   300  	Field2 string
   301  }
   302  
   303  func TestMultipleDestination(t *testing.T) {
   304  	tests := []struct {
   305  		input  *multipleSource
   306  		output *multipleDest
   307  	}{
   308  		{
   309  			input: &multipleSource{
   310  				Field: "value",
   311  			},
   312  			output: &multipleDest{
   313  				Field1: "value",
   314  				Field2: "value",
   315  			},
   316  		},
   317  	}
   318  
   319  	for _, tt := range tests {
   320  		output := &multipleDest{}
   321  		err := ConvertTo(tt.input, output)
   322  		require.NoError(t, err)
   323  		require.Equal(t, tt.output, output)
   324  	}
   325  }
   326  
   327  type recursiveSource struct {
   328  	Field *recursiveSourceChild `mapconv:",recursive"`
   329  }
   330  
   331  type recursiveSourceChild struct {
   332  	Field1 string `mapconv:"Dest1,omitempty"`
   333  	Field2 string `mapconv:"Dest2,omitempty"`
   334  }
   335  
   336  type recursiveDest struct {
   337  	Field *recursiveDestChild
   338  }
   339  
   340  type recursiveDestChild struct {
   341  	Dest1 string
   342  	Dest2 string
   343  }
   344  
   345  type recursiveSourceSlice struct {
   346  	Fields []*recursiveSourceChild `mapconv:"[]Slice,recursive"`
   347  }
   348  
   349  type recursiveDestSlice struct {
   350  	Slice []*recursiveDestChild
   351  }
   352  
   353  func TestRecursive(t *testing.T) {
   354  	tests := []struct {
   355  		input  *recursiveSource
   356  		expect *recursiveDest
   357  	}{
   358  		{
   359  			input: &recursiveSource{
   360  				Field: &recursiveSourceChild{
   361  					Field1: "value1",
   362  					Field2: "value2",
   363  				},
   364  			},
   365  			expect: &recursiveDest{
   366  				Field: &recursiveDestChild{
   367  					Dest1: "value1",
   368  					Dest2: "value2",
   369  				},
   370  			},
   371  		},
   372  	}
   373  
   374  	for _, tt := range tests {
   375  		dest := &recursiveDest{}
   376  		err := ConvertTo(tt.input, dest)
   377  		require.NoError(t, err)
   378  		require.Equal(t, tt.expect, dest)
   379  
   380  		// reverse
   381  		source := &recursiveSource{}
   382  		err = ConvertFrom(tt.expect, source)
   383  		require.NoError(t, err)
   384  		require.Equal(t, tt.input, source)
   385  	}
   386  }
   387  
   388  func TestRecursiveSlice(t *testing.T) {
   389  	tests := []struct {
   390  		input  *recursiveSourceSlice
   391  		output *recursiveDestSlice
   392  	}{
   393  		{
   394  			input: &recursiveSourceSlice{
   395  				Fields: []*recursiveSourceChild{
   396  					{
   397  						Field1: "value1",
   398  						Field2: "value2",
   399  					},
   400  					{
   401  						Field1: "value3",
   402  						Field2: "value4",
   403  					},
   404  				},
   405  			},
   406  			output: &recursiveDestSlice{
   407  				Slice: []*recursiveDestChild{
   408  					{
   409  						Dest1: "value1",
   410  						Dest2: "value2",
   411  					},
   412  					{
   413  						Dest1: "value3",
   414  						Dest2: "value4",
   415  					},
   416  				},
   417  			},
   418  		},
   419  	}
   420  
   421  	for _, tt := range tests {
   422  		output := &recursiveDestSlice{}
   423  		err := ConvertTo(tt.input, output)
   424  		require.NoError(t, err)
   425  		require.Equal(t, tt.output, output)
   426  
   427  		// reverse
   428  		source := &recursiveSourceSlice{}
   429  		err = ConvertFrom(tt.output, source)
   430  		require.NoError(t, err)
   431  		require.Equal(t, tt.input, source)
   432  	}
   433  }
   434  
   435  func TestRecursiveSliceMerging(t *testing.T) {
   436  	tests := []struct {
   437  		src    *recursiveSourceSlice
   438  		dest   *recursiveDestSlice
   439  		expect *recursiveDestSlice
   440  	}{
   441  		{
   442  			src: &recursiveSourceSlice{
   443  				Fields: []*recursiveSourceChild{
   444  					{
   445  						Field1: "value1-upd",
   446  					},
   447  					{
   448  						Field2: "value4-upd",
   449  					},
   450  				},
   451  			},
   452  			dest: &recursiveDestSlice{
   453  				Slice: []*recursiveDestChild{
   454  					{
   455  						Dest1: "value1",
   456  						Dest2: "value2",
   457  					},
   458  					{
   459  						Dest1: "value3",
   460  						Dest2: "value4",
   461  					},
   462  				},
   463  			},
   464  			expect: &recursiveDestSlice{
   465  				Slice: []*recursiveDestChild{
   466  					{
   467  						Dest1: "value1-upd",
   468  						Dest2: "value2",
   469  					},
   470  					{
   471  						Dest1: "value3",
   472  						Dest2: "value4-upd",
   473  					},
   474  				},
   475  			},
   476  		},
   477  	}
   478  
   479  	for _, tc := range tests {
   480  		err := ConvertTo(tc.src, tc.dest)
   481  		require.NoError(t, err)
   482  		require.EqualValues(t, tc.expect, tc.dest)
   483  	}
   484  }
   485  
   486  type sourceSquash struct {
   487  	Field *sourceSquashChild `mapconv:",squash"`
   488  }
   489  
   490  type sourceSquashChild struct {
   491  	Field1 string
   492  	Field2 string
   493  }
   494  
   495  type destSquash struct {
   496  	Field1 string
   497  	Field2 string
   498  }
   499  
   500  func TestSquash(t *testing.T) {
   501  	tests := []struct {
   502  		input  *sourceSquash
   503  		output *destSquash
   504  	}{
   505  		{
   506  			input: &sourceSquash{
   507  				Field: &sourceSquashChild{
   508  					Field1: "f1",
   509  					Field2: "f2",
   510  				},
   511  			},
   512  			output: &destSquash{
   513  				Field1: "f1",
   514  				Field2: "f2",
   515  			},
   516  		},
   517  	}
   518  
   519  	for _, tt := range tests {
   520  		output := &destSquash{}
   521  		err := ConvertTo(tt.input, output)
   522  		require.NoError(t, err)
   523  		require.Equal(t, tt.output, output)
   524  
   525  		// reverse
   526  		source := &sourceSquash{}
   527  		err = ConvertFrom(tt.output, source)
   528  		require.Error(t, err)
   529  	}
   530  }
   531  
   532  func testDecoder() *Decoder {
   533  	strToNumFilter := func(v interface{}) (interface{}, error) {
   534  		return strconv.ParseInt(v.(string), 10, 64)
   535  	}
   536  	toUpperFilter := func(v interface{}) (interface{}, error) {
   537  		// to upper
   538  		return strings.ToUpper(v.(string)), nil
   539  	}
   540  	numToIDFilter := func(v interface{}) (interface{}, error) {
   541  		return types.ID(v.(int64)), nil
   542  	}
   543  	errorFilter := func(v interface{}) (interface{}, error) {
   544  		return nil, errors.New("foobar")
   545  	}
   546  
   547  	return &Decoder{Config: &DecoderConfig{
   548  		TagName: DefaultMapConvTag,
   549  		FilterFuncs: map[string]FilterFunc{
   550  			"toUpper":  toUpperFilter,
   551  			"strToNum": strToNumFilter,
   552  			"numToID":  numToIDFilter,
   553  			"error":    errorFilter,
   554  		},
   555  	}}
   556  }
   557  
   558  func TestFiltersWithConvertTo(t *testing.T) {
   559  	decoder := testDecoder()
   560  
   561  	cases := []struct {
   562  		in     interface{}
   563  		dest   interface{}
   564  		expect interface{}
   565  		err    error
   566  	}{
   567  		{
   568  			in: &struct {
   569  				Field string `mapconv:",filters=toUpper"`
   570  			}{Field: "foo"},
   571  			dest:   &struct{ Field string }{},
   572  			expect: &struct{ Field string }{Field: "FOO"},
   573  		},
   574  		{
   575  			in: &struct {
   576  				Field string `mapconv:",filters=strToNum numToID"`
   577  			}{Field: "1"},
   578  			dest:   &struct{ Field types.ID }{},
   579  			expect: &struct{ Field types.ID }{Field: types.ID(1)},
   580  		},
   581  		{
   582  			in: &struct {
   583  				Field string `mapconv:",filters=error"`
   584  			}{Field: "1"},
   585  			dest: &struct{ Field types.ID }{},
   586  			err:  errors.New("failed to apply the filter: foobar"),
   587  		},
   588  		{
   589  			in: &struct {
   590  				Field string `mapconv:",filters=strToNum numToID error"`
   591  			}{Field: "1"},
   592  			dest: &struct{ Field types.ID }{},
   593  			err:  errors.New("failed to apply the filter: foobar"),
   594  		},
   595  	}
   596  
   597  	for _, tc := range cases {
   598  		err := decoder.ConvertTo(tc.in, tc.dest)
   599  		require.Equal(t, tc.err, err)
   600  		if err == nil {
   601  			require.EqualValues(t, tc.expect, tc.dest)
   602  		}
   603  	}
   604  }
   605  
   606  func TestFiltersWithConvertFrom(t *testing.T) {
   607  	decoder := testDecoder()
   608  
   609  	cases := []struct {
   610  		in     interface{}
   611  		dest   interface{}
   612  		expect interface{}
   613  		err    error
   614  	}{
   615  		{
   616  			in: &struct{ Field string }{Field: "foo"},
   617  			dest: &struct {
   618  				Field string `mapconv:",filters=toUpper"`
   619  			}{},
   620  			expect: &struct {
   621  				Field string `mapconv:",filters=toUpper"`
   622  			}{Field: "FOO"},
   623  		},
   624  		{
   625  			in: &struct{ Field string }{Field: "1"},
   626  			dest: &struct {
   627  				Field types.ID `mapconv:",filters=strToNum numToID"`
   628  			}{},
   629  			expect: &struct {
   630  				Field types.ID `mapconv:",filters=strToNum numToID"`
   631  			}{Field: types.ID(1)},
   632  		},
   633  		{
   634  			in: &struct{ Field string }{Field: "1"},
   635  			dest: &struct {
   636  				Field types.ID `mapconv:",filters=error"`
   637  			}{},
   638  			err: errors.New("failed to apply the filter: foobar"),
   639  		},
   640  		{
   641  			in: &struct{ Field string }{Field: "1"},
   642  			dest: &struct {
   643  				Field types.ID `mapconv:",filters=strToNum numToID error"`
   644  			}{},
   645  			err: errors.New("failed to apply the filter: foobar"),
   646  		},
   647  	}
   648  
   649  	for _, tc := range cases {
   650  		err := decoder.ConvertFrom(tc.in, tc.dest)
   651  		require.Equal(t, tc.err, err)
   652  		if err == nil {
   653  			require.EqualValues(t, tc.expect, tc.dest)
   654  		}
   655  	}
   656  }
   657  
   658  type recursiveMerge struct {
   659  	Nest *recursiveMergeNest `mapconv:",recursive"`
   660  }
   661  
   662  type recursiveMergeNest struct {
   663  	Field1 string `mapconv:",omitempty"`
   664  	Field2 string `mapconv:",omitempty"`
   665  }
   666  
   667  func TestOverwrite(t *testing.T) {
   668  	cases := []struct {
   669  		src    *recursiveMergeNest
   670  		dest   *recursiveMergeNest
   671  		expect *recursiveMergeNest
   672  	}{
   673  		{
   674  			src: &recursiveMergeNest{
   675  				Field1: "field1-upd",
   676  				Field2: "",
   677  			},
   678  			dest: &recursiveMergeNest{
   679  				Field1: "field1",
   680  				Field2: "field2",
   681  			},
   682  			expect: &recursiveMergeNest{
   683  				Field1: "field1-upd",
   684  				Field2: "field2",
   685  			},
   686  		},
   687  	}
   688  	for _, tc := range cases {
   689  		if err := ConvertTo(tc.src, tc.dest); err != nil {
   690  			t.Fatal(err)
   691  		}
   692  		require.EqualValues(t, tc.expect, tc.dest)
   693  	}
   694  }
   695  
   696  func TestRecursiveMerge(t *testing.T) {
   697  	cases := []struct {
   698  		src    *recursiveMerge
   699  		dest   *recursiveMerge
   700  		expect *recursiveMerge
   701  	}{
   702  		{
   703  			src: &recursiveMerge{
   704  				Nest: &recursiveMergeNest{
   705  					Field1: "field1-upd",
   706  					Field2: "",
   707  				},
   708  			},
   709  			dest: &recursiveMerge{
   710  				Nest: &recursiveMergeNest{
   711  					Field1: "field1",
   712  					Field2: "field2",
   713  				},
   714  			},
   715  			expect: &recursiveMerge{
   716  				Nest: &recursiveMergeNest{
   717  					Field1: "field1-upd",
   718  					Field2: "field2",
   719  				},
   720  			},
   721  		},
   722  	}
   723  	for _, tc := range cases {
   724  		if err := ConvertTo(tc.src, tc.dest); err != nil {
   725  			t.Fatal(err)
   726  		}
   727  		require.EqualValues(t, tc.expect, tc.dest)
   728  	}
   729  }