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 }