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 }