github.com/aavshr/aws-sdk-go@v1.41.3/service/dynamodb/dynamodbattribute/decode_test.go (about) 1 package dynamodbattribute 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "reflect" 7 "strconv" 8 "testing" 9 "time" 10 11 "github.com/aavshr/aws-sdk-go/aws" 12 "github.com/aavshr/aws-sdk-go/aws/awserr" 13 "github.com/aavshr/aws-sdk-go/service/dynamodb" 14 ) 15 16 func TestUnmarshalErrorTypes(t *testing.T) { 17 var _ awserr.Error = (*UnmarshalTypeError)(nil) 18 var _ awserr.Error = (*InvalidUnmarshalError)(nil) 19 } 20 21 func TestUnmarshalShared(t *testing.T) { 22 for i, c := range sharedTestCases { 23 err := Unmarshal(c.in, c.actual) 24 assertConvertTest(t, i, c.actual, c.expected, err, c.err) 25 } 26 } 27 28 func TestUnmarshal(t *testing.T) { 29 cases := []struct { 30 in *dynamodb.AttributeValue 31 actual, expected interface{} 32 err error 33 }{ 34 //------------ 35 // Sets 36 //------------ 37 { 38 in: &dynamodb.AttributeValue{BS: [][]byte{ 39 {48, 49}, {50, 51}, 40 }}, 41 actual: &[][]byte{}, 42 expected: [][]byte{{48, 49}, {50, 51}}, 43 }, 44 { 45 in: &dynamodb.AttributeValue{NS: []*string{ 46 aws.String("123"), aws.String("321"), 47 }}, 48 actual: &[]int{}, 49 expected: []int{123, 321}, 50 }, 51 { 52 in: &dynamodb.AttributeValue{NS: []*string{ 53 aws.String("123"), aws.String("321"), 54 }}, 55 actual: &[]interface{}{}, 56 expected: []interface{}{123., 321.}, 57 }, 58 { 59 in: &dynamodb.AttributeValue{SS: []*string{ 60 aws.String("abc"), aws.String("123"), 61 }}, 62 actual: &[]string{}, 63 expected: &[]string{"abc", "123"}, 64 }, 65 { 66 in: &dynamodb.AttributeValue{SS: []*string{ 67 aws.String("abc"), aws.String("123"), 68 }}, 69 actual: &[]*string{}, 70 expected: &[]*string{aws.String("abc"), aws.String("123")}, 71 }, 72 //------------ 73 // Interfaces 74 //------------ 75 { 76 in: &dynamodb.AttributeValue{B: []byte{48, 49}}, 77 actual: func() interface{} { 78 var v interface{} 79 return &v 80 }(), 81 expected: []byte{48, 49}, 82 }, 83 { 84 in: &dynamodb.AttributeValue{BS: [][]byte{ 85 {48, 49}, {50, 51}, 86 }}, 87 actual: func() interface{} { 88 var v interface{} 89 return &v 90 }(), 91 expected: [][]byte{{48, 49}, {50, 51}}, 92 }, 93 { 94 in: &dynamodb.AttributeValue{BOOL: aws.Bool(true)}, 95 actual: func() interface{} { 96 var v interface{} 97 return &v 98 }(), 99 expected: bool(true), 100 }, 101 { 102 in: &dynamodb.AttributeValue{L: []*dynamodb.AttributeValue{ 103 {S: aws.String("abc")}, {S: aws.String("123")}, 104 }}, 105 actual: func() interface{} { 106 var v interface{} 107 return &v 108 }(), 109 expected: []interface{}{"abc", "123"}, 110 }, 111 { 112 in: &dynamodb.AttributeValue{M: map[string]*dynamodb.AttributeValue{ 113 "123": {S: aws.String("abc")}, 114 "abc": {S: aws.String("123")}, 115 }}, 116 actual: func() interface{} { 117 var v interface{} 118 return &v 119 }(), 120 expected: map[string]interface{}{"123": "abc", "abc": "123"}, 121 }, 122 { 123 in: &dynamodb.AttributeValue{N: aws.String("123")}, 124 actual: func() interface{} { 125 var v interface{} 126 return &v 127 }(), 128 expected: float64(123), 129 }, 130 { 131 in: &dynamodb.AttributeValue{NS: []*string{ 132 aws.String("123"), aws.String("321"), 133 }}, 134 actual: func() interface{} { 135 var v interface{} 136 return &v 137 }(), 138 expected: []float64{123., 321.}, 139 }, 140 { 141 in: &dynamodb.AttributeValue{S: aws.String("123")}, 142 actual: func() interface{} { 143 var v interface{} 144 return &v 145 }(), 146 expected: "123", 147 }, 148 { 149 in: &dynamodb.AttributeValue{SS: []*string{ 150 aws.String("123"), aws.String("321"), 151 }}, 152 actual: func() interface{} { 153 var v interface{} 154 return &v 155 }(), 156 expected: []string{"123", "321"}, 157 }, 158 { 159 in: &dynamodb.AttributeValue{M: map[string]*dynamodb.AttributeValue{ 160 "abc": {S: aws.String("123")}, 161 "Cba": {S: aws.String("321")}, 162 }}, 163 actual: &struct{ Abc, Cba string }{}, 164 expected: struct{ Abc, Cba string }{Abc: "123", Cba: "321"}, 165 }, 166 { 167 in: &dynamodb.AttributeValue{N: aws.String("512")}, 168 actual: new(uint8), 169 err: &UnmarshalTypeError{ 170 Value: fmt.Sprintf("number overflow, 512"), 171 Type: reflect.TypeOf(uint8(0)), 172 }, 173 }, 174 // ------- 175 // Empty Values 176 // ------- 177 { 178 in: &dynamodb.AttributeValue{B: []byte{}}, 179 actual: &[]byte{}, 180 expected: []byte{}, 181 }, 182 { 183 in: &dynamodb.AttributeValue{BS: [][]byte{}}, 184 actual: &[][]byte{}, 185 expected: [][]byte{}, 186 }, 187 { 188 in: &dynamodb.AttributeValue{L: []*dynamodb.AttributeValue{}}, 189 actual: &[]interface{}{}, 190 expected: []interface{}{}, 191 }, 192 { 193 in: &dynamodb.AttributeValue{M: map[string]*dynamodb.AttributeValue{}}, 194 actual: &map[string]interface{}{}, 195 expected: map[string]interface{}{}, 196 }, 197 { 198 in: &dynamodb.AttributeValue{N: aws.String("")}, 199 actual: new(int), 200 err: fmt.Errorf("invalid syntax"), 201 }, 202 { 203 in: &dynamodb.AttributeValue{NS: []*string{}}, 204 actual: &[]*string{}, 205 expected: []*string{}, 206 }, 207 { 208 in: &dynamodb.AttributeValue{S: aws.String("")}, 209 actual: new(string), 210 expected: "", 211 }, 212 { 213 in: &dynamodb.AttributeValue{SS: []*string{}}, 214 actual: &[]*string{}, 215 expected: []*string{}, 216 }, 217 } 218 219 for i, c := range cases { 220 err := Unmarshal(c.in, c.actual) 221 assertConvertTest(t, i, c.actual, c.expected, err, c.err) 222 } 223 } 224 225 func TestInterfaceInput(t *testing.T) { 226 var v interface{} 227 expected := []interface{}{"abc", "123"} 228 err := Unmarshal(&dynamodb.AttributeValue{L: []*dynamodb.AttributeValue{ 229 {S: aws.String("abc")}, {S: aws.String("123")}, 230 }}, &v) 231 assertConvertTest(t, 0, v, expected, err, nil) 232 } 233 234 func TestUnmarshalError(t *testing.T) { 235 cases := []struct { 236 in *dynamodb.AttributeValue 237 actual, expected interface{} 238 err error 239 }{ 240 { 241 in: &dynamodb.AttributeValue{}, 242 actual: int(0), 243 expected: nil, 244 err: &InvalidUnmarshalError{Type: reflect.TypeOf(int(0))}, 245 }, 246 } 247 248 for i, c := range cases { 249 err := Unmarshal(c.in, c.actual) 250 assertConvertTest(t, i, c.actual, c.expected, err, c.err) 251 } 252 } 253 254 func TestUnmarshalListShared(t *testing.T) { 255 for i, c := range sharedListTestCases { 256 err := UnmarshalList(c.in, c.actual) 257 assertConvertTest(t, i, c.actual, c.expected, err, c.err) 258 } 259 } 260 261 func TestUnmarshalListError(t *testing.T) { 262 cases := []struct { 263 in []*dynamodb.AttributeValue 264 actual, expected interface{} 265 err error 266 }{ 267 { 268 in: []*dynamodb.AttributeValue{}, 269 actual: []interface{}{}, 270 expected: nil, 271 err: &InvalidUnmarshalError{Type: reflect.TypeOf([]interface{}{})}, 272 }, 273 } 274 275 for i, c := range cases { 276 err := UnmarshalList(c.in, c.actual) 277 assertConvertTest(t, i, c.actual, c.expected, err, c.err) 278 } 279 } 280 281 func TestUnmarshalConvertToData(t *testing.T) { 282 type T struct { 283 Int int 284 Str string 285 ByteSlice []byte 286 StrSlice []string 287 } 288 289 exp := T{ 290 Int: 42, 291 Str: "foo", 292 ByteSlice: []byte{42, 97, 83}, 293 StrSlice: []string{"cat", "dog"}, 294 } 295 av, err := ConvertToMap(exp) 296 if err != nil { 297 t.Fatalf("expect no error, got %v", err) 298 } 299 300 var act T 301 err = UnmarshalMap(av, &act) 302 assertConvertTest(t, 0, act, exp, err, nil) 303 } 304 305 func TestUnmarshalMapShared(t *testing.T) { 306 for i, c := range sharedMapTestCases { 307 err := UnmarshalMap(c.in, c.actual) 308 assertConvertTest(t, i, c.actual, c.expected, err, c.err) 309 } 310 } 311 312 func TestUnmarshalMapError(t *testing.T) { 313 cases := []struct { 314 in map[string]*dynamodb.AttributeValue 315 actual, expected interface{} 316 err error 317 }{ 318 { 319 in: map[string]*dynamodb.AttributeValue{}, 320 actual: map[string]interface{}{}, 321 expected: nil, 322 err: &InvalidUnmarshalError{Type: reflect.TypeOf(map[string]interface{}{})}, 323 }, 324 { 325 in: map[string]*dynamodb.AttributeValue{ 326 "BOOL": {BOOL: aws.Bool(true)}, 327 }, 328 actual: &map[int]interface{}{}, 329 expected: nil, 330 err: &UnmarshalTypeError{Value: "map string key", Type: reflect.TypeOf(int(0))}, 331 }, 332 } 333 334 for i, c := range cases { 335 err := UnmarshalMap(c.in, c.actual) 336 assertConvertTest(t, i, c.actual, c.expected, err, c.err) 337 } 338 } 339 340 func TestUnmarshalListOfMaps(t *testing.T) { 341 type testItem struct { 342 Value string 343 Value2 int 344 } 345 346 cases := []struct { 347 in []map[string]*dynamodb.AttributeValue 348 actual, expected interface{} 349 err error 350 }{ 351 { // Simple map conversion. 352 in: []map[string]*dynamodb.AttributeValue{ 353 { 354 "Value": &dynamodb.AttributeValue{ 355 BOOL: aws.Bool(true), 356 }, 357 }, 358 }, 359 actual: &[]map[string]interface{}{}, 360 expected: []map[string]interface{}{ 361 { 362 "Value": true, 363 }, 364 }, 365 }, 366 { // attribute to struct. 367 in: []map[string]*dynamodb.AttributeValue{ 368 { 369 "Value": &dynamodb.AttributeValue{ 370 S: aws.String("abc"), 371 }, 372 "Value2": &dynamodb.AttributeValue{ 373 N: aws.String("123"), 374 }, 375 }, 376 }, 377 actual: &[]testItem{}, 378 expected: []testItem{ 379 { 380 Value: "abc", 381 Value2: 123, 382 }, 383 }, 384 }, 385 } 386 387 for i, c := range cases { 388 err := UnmarshalListOfMaps(c.in, c.actual) 389 assertConvertTest(t, i, c.actual, c.expected, err, c.err) 390 } 391 } 392 393 type unmarshalUnmarshaler struct { 394 Value string 395 Value2 int 396 Value3 bool 397 Value4 time.Time 398 } 399 400 func (u *unmarshalUnmarshaler) UnmarshalDynamoDBAttributeValue(av *dynamodb.AttributeValue) error { 401 if av.M == nil { 402 return fmt.Errorf("expected AttributeValue to be map") 403 } 404 405 if v, ok := av.M["abc"]; !ok { 406 return fmt.Errorf("expected `abc` map key") 407 } else if v.S == nil { 408 return fmt.Errorf("expected `abc` map value string") 409 } else { 410 u.Value = *v.S 411 } 412 413 if v, ok := av.M["def"]; !ok { 414 return fmt.Errorf("expected `def` map key") 415 } else if v.N == nil { 416 return fmt.Errorf("expected `def` map value number") 417 } else { 418 n, err := strconv.ParseInt(*v.N, 10, 64) 419 if err != nil { 420 return err 421 } 422 u.Value2 = int(n) 423 } 424 425 if v, ok := av.M["ghi"]; !ok { 426 return fmt.Errorf("expected `ghi` map key") 427 } else if v.BOOL == nil { 428 return fmt.Errorf("expected `ghi` map value number") 429 } else { 430 u.Value3 = *v.BOOL 431 } 432 433 if v, ok := av.M["jkl"]; !ok { 434 return fmt.Errorf("expected `jkl` map key") 435 } else if v.S == nil { 436 return fmt.Errorf("expected `jkl` map value string") 437 } else { 438 t, err := time.Parse(time.RFC3339, *v.S) 439 if err != nil { 440 return err 441 } 442 u.Value4 = t 443 } 444 445 return nil 446 } 447 448 func TestUnmarshalUnmashaler(t *testing.T) { 449 u := &unmarshalUnmarshaler{} 450 av := &dynamodb.AttributeValue{ 451 M: map[string]*dynamodb.AttributeValue{ 452 "abc": {S: aws.String("value")}, 453 "def": {N: aws.String("123")}, 454 "ghi": {BOOL: aws.Bool(true)}, 455 "jkl": {S: aws.String("2016-05-03T17:06:26.209072Z")}, 456 }, 457 } 458 459 err := Unmarshal(av, u) 460 if err != nil { 461 t.Errorf("expect no error, got %v", err) 462 } 463 464 if e, a := "value", u.Value; e != a { 465 t.Errorf("expect %v, got %v", e, a) 466 } 467 if e, a := 123, u.Value2; e != a { 468 t.Errorf("expect %v, got %v", e, a) 469 } 470 if e, a := true, u.Value3; e != a { 471 t.Errorf("expect %v, got %v", e, a) 472 } 473 if e, a := testDate, u.Value4; e != a { 474 t.Errorf("expect %v, got %v", e, a) 475 } 476 } 477 478 func TestDecodeUseNumber(t *testing.T) { 479 u := map[string]interface{}{} 480 av := &dynamodb.AttributeValue{ 481 M: map[string]*dynamodb.AttributeValue{ 482 "abc": {S: aws.String("value")}, 483 "def": {N: aws.String("123")}, 484 "ghi": {BOOL: aws.Bool(true)}, 485 }, 486 } 487 488 decoder := NewDecoder(func(d *Decoder) { 489 d.UseNumber = true 490 }) 491 err := decoder.Decode(av, &u) 492 if err != nil { 493 t.Errorf("expect no error, got %v", err) 494 } 495 496 if e, a := "value", u["abc"]; e != a { 497 t.Errorf("expect %v, got %v", e, a) 498 } 499 n := u["def"].(Number) 500 if e, a := "123", n.String(); e != a { 501 t.Errorf("expect %v, got %v", e, a) 502 } 503 if e, a := true, u["ghi"]; e != a { 504 t.Errorf("expect %v, got %v", e, a) 505 } 506 } 507 508 func TestDecodeUseJsonNumber(t *testing.T) { 509 u := map[string]interface{}{} 510 av := &dynamodb.AttributeValue{ 511 M: map[string]*dynamodb.AttributeValue{ 512 "abc": {S: aws.String("value")}, 513 "def": {N: aws.String("123")}, 514 "ghi": {BOOL: aws.Bool(true)}, 515 }, 516 } 517 518 decoder := NewDecoder(func(d *Decoder) { 519 d.UseJsonNumber = true 520 }) 521 err := decoder.Decode(av, &u) 522 if err != nil { 523 t.Errorf("expect no error, got %v", err) 524 } 525 526 if e, a := "value", u["abc"]; e != a { 527 t.Errorf("expect %v, got %v", e, a) 528 } 529 n := u["def"].(json.Number) 530 if e, a := "123", n.String(); e != a { 531 t.Errorf("expect %v, got %v", e, a) 532 } 533 if e, a := true, u["ghi"]; e != a { 534 t.Errorf("expect %v, got %v", e, a) 535 } 536 } 537 538 func TestDecodeUseNumberNumberSet(t *testing.T) { 539 u := map[string]interface{}{} 540 av := &dynamodb.AttributeValue{ 541 M: map[string]*dynamodb.AttributeValue{ 542 "ns": { 543 NS: []*string{ 544 aws.String("123"), aws.String("321"), 545 }, 546 }, 547 }, 548 } 549 550 decoder := NewDecoder(func(d *Decoder) { 551 d.UseNumber = true 552 }) 553 err := decoder.Decode(av, &u) 554 if err != nil { 555 t.Errorf("expect no error, got %v", err) 556 } 557 558 ns := u["ns"].([]Number) 559 560 if e, a := "123", ns[0].String(); e != a { 561 t.Errorf("expect %v, got %v", e, a) 562 } 563 if e, a := "321", ns[1].String(); e != a { 564 t.Errorf("expect %v, got %v", e, a) 565 } 566 } 567 568 func TestDecodeEmbeddedPointerStruct(t *testing.T) { 569 type B struct { 570 Bint int 571 } 572 type C struct { 573 Cint int 574 } 575 type A struct { 576 Aint int 577 *B 578 *C 579 } 580 av := &dynamodb.AttributeValue{ 581 M: map[string]*dynamodb.AttributeValue{ 582 "Aint": { 583 N: aws.String("321"), 584 }, 585 "Bint": { 586 N: aws.String("123"), 587 }, 588 }, 589 } 590 decoder := NewDecoder() 591 a := A{} 592 err := decoder.Decode(av, &a) 593 if err != nil { 594 t.Errorf("expect no error, got %v", err) 595 } 596 if e, a := 321, a.Aint; e != a { 597 t.Errorf("expect %v, got %v", e, a) 598 } 599 // Embedded pointer struct can be created automatically. 600 if e, a := 123, a.Bint; e != a { 601 t.Errorf("expect %v, got %v", e, a) 602 } 603 // But not for absent fields. 604 if a.C != nil { 605 t.Errorf("expect nil, got %v", a.C) 606 } 607 } 608 609 func TestDecodeBooleanOverlay(t *testing.T) { 610 type BooleanOverlay bool 611 612 av := &dynamodb.AttributeValue{ 613 BOOL: aws.Bool(true), 614 } 615 616 decoder := NewDecoder() 617 618 var v BooleanOverlay 619 620 err := decoder.Decode(av, &v) 621 if err != nil { 622 t.Errorf("expect no error, got %v", err) 623 } 624 if e, a := BooleanOverlay(true), v; e != a { 625 t.Errorf("expect %v, got %v", e, a) 626 } 627 } 628 629 func TestDecodeUnixTime(t *testing.T) { 630 type A struct { 631 Normal time.Time 632 Tagged time.Time `dynamodbav:",unixtime"` 633 Typed UnixTime 634 } 635 636 expect := A{ 637 Normal: time.Unix(123, 0).UTC(), 638 Tagged: time.Unix(456, 0), 639 Typed: UnixTime(time.Unix(789, 0)), 640 } 641 642 input := &dynamodb.AttributeValue{ 643 M: map[string]*dynamodb.AttributeValue{ 644 "Normal": { 645 S: aws.String("1970-01-01T00:02:03Z"), 646 }, 647 "Tagged": { 648 N: aws.String("456"), 649 }, 650 "Typed": { 651 N: aws.String("789"), 652 }, 653 }, 654 } 655 actual := A{} 656 657 err := Unmarshal(input, &actual) 658 if err != nil { 659 t.Errorf("expect no error, got %v", err) 660 } 661 if e, a := expect, actual; e != a { 662 t.Errorf("expect %v, got %v", e, a) 663 } 664 } 665 666 func TestDecodeAliasedUnixTime(t *testing.T) { 667 type A struct { 668 Normal AliasedTime 669 Tagged AliasedTime `dynamodbav:",unixtime"` 670 } 671 672 expect := A{ 673 Normal: AliasedTime(time.Unix(123, 0).UTC()), 674 Tagged: AliasedTime(time.Unix(456, 0)), 675 } 676 677 input := &dynamodb.AttributeValue{ 678 M: map[string]*dynamodb.AttributeValue{ 679 "Normal": { 680 S: aws.String("1970-01-01T00:02:03Z"), 681 }, 682 "Tagged": { 683 N: aws.String("456"), 684 }, 685 }, 686 } 687 actual := A{} 688 689 err := Unmarshal(input, &actual) 690 if err != nil { 691 t.Errorf("expect no error, got %v", err) 692 } 693 if expect != actual { 694 t.Errorf("expect %v, got %v", expect, actual) 695 } 696 } 697 698 func TestDecoderFieldByIndex(t *testing.T) { 699 type ( 700 Middle struct{ Inner int } 701 Outer struct{ *Middle } 702 ) 703 var outer Outer 704 705 outerType := reflect.TypeOf(outer) 706 outerValue := reflect.ValueOf(&outer) 707 outerFields := unionStructFields(outerType, MarshalOptions{}) 708 innerField, _ := outerFields.FieldByName("Inner") 709 710 f := decoderFieldByIndex(outerValue.Elem(), innerField.Index) 711 if outer.Middle == nil { 712 t.Errorf("expected outer.Middle to be non-nil") 713 } 714 if f.Kind() != reflect.Int || f.Int() != int64(outer.Inner) { 715 t.Error("expected f to be an int with value equal to outer.Inner") 716 } 717 } 718 719 func TestDecodeAliasType(t *testing.T) { 720 type Str string 721 type Int int 722 type Uint uint 723 type TT struct { 724 A Str 725 B Int 726 C Uint 727 S Str 728 } 729 730 expect := TT{ 731 A: "12345", 732 B: 12345, 733 C: 12345, 734 S: "string", 735 } 736 m := map[string]*dynamodb.AttributeValue{ 737 "A": { 738 N: aws.String("12345"), 739 }, 740 "B": { 741 N: aws.String("12345"), 742 }, 743 "C": { 744 N: aws.String("12345"), 745 }, 746 "S": { 747 S: aws.String("string"), 748 }, 749 } 750 751 var actual TT 752 err := UnmarshalMap(m, &actual) 753 if err != nil { 754 t.Fatalf("expect no error, got %v", err) 755 } 756 757 if !reflect.DeepEqual(expect, actual) { 758 t.Errorf("expect:\n%v\nactual:\n%v", expect, actual) 759 } 760 }