github.com/ethersphere/bee/v2@v2.2.0/pkg/api/util_test.go (about)

     1  // Copyright 2020 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package api_test
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"math"
    11  	"math/big"
    12  	"reflect"
    13  	"strconv"
    14  	"testing"
    15  
    16  	"github.com/ethereum/go-ethereum/common"
    17  	"github.com/ethersphere/bee/v2/pkg/api"
    18  	"github.com/ethersphere/bee/v2/pkg/swarm"
    19  	"github.com/google/go-cmp/cmp"
    20  )
    21  
    22  type (
    23  	mapBoolTest struct {
    24  		BoolVal bool `map:"boolVal"`
    25  	}
    26  
    27  	mapUintTest struct {
    28  		UintVal uint `map:"uintVal"`
    29  	}
    30  
    31  	mapUint8Test struct {
    32  		Uint8Val uint8 `map:"uint8Val"`
    33  	}
    34  
    35  	mapUint16Test struct {
    36  		Uint16Val uint16 `map:"uint16Val"`
    37  	}
    38  
    39  	mapUint32Test struct {
    40  		Uint32Val uint32 `map:"uint32Val"`
    41  	}
    42  
    43  	mapUint64Test struct {
    44  		Uint64Val uint64 `map:"uint64Val"`
    45  	}
    46  
    47  	mapIntTest struct {
    48  		IntVal int `map:"intVal"`
    49  	}
    50  
    51  	mapInt8Test struct {
    52  		Int8Val int8 `map:"int8Val"`
    53  	}
    54  
    55  	mapInt16Test struct {
    56  		Int16Val int16 `map:"int16Val"`
    57  	}
    58  
    59  	mapInt32Test struct {
    60  		Int32Val int32 `map:"int32Val"`
    61  	}
    62  
    63  	mapInt64Test struct {
    64  		Int64Val int64 `map:"int64Val"`
    65  	}
    66  
    67  	mapFloat32Test struct {
    68  		Float32Val float32 `map:"float32Val"`
    69  	}
    70  
    71  	mapFloat64Test struct {
    72  		Float64Val float64 `map:"float64Val"`
    73  	}
    74  
    75  	mapByteSliceTest struct {
    76  		ByteSliceVal []byte `map:"byteSliceVal"`
    77  	}
    78  
    79  	mapStringTest struct {
    80  		StringVal string `map:"stringVal"`
    81  	}
    82  
    83  	mapStringWithOmitemptyTest struct {
    84  		StringVal string `map:"stringVal,omitempty"`
    85  	}
    86  
    87  	mapBigIntTest struct {
    88  		BigIntVal *big.Int `map:"bigIntVal"`
    89  	}
    90  
    91  	mapCommonHashTest struct {
    92  		CommonHashVal common.Hash `map:"commonHashVal"`
    93  	}
    94  
    95  	mapSwarmAddressTest struct {
    96  		SwarmAddressVal swarm.Address `map:"swarmAddressVal"`
    97  	}
    98  )
    99  
   100  func TestMapStructure(t *testing.T) {
   101  	t.Parallel()
   102  
   103  	tests := []struct {
   104  		name    string
   105  		src     interface{}
   106  		want    interface{}
   107  		wantErr error
   108  	}{{
   109  		name: "bool zero value",
   110  		src:  map[string]string{"boolVal": "0"},
   111  		want: &mapBoolTest{},
   112  	}, {
   113  		name: "bool false",
   114  		src:  map[string]string{"boolVal": "false"},
   115  		want: &mapBoolTest{BoolVal: false},
   116  	}, {
   117  		name: "bool true",
   118  		src:  map[string]string{"boolVal": "true"},
   119  		want: &mapBoolTest{BoolVal: true},
   120  	}, {
   121  		name:    "bool syntax error",
   122  		src:     map[string]string{"boolVal": "a"},
   123  		want:    &mapBoolTest{},
   124  		wantErr: api.NewParseError("boolVal", "a", strconv.ErrSyntax),
   125  	}, {
   126  		name: "uint zero value",
   127  		src:  map[string]string{"uintVal": "0"},
   128  		want: &mapUintTest{},
   129  	}, {
   130  		name: "uint in range value",
   131  		src:  map[string]string{"uintVal": "1"},
   132  		want: &mapUintTest{UintVal: 1},
   133  	}, {
   134  		name: "uint max value",
   135  		src:  map[string]string{"uintVal": fmt.Sprintf("%d", uint(math.MaxUint))},
   136  		want: &mapUintTest{UintVal: math.MaxUint},
   137  	}, {
   138  		name:    "uint out of range value",
   139  		src:     map[string]string{"uintVal": "18446744073709551616"},
   140  		want:    &mapUintTest{},
   141  		wantErr: api.NewParseError("uintVal", "18446744073709551616", strconv.ErrRange),
   142  	}, {
   143  		name:    "uint syntax error",
   144  		src:     map[string]string{"uintVal": "one"},
   145  		want:    &mapUintTest{},
   146  		wantErr: api.NewParseError("uintVal", "one", strconv.ErrSyntax),
   147  	}, {
   148  		name: "uint8 zero value",
   149  		src:  map[string]string{"uint8Val": "0"},
   150  		want: &mapUint8Test{},
   151  	}, {
   152  		name: "uint8 in range value",
   153  		src:  map[string]string{"uint8Val": "10"},
   154  		want: &mapUint8Test{Uint8Val: 10},
   155  	}, {
   156  		name: "uint8 max value",
   157  		src:  map[string]string{"uint8Val": "255"},
   158  		want: &mapUint8Test{Uint8Val: math.MaxUint8},
   159  	}, {
   160  		name:    "uint8 out of range value",
   161  		src:     map[string]string{"uint8Val": "256"},
   162  		want:    &mapUint8Test{},
   163  		wantErr: api.NewParseError("uint8Val", "256", strconv.ErrRange),
   164  	}, {
   165  		name:    "uint8 syntax error",
   166  		src:     map[string]string{"uint8Val": "ten"},
   167  		want:    &mapUint8Test{},
   168  		wantErr: api.NewParseError("uint8Val", "ten", strconv.ErrSyntax),
   169  	}, {
   170  		name: "uint16 zero value",
   171  		src:  map[string]string{"uint16Val": "0"},
   172  		want: &mapUint16Test{},
   173  	}, {
   174  		name: "uint16 in range value",
   175  		src:  map[string]string{"uint16Val": "100"},
   176  		want: &mapUint16Test{Uint16Val: 100},
   177  	}, {
   178  		name: "uint16 max value",
   179  		src:  map[string]string{"uint16Val": "65535"},
   180  		want: &mapUint16Test{Uint16Val: math.MaxUint16},
   181  	}, {
   182  		name:    "uint16 out of range value",
   183  		src:     map[string]string{"uint16Val": "65536"},
   184  		want:    &mapUint16Test{},
   185  		wantErr: api.NewParseError("uint16Val", "65536", strconv.ErrRange),
   186  	}, {
   187  		name:    "uint16 syntax error",
   188  		src:     map[string]string{"uint16Val": "hundred"},
   189  		want:    &mapUint16Test{},
   190  		wantErr: api.NewParseError("uint16Val", "hundred", strconv.ErrSyntax),
   191  	}, {
   192  		name: "uint32 zero value",
   193  		src:  map[string]string{"uint32Val": "0"},
   194  		want: &mapUint32Test{},
   195  	}, {
   196  		name: "uint32 in range value",
   197  		src:  map[string]string{"uint32Val": "1000"},
   198  		want: &mapUint32Test{Uint32Val: 1000},
   199  	}, {
   200  		name: "uint32 max value",
   201  		src:  map[string]string{"uint32Val": "4294967295"},
   202  		want: &mapUint32Test{Uint32Val: math.MaxUint32},
   203  	}, {
   204  		name:    "uint32 out of range value",
   205  		src:     map[string]string{"uint32Val": "4294967296"},
   206  		want:    &mapUint32Test{},
   207  		wantErr: api.NewParseError("uint32Val", "4294967296", strconv.ErrRange),
   208  	}, {
   209  		name:    "uint32 syntax error",
   210  		src:     map[string]string{"uint32Val": "thousand"},
   211  		want:    &mapUint32Test{},
   212  		wantErr: api.NewParseError("uint32Val", "thousand", strconv.ErrSyntax),
   213  	}, {
   214  		name: "uint64 zero value",
   215  		src:  map[string]string{"uint64Val": "0"},
   216  		want: &mapUint64Test{},
   217  	}, {
   218  		name: "uint64 in range value",
   219  		src:  map[string]string{"uint64Val": "10000"},
   220  		want: &mapUint64Test{Uint64Val: 10000},
   221  	}, {
   222  		name: "uint64 max value",
   223  		src:  map[string]string{"uint64Val": "18446744073709551615"},
   224  		want: &mapUint64Test{Uint64Val: math.MaxUint64},
   225  	}, {
   226  		name:    "uint64 out of range value",
   227  		src:     map[string]string{"uint64Val": "18446744073709551616"},
   228  		want:    &mapUint64Test{},
   229  		wantErr: api.NewParseError("uint64Val", "18446744073709551616", strconv.ErrRange),
   230  	}, {
   231  		name:    "uint64 syntax error",
   232  		src:     map[string]string{"uint64Val": "ten thousand"},
   233  		want:    &mapUint64Test{},
   234  		wantErr: api.NewParseError("uint64Val", "ten thousand", strconv.ErrSyntax),
   235  	}, {
   236  		name: "int zero value",
   237  		src:  map[string]string{"intVal": "0"},
   238  		want: &mapIntTest{},
   239  	}, {
   240  		name: "int in range value",
   241  		src:  map[string]string{"intVal": "1"},
   242  		want: &mapIntTest{IntVal: 1},
   243  	}, {
   244  		name: "int min value",
   245  		src:  map[string]string{"intVal": fmt.Sprintf("%d", math.MinInt)},
   246  		want: &mapIntTest{IntVal: math.MinInt},
   247  	}, {
   248  		name: "int max value",
   249  		src:  map[string]string{"intVal": fmt.Sprintf("%d", math.MaxInt)},
   250  		want: &mapIntTest{IntVal: math.MaxInt},
   251  	}, {
   252  		name:    "int min out of range value",
   253  		src:     map[string]string{"intVal": "-9223372036854775809"},
   254  		want:    &mapIntTest{},
   255  		wantErr: api.NewParseError("intVal", "-9223372036854775809", strconv.ErrRange),
   256  	}, {
   257  		name:    "int max out of range value",
   258  		src:     map[string]string{"intVal": "9223372036854775808"},
   259  		want:    &mapIntTest{},
   260  		wantErr: api.NewParseError("intVal", "9223372036854775808", strconv.ErrRange),
   261  	}, {
   262  		name:    "int syntax error",
   263  		src:     map[string]string{"intVal": "one"},
   264  		want:    &mapIntTest{},
   265  		wantErr: api.NewParseError("intVal", "one", strconv.ErrSyntax),
   266  	}, {
   267  		name: "int8 zero value",
   268  		src:  map[string]string{"int8Val": "0"},
   269  		want: &mapInt8Test{},
   270  	}, {
   271  		name: "int8 in range value",
   272  		src:  map[string]string{"int8Val": "10"},
   273  		want: &mapInt8Test{Int8Val: 10},
   274  	}, {
   275  		name: "int8 min value",
   276  		src:  map[string]string{"int8Val": "-128"},
   277  		want: &mapInt8Test{Int8Val: math.MinInt8},
   278  	}, {
   279  		name: "int8 max value",
   280  		src:  map[string]string{"int8Val": "127"},
   281  		want: &mapInt8Test{Int8Val: math.MaxInt8},
   282  	}, {
   283  		name:    "int8 min out of range value",
   284  		src:     map[string]string{"int8Val": "-129"},
   285  		want:    &mapInt8Test{},
   286  		wantErr: api.NewParseError("int8Val", "-129", strconv.ErrRange),
   287  	}, {
   288  		name:    "int8 max out of range value",
   289  		src:     map[string]string{"int8Val": "128"},
   290  		want:    &mapInt8Test{},
   291  		wantErr: api.NewParseError("int8Val", "128", strconv.ErrRange),
   292  	}, {
   293  		name:    "int8 syntax error",
   294  		src:     map[string]string{"int8Val": "ten"},
   295  		want:    &mapInt8Test{},
   296  		wantErr: api.NewParseError("int8Val", "ten", strconv.ErrSyntax),
   297  	}, {
   298  		name: "int16 zero value",
   299  		src:  map[string]string{"int16Val": "0"},
   300  		want: &mapInt16Test{},
   301  	}, {
   302  		name: "int16 in range value",
   303  		src:  map[string]string{"int16Val": "100"},
   304  		want: &mapInt16Test{Int16Val: 100},
   305  	}, {
   306  		name: "int16 min value",
   307  		src:  map[string]string{"int16Val": "-32768"},
   308  		want: &mapInt16Test{Int16Val: math.MinInt16},
   309  	}, {
   310  		name: "int16 max value",
   311  		src:  map[string]string{"int16Val": "32767"},
   312  		want: &mapInt16Test{Int16Val: math.MaxInt16},
   313  	}, {
   314  		name:    "int16 min out of range value",
   315  		src:     map[string]string{"int16Val": "-32769"},
   316  		want:    &mapInt16Test{},
   317  		wantErr: api.NewParseError("int16Val", "-32769", strconv.ErrRange),
   318  	}, {
   319  		name:    "int16 max out of range value",
   320  		src:     map[string]string{"int16Val": "32768"},
   321  		want:    &mapInt16Test{},
   322  		wantErr: api.NewParseError("int16Val", "32768", strconv.ErrRange),
   323  	}, {
   324  		name:    "int16 syntax error",
   325  		src:     map[string]string{"int16Val": "hundred"},
   326  		want:    &mapInt16Test{},
   327  		wantErr: api.NewParseError("int16Val", "hundred", strconv.ErrSyntax),
   328  	}, {
   329  		name: "int32 zero value",
   330  		src:  map[string]string{"int32Val": "0"},
   331  		want: &mapInt32Test{},
   332  	}, {
   333  		name: "int32 in range value",
   334  		src:  map[string]string{"int32Val": "1000"},
   335  		want: &mapInt32Test{Int32Val: 1000},
   336  	}, {
   337  		name: "int32 min value",
   338  		src:  map[string]string{"int32Val": "-2147483648"},
   339  		want: &mapInt32Test{Int32Val: math.MinInt32},
   340  	}, {
   341  		name: "int32 max value",
   342  		src:  map[string]string{"int32Val": "2147483647"},
   343  		want: &mapInt32Test{Int32Val: math.MaxInt32},
   344  	}, {
   345  		name:    "int32 min out of range value",
   346  		src:     map[string]string{"int32Val": "-2147483649"},
   347  		want:    &mapInt32Test{},
   348  		wantErr: api.NewParseError("int32Val", "-2147483649", strconv.ErrRange),
   349  	}, {
   350  		name:    "int32 max out of range value",
   351  		src:     map[string]string{"int32Val": "2147483648"},
   352  		want:    &mapInt32Test{},
   353  		wantErr: api.NewParseError("int32Val", "2147483648", strconv.ErrRange),
   354  	}, {
   355  		name:    "int32 syntax error",
   356  		src:     map[string]string{"int32Val": "thousand"},
   357  		want:    &mapInt32Test{},
   358  		wantErr: api.NewParseError("int32Val", "thousand", strconv.ErrSyntax),
   359  	}, {
   360  		name: "int64 zero value",
   361  		src:  map[string]string{"int64Val": "0"},
   362  		want: &mapInt64Test{},
   363  	}, {
   364  		name: "int64 in range value",
   365  		src:  map[string]string{"int64Val": "10000"},
   366  		want: &mapInt64Test{Int64Val: 10000},
   367  	}, {
   368  		name: "int64 min value",
   369  		src:  map[string]string{"int64Val": "-9223372036854775808"},
   370  		want: &mapInt64Test{Int64Val: math.MinInt64},
   371  	}, {
   372  		name: "int64 max value",
   373  		src:  map[string]string{"int64Val": "9223372036854775807"},
   374  		want: &mapInt64Test{Int64Val: math.MaxInt64},
   375  	}, {
   376  		name:    "int64 min out of range value",
   377  		src:     map[string]string{"int64Val": "-9223372036854775809"},
   378  		want:    &mapInt64Test{},
   379  		wantErr: api.NewParseError("int64Val", "-9223372036854775809", strconv.ErrRange),
   380  	}, {
   381  		name:    "int64 max out of range value",
   382  		src:     map[string]string{"int64Val": "9223372036854775808"},
   383  		want:    &mapInt64Test{},
   384  		wantErr: api.NewParseError("int64Val", "9223372036854775808", strconv.ErrRange),
   385  	}, {
   386  		name:    "int64 syntax error",
   387  		src:     map[string]string{"int64Val": "ten thousand"},
   388  		want:    &mapInt64Test{},
   389  		wantErr: api.NewParseError("int64Val", "ten thousand", strconv.ErrSyntax),
   390  	}, {
   391  		name: "float32 zero value",
   392  		src:  map[string]string{"float32Val": "0"},
   393  		want: &mapFloat32Test{},
   394  	}, {
   395  		name: "float32 in range value",
   396  		src:  map[string]string{"float32Val": "10.12345"},
   397  		want: &mapFloat32Test{Float32Val: 10.12345},
   398  	}, {
   399  		name: "float32 min value",
   400  		src:  map[string]string{"float32Val": "1.401298464324817070923729583289916131280e-45"},
   401  		want: &mapFloat32Test{Float32Val: math.SmallestNonzeroFloat32},
   402  	}, {
   403  		name: "float32 max value",
   404  		src:  map[string]string{"float32Val": "3.40282346638528859811704183484516925440e+38"},
   405  		want: &mapFloat32Test{Float32Val: math.MaxFloat32},
   406  	}, {
   407  		name:    "float32 max out of range value",
   408  		src:     map[string]string{"float32Val": "3.40282346638528859811704183484516925440e+39"},
   409  		want:    &mapFloat32Test{},
   410  		wantErr: api.NewParseError("float32Val", "3.40282346638528859811704183484516925440e+39", strconv.ErrRange),
   411  	}, {
   412  		name:    "float32 syntax error",
   413  		src:     map[string]string{"float32Val": "ten point one ... five"},
   414  		want:    &mapFloat32Test{},
   415  		wantErr: api.NewParseError("float32Val", "ten point one ... five", strconv.ErrSyntax),
   416  	}, {
   417  		name: "float64 zero value",
   418  		src:  map[string]string{"float64Val": "0"},
   419  		want: &mapFloat64Test{},
   420  	}, {
   421  		name: "float64 in range value",
   422  		src:  map[string]string{"float64Val": "10.123456789"},
   423  		want: &mapFloat64Test{Float64Val: 10.123456789},
   424  	}, {
   425  		name: "float64 min value",
   426  		src:  map[string]string{"float64Val": "4.9406564584124654417656879286822137236505980e-324"},
   427  		want: &mapFloat64Test{Float64Val: math.SmallestNonzeroFloat64},
   428  	}, {
   429  		name: "float64 max value",
   430  		src:  map[string]string{"float64Val": "1.79769313486231570814527423731704356798070e+308"},
   431  		want: &mapFloat64Test{Float64Val: math.MaxFloat64},
   432  	}, {
   433  		name:    "float64 max out of range value",
   434  		src:     map[string]string{"float64Val": "1.79769313486231570814527423731704356798070e+309"},
   435  		want:    &mapFloat64Test{},
   436  		wantErr: api.NewParseError("float64Val", "1.79769313486231570814527423731704356798070e+309", strconv.ErrRange),
   437  	}, {
   438  		name:    "float64 syntax error",
   439  		src:     map[string]string{"float64Val": "ten point one ... nine"},
   440  		want:    &mapFloat64Test{},
   441  		wantErr: api.NewParseError("float64Val", "ten point one ... nine", strconv.ErrSyntax),
   442  	}, {
   443  		name: "byte slice zero value",
   444  		src:  map[string]string{"byteSliceVal": ""},
   445  		want: &mapByteSliceTest{},
   446  	}, {
   447  		name: "byte slice single byte",
   448  		src:  map[string]string{"byteSliceVal": "66"},
   449  		want: &mapByteSliceTest{ByteSliceVal: []byte{'f'}},
   450  	}, {
   451  		name: "byte slice multiple bytes",
   452  		src:  map[string]string{"byteSliceVal": "000102030405060708090a0b0c0d0e0f"},
   453  		want: &mapByteSliceTest{ByteSliceVal: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}},
   454  	}, {
   455  		name:    "byte slice invalid byte",
   456  		src:     map[string]string{"byteSliceVal": "0g"},
   457  		want:    &mapByteSliceTest{},
   458  		wantErr: api.NewParseError("byteSliceVal", "0g", api.HexInvalidByteError('g')),
   459  	}, {
   460  		name:    "byte slice invalid length",
   461  		src:     map[string]string{"byteSliceVal": "fff"},
   462  		want:    &mapByteSliceTest{},
   463  		wantErr: api.NewParseError("byteSliceVal", "fff", api.ErrHexLength),
   464  	}, {
   465  		name: "string zero value",
   466  		src:  map[string]string{"stringVal": ""},
   467  		want: &mapStringTest{},
   468  	}, {
   469  		name: "string single character",
   470  		src:  map[string]string{"stringVal": "F"},
   471  		want: &mapStringTest{StringVal: "F"},
   472  	}, {
   473  		name: "string multiple characters",
   474  		src:  map[string]string{"stringVal": "Hello, World!"},
   475  		want: &mapStringTest{StringVal: "Hello, World!"},
   476  	}, {
   477  		name: "string with multiple values",
   478  		src:  map[string][]string{"stringVal": {"11", "22", "33"}},
   479  		want: &mapStringTest{StringVal: "11"},
   480  	}, {
   481  		name: "string without matching field",
   482  		src:  map[string]string{"-": "key does not match any field"},
   483  		want: &mapStringTest{},
   484  	}, {
   485  		name: "string with omitempty field",
   486  		src:  map[string]string{"stringVal": ""},
   487  		want: &mapStringWithOmitemptyTest{},
   488  	}, {
   489  		name: "bit.Int value",
   490  		src:  map[string]string{"bigIntVal": "1234567890"},
   491  		want: &mapBigIntTest{BigIntVal: new(big.Int).SetInt64(1234567890)},
   492  	}, {
   493  		name: "common.Hash value",
   494  		src:  map[string]string{"commonHashVal": "0x1234567890abcdef"},
   495  		want: &mapCommonHashTest{CommonHashVal: common.HexToHash("0x1234567890abcdef")},
   496  	}, {
   497  		name: "swarm.Address value",
   498  		src:  map[string]string{"swarmAddressVal": "1234567890abcdef"},
   499  		want: &mapSwarmAddressTest{SwarmAddressVal: swarm.MustParseHexAddress("1234567890abcdef")},
   500  	}}
   501  	for _, tc := range tests {
   502  		tc := tc
   503  
   504  		t.Run(tc.name, func(t *testing.T) {
   505  			t.Parallel()
   506  
   507  			have := reflect.New(reflect.TypeOf(tc.want).Elem()).Interface()
   508  			haveErr := errors.Unwrap(api.MapStructure(tc.src, have, nil))
   509  			if diff := cmp.Diff(tc.wantErr, haveErr); diff != "" {
   510  				t.Fatalf("api.mapStructure(...): error mismatch (-want +have):\n%s", diff)
   511  			}
   512  			if diff := cmp.Diff(tc.want, have, cmp.AllowUnexported(big.Int{})); diff != "" {
   513  				t.Errorf("api.mapStructure(...): result mismatch (-want +have):\n%s", diff)
   514  			}
   515  		})
   516  	}
   517  }
   518  
   519  func TestMapStructure_InputOutputSanityCheck(t *testing.T) {
   520  	t.Parallel()
   521  
   522  	t.Run("input is nil", func(t *testing.T) {
   523  		t.Parallel()
   524  
   525  		var input interface{}
   526  		err := api.MapStructure(input, struct{}{}, nil)
   527  		if err != nil {
   528  			t.Fatalf("unexpected error: %v", err)
   529  		}
   530  	})
   531  
   532  	t.Run("input is not a map", func(t *testing.T) {
   533  		t.Parallel()
   534  
   535  		input := "foo"
   536  		err := api.MapStructure(&input, struct{}{}, nil)
   537  		if err == nil {
   538  			t.Fatalf("expected error; have none")
   539  		}
   540  	})
   541  
   542  	t.Run("output is not a pointer", func(t *testing.T) {
   543  		t.Parallel()
   544  
   545  		var (
   546  			input  = map[string]interface{}{"someVal": "123"}
   547  			output struct {
   548  				SomeVal string `map:"someVal"`
   549  			}
   550  		)
   551  		err := api.MapStructure(&input, output, nil)
   552  		if err == nil {
   553  			t.Fatalf("expected error; have none")
   554  		}
   555  	})
   556  
   557  	t.Run("output is nil", func(t *testing.T) {
   558  		t.Parallel()
   559  
   560  		var (
   561  			input  = map[string]interface{}{"someVal": "123"}
   562  			output interface{}
   563  		)
   564  		err := api.MapStructure(&input, output, nil)
   565  		if err != nil {
   566  			t.Fatalf("unexpected error: %v", err)
   567  		}
   568  	})
   569  
   570  	t.Run("output is a nil pointer", func(t *testing.T) {
   571  		t.Parallel()
   572  
   573  		var (
   574  			input  = map[string]interface{}{"someVal": "123"}
   575  			output = struct {
   576  				SomeVal string `map:"someVal"`
   577  			}{}
   578  		)
   579  		err := api.MapStructure(&input, &output, nil)
   580  		if err != nil {
   581  			t.Fatalf("unexpected error: %v", err)
   582  		}
   583  	})
   584  
   585  	t.Run("output is not a struct", func(t *testing.T) {
   586  		t.Parallel()
   587  
   588  		var (
   589  			input  = map[string]interface{}{"someVal": "123"}
   590  			output = "foo"
   591  		)
   592  		err := api.MapStructure(&input, &output, nil)
   593  		if err == nil {
   594  			t.Fatalf("expected error; have none")
   595  		}
   596  	})
   597  }