1package csvutil 2 3import ( 4 "bytes" 5 "encoding" 6 "encoding/csv" 7 "encoding/json" 8 "errors" 9 "math" 10 "reflect" 11 "testing" 12) 13 14var Error = errors.New("error") 15 16var nilIface interface{} 17 18var nilPtr *TypeF 19 20var nilIfacePtr interface{} = nilPtr 21 22type embeddedMap map[string]string 23 24type Embedded14 Embedded3 25 26func (e *Embedded14) MarshalCSV() ([]byte, error) { 27 return json.Marshal(e) 28} 29 30type Embedded15 Embedded3 31 32func (e *Embedded15) MarshalText() ([]byte, error) { 33 return json.Marshal(Embedded3(*e)) 34} 35 36type CSVMarshaler struct { 37 Err error 38} 39 40func (m CSVMarshaler) MarshalCSV() ([]byte, error) { 41 if m.Err != nil { 42 return nil, m.Err 43 } 44 return []byte("csvmarshaler"), nil 45} 46 47type PtrRecCSVMarshaler int 48 49func (m *PtrRecCSVMarshaler) MarshalCSV() ([]byte, error) { 50 return []byte("ptrreccsvmarshaler"), nil 51} 52 53func (m *PtrRecCSVMarshaler) CSV() ([]byte, error) { 54 return []byte("ptrreccsvmarshaler.CSV"), nil 55} 56 57type PtrRecTextMarshaler int 58 59func (m *PtrRecTextMarshaler) MarshalText() ([]byte, error) { 60 return []byte("ptrrectextmarshaler"), nil 61} 62 63type TextMarshaler struct { 64 Err error 65} 66 67func (m TextMarshaler) MarshalText() ([]byte, error) { 68 if m.Err != nil { 69 return nil, m.Err 70 } 71 return []byte("textmarshaler"), nil 72} 73 74type CSVTextMarshaler struct { 75 CSVMarshaler 76 TextMarshaler 77} 78 79type Inline struct { 80 J1 TypeJ `csv:",inline"` 81 J2 TypeJ `csv:"prefix-,inline"` 82 String string `csv:"top-string"` 83 String2 string `csv:"STR"` 84} 85 86type Inline2 struct { 87 S string 88 A Inline3 `csv:"A,inline"` 89 B Inline3 `csv:",inline"` 90} 91 92type Inline3 struct { 93 Inline4 `csv:",inline"` 94} 95 96type Inline4 struct { 97 A string 98} 99 100type Inline5 struct { 101 A Inline2 `csv:"A,inline"` 102 B Inline2 `csv:",inline"` 103} 104 105type Inline6 struct { 106 A Inline7 `csv:",inline"` 107} 108 109type Inline7 struct { 110 A *Inline6 `csv:",inline"` 111 X int 112} 113 114type Inline8 struct { 115 F *Inline4 `csv:"A,inline"` 116 AA int 117} 118 119type TypeH struct { 120 Int int `csv:"int,omitempty"` 121 Int8 int8 `csv:"int8,omitempty"` 122 Int16 int16 `csv:"int16,omitempty"` 123 Int32 int32 `csv:"int32,omitempty"` 124 Int64 int64 `csv:"int64,omitempty"` 125 UInt uint `csv:"uint,omitempty"` 126 Uint8 uint8 `csv:"uint8,omitempty"` 127 Uint16 uint16 `csv:"uint16,omitempty"` 128 Uint32 uint32 `csv:"uint32,omitempty"` 129 Uint64 uint64 `csv:"uint64,omitempty"` 130 Float32 float32 `csv:"float32,omitempty"` 131 Float64 float64 `csv:"float64,omitempty"` 132 String string `csv:"string,omitempty"` 133 Bool bool `csv:"bool,omitempty"` 134 V interface{} `csv:"interface,omitempty"` 135} 136 137type TypeM struct { 138 *TextMarshaler `csv:"text"` 139} 140 141func TestEncoder(t *testing.T) { 142 fixtures := []struct { 143 desc string 144 in []interface{} 145 regFunc []interface{} 146 out [][]string 147 err error 148 }{ 149 { 150 desc: "test all types", 151 in: []interface{}{ 152 TypeF{ 153 Int: 1, 154 Pint: pint(2), 155 Int8: 3, 156 Pint8: pint8(4), 157 Int16: 5, 158 Pint16: pint16(6), 159 Int32: 7, 160 Pint32: pint32(8), 161 Int64: 9, 162 Pint64: pint64(10), 163 UInt: 11, 164 Puint: puint(12), 165 Uint8: 13, 166 Puint8: puint8(14), 167 Uint16: 15, 168 Puint16: puint16(16), 169 Uint32: 17, 170 Puint32: puint32(18), 171 Uint64: 19, 172 Puint64: puint64(20), 173 Float32: 21, 174 Pfloat32: pfloat32(22), 175 Float64: 23, 176 Pfloat64: pfloat64(24), 177 String: "25", 178 PString: pstring("26"), 179 Bool: true, 180 Pbool: pbool(true), 181 V: "true", 182 Pv: pinterface("1"), 183 Binary: Binary, 184 PBinary: &BinaryLarge, 185 }, 186 TypeF{}, 187 }, 188 out: [][]string{ 189 { 190 "int", "pint", "int8", "pint8", "int16", "pint16", "int32", 191 "pint32", "int64", "pint64", "uint", "puint", "uint8", "puint8", 192 "uint16", "puint16", "uint32", "puint32", "uint64", "puint64", 193 "float32", "pfloat32", "float64", "pfloat64", "string", "pstring", 194 "bool", "pbool", "interface", "pinterface", "binary", "pbinary", 195 }, 196 {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", 197 "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", 198 "22", "23", "24", "25", "26", "true", "true", "true", "1", 199 EncodedBinary, EncodedBinaryLarge, 200 }, 201 {"0", "", "0", "", "0", "", "0", "", "0", "", "0", "", 202 "0", "", "0", "", "0", "", "0", "", "0", "", "0", "", "", "", 203 "false", "", "", "", "", "", 204 }, 205 }, 206 }, 207 { 208 desc: "tags and unexported fields", 209 in: []interface{}{ 210 TypeG{ 211 String: "string", 212 Int: 1, 213 Float: 3.14, 214 unexported1: 100, 215 unexported2: 200, 216 }, 217 }, 218 out: [][]string{ 219 {"String", "Int"}, 220 {"string", "1"}, 221 }, 222 }, 223 { 224 desc: "omitempty tags", 225 in: []interface{}{ 226 TypeH{}, 227 }, 228 out: [][]string{ 229 {"int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", 230 "uint32", "uint64", "float32", "float64", "string", "bool", "interface", 231 }, 232 {"", "", "", "", "", "", "", "", "", "", "", "", "", "", ""}, 233 }, 234 }, 235 { 236 desc: "omitempty tags on pointers - non nil default values", 237 in: []interface{}{ 238 struct { 239 Pint *int `csv:",omitempty"` 240 PPint **int `csv:",omitempty"` 241 PPint2 **int `csv:",omitempty"` 242 PString *string `csv:",omitempty"` 243 PBool *bool `csv:",omitempty"` 244 Iint *interface{} `csv:",omitempty"` 245 }{ 246 pint(0), 247 ppint(0), 248 new(*int), 249 pstring(""), 250 pbool(false), 251 pinterface(0), 252 }, 253 }, 254 out: [][]string{ 255 {"Pint", "PPint", "PPint2", "PString", "PBool", "Iint"}, 256 {"0", "0", "", "", "false", "0"}, 257 }, 258 }, 259 { 260 desc: "omitempty tags on pointers - nil ptrs", 261 in: []interface{}{ 262 struct { 263 Pint *int `csv:",omitempty"` 264 PPint **int `csv:",omitempty"` 265 PString *string `csv:",omitempty"` 266 PBool *bool `csv:",omitempty"` 267 Iint *interface{} `csv:",omitempty"` 268 }{}, 269 }, 270 out: [][]string{ 271 {"Pint", "PPint", "PString", "PBool", "Iint"}, 272 {"", "", "", "", ""}, 273 }, 274 }, 275 { 276 desc: "omitempty tags on interfaces - non nil default values", 277 in: []interface{}{ 278 struct { 279 Iint interface{} `csv:",omitempty"` 280 IPint interface{} `csv:",omitempty"` 281 }{ 282 0, 283 pint(0), 284 }, 285 struct { 286 Iint interface{} `csv:",omitempty"` 287 IPint interface{} `csv:",omitempty"` 288 }{ 289 1, 290 pint(1), 291 }, 292 }, 293 out: [][]string{ 294 {"Iint", "IPint"}, 295 {"0", "0"}, 296 {"1", "1"}, 297 }, 298 }, 299 { 300 desc: "omitempty tags on interfaces - nil", 301 in: []interface{}{ 302 struct { 303 Iint interface{} `csv:",omitempty"` 304 IPint interface{} `csv:",omitempty"` 305 }{ 306 nil, 307 nil, 308 }, 309 struct { 310 Iint interface{} `csv:",omitempty"` 311 IPint interface{} `csv:",omitempty"` 312 }{ 313 (*int)(nil), 314 pinterface((*int)(nil)), 315 }, 316 }, 317 out: [][]string{ 318 {"Iint", "IPint"}, 319 {"", ""}, 320 {"", ""}, 321 }, 322 }, 323 { 324 desc: "embedded types #1", 325 in: []interface{}{ 326 TypeA{ 327 Embedded1: Embedded1{ 328 String: "string1", 329 Float: 1, 330 }, 331 String: "string", 332 Embedded2: Embedded2{ 333 Float: 2, 334 Bool: true, 335 }, 336 Int: 10, 337 }, 338 }, 339 out: [][]string{ 340 {"string", "bool", "int"}, 341 {"string", "true", "10"}, 342 }, 343 }, 344 { 345 desc: "embedded non struct tagged types", 346 in: []interface{}{ 347 TypeB{ 348 Embedded3: Embedded3{"key": "val"}, 349 String: "string1", 350 }, 351 }, 352 out: [][]string{ 353 {"json", "string"}, 354 {`{"key":"val"}`, "string1"}, 355 }, 356 }, 357 { 358 desc: "embedded non struct tagged types with pointer receiver MarshalCSV", 359 in: []interface{}{ 360 &struct { 361 Embedded14 `csv:"json"` 362 A Embedded14 `csv:"json2"` 363 }{ 364 Embedded14: Embedded14{"key": "val"}, 365 A: Embedded14{"key1": "val1"}, 366 }, 367 struct { 368 *Embedded14 `csv:"json"` 369 A *Embedded14 `csv:"json2"` 370 }{ 371 Embedded14: &Embedded14{"key": "val"}, 372 A: &Embedded14{"key1": "val1"}, 373 }, 374 }, 375 out: [][]string{ 376 {"json", "json2"}, 377 {`{"key":"val"}`, `{"key1":"val1"}`}, 378 {`{"key":"val"}`, `{"key1":"val1"}`}, 379 }, 380 }, 381 { 382 desc: "embedded non struct tagged types with pointer receiver MarshalText", 383 in: []interface{}{ 384 &struct { 385 Embedded15 `csv:"json"` 386 A Embedded15 `csv:"json2"` 387 }{ 388 Embedded15: Embedded15{"key": "val"}, 389 A: Embedded15{"key1": "val1"}, 390 }, 391 struct { 392 *Embedded15 `csv:"json"` 393 A *Embedded15 `csv:"json2"` 394 }{ 395 Embedded15: &Embedded15{"key": "val"}, 396 A: &Embedded15{"key1": "val1"}, 397 }, 398 }, 399 out: [][]string{ 400 {"json", "json2"}, 401 {`{"key":"val"}`, `{"key1":"val1"}`}, 402 {`{"key":"val"}`, `{"key1":"val1"}`}, 403 }, 404 }, 405 { 406 desc: "embedded pointer types", 407 in: []interface{}{ 408 TypeC{ 409 Embedded1: &Embedded1{ 410 String: "string2", 411 Float: 1, 412 }, 413 String: "string1", 414 }, 415 }, 416 out: [][]string{ 417 {"float", "string"}, 418 {`1`, "string1"}, 419 }, 420 }, 421 { 422 desc: "embedded pointer types with nil values", 423 in: []interface{}{ 424 TypeC{ 425 Embedded1: nil, 426 String: "string1", 427 }, 428 }, 429 out: [][]string{ 430 {"float", "string"}, 431 {``, "string1"}, 432 }, 433 }, 434 { 435 desc: "embedded non struct tagged pointer types", 436 in: []interface{}{ 437 TypeD{ 438 Embedded3: &Embedded3{"key": "val"}, 439 String: "string1", 440 }, 441 }, 442 out: [][]string{ 443 {"json", "string"}, 444 {`{"key":"val"}`, "string1"}, 445 }, 446 }, 447 { 448 desc: "embedded non struct tagged pointer types with nil value - textmarshaler", 449 in: []interface{}{ 450 TypeM{ 451 TextMarshaler: nil, 452 }, 453 }, 454 out: [][]string{ 455 {"text"}, 456 {""}, 457 }, 458 }, 459 { 460 desc: "embedded non struct tagged pointer types with nil value - csvmarshaler", 461 in: []interface{}{ 462 TypeD{ 463 Embedded3: nil, 464 String: "string1", 465 }, 466 }, 467 out: [][]string{ 468 {"json", "string"}, 469 {"", "string1"}, 470 }, 471 }, 472 { 473 desc: "tagged fields priority", 474 in: []interface{}{ 475 TagPriority{Foo: 1, Bar: 2}, 476 }, 477 out: [][]string{ 478 {"Foo"}, 479 {"2"}, 480 }, 481 }, 482 { 483 desc: "conflicting embedded fields #1", 484 in: []interface{}{ 485 Embedded5{ 486 Embedded6: Embedded6{X: 60}, 487 Embedded7: Embedded7{X: 70}, 488 Embedded8: Embedded8{ 489 Embedded9: Embedded9{ 490 X: 90, 491 Y: 91, 492 }, 493 }, 494 }, 495 }, 496 out: [][]string{ 497 {"Y"}, 498 {"91"}, 499 }, 500 }, 501 { 502 desc: "conflicting embedded fields #2", 503 in: []interface{}{ 504 Embedded10{ 505 Embedded11: Embedded11{ 506 Embedded6: Embedded6{X: 60}, 507 }, 508 Embedded12: Embedded12{ 509 Embedded6: Embedded6{X: 60}, 510 }, 511 Embedded13: Embedded13{ 512 Embedded8: Embedded8{ 513 Embedded9: Embedded9{ 514 X: 90, 515 Y: 91, 516 }, 517 }, 518 }, 519 }, 520 }, 521 out: [][]string{ 522 {"Y"}, 523 {"91"}, 524 }, 525 }, 526 { 527 desc: "double pointer", 528 in: []interface{}{ 529 TypeE{ 530 String: &PString, 531 Int: &Int, 532 }, 533 }, 534 out: [][]string{ 535 {"string", "int"}, 536 {"string", "10"}, 537 }, 538 }, 539 { 540 desc: "nil double pointer", 541 in: []interface{}{ 542 TypeE{}, 543 }, 544 out: [][]string{ 545 {"string", "int"}, 546 {"", ""}, 547 }, 548 }, 549 { 550 desc: "unexported non-struct embedded", 551 in: []interface{}{ 552 struct { 553 A int 554 embeddedMap 555 }{1, make(embeddedMap)}, 556 }, 557 out: [][]string{ 558 {"A"}, 559 {"1"}, 560 }, 561 }, 562 { 563 desc: "cyclic reference", 564 in: []interface{}{ 565 A{ 566 B: B{Y: 2, A: &A{}}, 567 X: 1, 568 }, 569 }, 570 out: [][]string{ 571 {"Y", "X"}, 572 {"2", "1"}, 573 }, 574 }, 575 { 576 desc: "ptr receiver csv marshaler", 577 in: []interface{}{ 578 &struct { 579 A PtrRecCSVMarshaler 580 }{}, 581 struct { 582 A PtrRecCSVMarshaler 583 }{}, 584 struct { 585 A *PtrRecCSVMarshaler 586 }{new(PtrRecCSVMarshaler)}, 587 &struct { 588 A *PtrRecCSVMarshaler 589 }{new(PtrRecCSVMarshaler)}, 590 &struct { 591 A *PtrRecCSVMarshaler 592 }{}, 593 }, 594 out: [][]string{ 595 {"A"}, 596 {"ptrreccsvmarshaler"}, 597 {"0"}, 598 {"ptrreccsvmarshaler"}, 599 {"ptrreccsvmarshaler"}, 600 {""}, 601 }, 602 }, 603 { 604 desc: "ptr receiver text marshaler", 605 in: []interface{}{ 606 &struct { 607 A PtrRecTextMarshaler 608 }{}, 609 struct { 610 A PtrRecTextMarshaler 611 }{}, 612 struct { 613 A *PtrRecTextMarshaler 614 }{new(PtrRecTextMarshaler)}, 615 &struct { 616 A *PtrRecTextMarshaler 617 }{new(PtrRecTextMarshaler)}, 618 &struct { 619 A *PtrRecTextMarshaler 620 }{}, 621 }, 622 out: [][]string{ 623 {"A"}, 624 {"ptrrectextmarshaler"}, 625 {"0"}, 626 {"ptrrectextmarshaler"}, 627 {"ptrrectextmarshaler"}, 628 {""}, 629 }, 630 }, 631 { 632 desc: "text marshaler", 633 in: []interface{}{ 634 struct { 635 A CSVMarshaler 636 }{}, 637 struct { 638 A TextMarshaler 639 }{}, 640 struct { 641 A struct { 642 TextMarshaler 643 CSVMarshaler 644 } 645 }{}, 646 }, 647 out: [][]string{ 648 {"A"}, 649 {"csvmarshaler"}, 650 {"textmarshaler"}, 651 {"csvmarshaler"}, 652 }, 653 }, 654 { 655 desc: "primitive type alias implementing Marshaler", 656 in: []interface{}{ 657 EnumType{Enum: EnumFirst}, 658 EnumType{Enum: EnumSecond}, 659 }, 660 out: [][]string{ 661 {"enum"}, 662 {"first"}, 663 {"second"}, 664 }, 665 }, 666 { 667 desc: "aliased type", 668 in: []interface{}{ 669 struct{ Float float64 }{3.14}, 670 }, 671 out: [][]string{ 672 {"Float"}, 673 {"3.14"}, 674 }, 675 }, 676 { 677 desc: "embedded tagged marshalers", 678 in: []interface{}{ 679 struct { 680 CSVMarshaler `csv:"csv"` 681 TextMarshaler `csv:"text"` 682 }{}, 683 }, 684 out: [][]string{ 685 {"csv", "text"}, 686 {"csvmarshaler", "textmarshaler"}, 687 }, 688 }, 689 { 690 desc: "embedded pointer tagged marshalers", 691 in: []interface{}{ 692 struct { 693 *CSVMarshaler `csv:"csv"` 694 *TextMarshaler `csv:"text"` 695 }{&CSVMarshaler{}, &TextMarshaler{}}, 696 }, 697 out: [][]string{ 698 {"csv", "text"}, 699 {"csvmarshaler", "textmarshaler"}, 700 }, 701 }, 702 { 703 desc: "inline fields", 704 in: []interface{}{ 705 Inline{ 706 J1: TypeJ{ 707 String: "j1", 708 Int: "1", 709 Float: "1", 710 Embedded16: Embedded16{Bool: true, Uint8: 1}, 711 }, 712 J2: TypeJ{ 713 String: "j2", 714 Int: "2", 715 Float: "2", 716 Embedded16: Embedded16{Bool: true, Uint8: 2}, 717 }, 718 String: "top-level-str", 719 String2: "STR", 720 }, 721 }, 722 out: [][]string{ 723 {"int", "Bool", "Uint8", "float", "prefix-STR", "prefix-int", "prefix-Bool", "prefix-Uint8", "prefix-float", "top-string", "STR"}, 724 {"1", "true", "1", "1", "j2", "2", "true", "2", "2", "top-level-str", "STR"}, 725 }, 726 }, 727 { 728 desc: "inline chain", 729 in: []interface{}{ 730 Inline5{ 731 A: Inline2{ 732 S: "1", 733 A: Inline3{ 734 Inline4: Inline4{A: "11"}, 735 }, 736 B: Inline3{ 737 Inline4: Inline4{A: "12"}, 738 }, 739 }, 740 B: Inline2{ 741 S: "2", 742 A: Inline3{ 743 Inline4: Inline4{A: "21"}, 744 }, 745 B: Inline3{ 746 Inline4: Inline4{A: "22"}, 747 }, 748 }, 749 }, 750 }, 751 out: [][]string{ 752 {"AS", "AAA", "S", "A"}, 753 {"1", "11", "2", "22"}, 754 }, 755 }, 756 { 757 desc: "cyclic inline - no prefix", 758 in: []interface{}{ 759 Inline6{ 760 A: Inline7{ 761 A: &Inline6{A: Inline7{ 762 A: &Inline6{}, 763 X: 10, 764 }}, 765 X: 1, 766 }, 767 }, 768 }, 769 out: [][]string{ 770 {"X"}, 771 {"1"}, 772 }, 773 }, 774 { 775 desc: "embedded with inline tag", 776 in: []interface{}{ 777 struct { 778 Inline7 `csv:"A,inline"` 779 }{ 780 Inline7: Inline7{ 781 A: &Inline6{A: Inline7{ 782 A: &Inline6{}, 783 X: 10, 784 }}, 785 X: 1, 786 }, 787 }, 788 }, 789 out: [][]string{ 790 {"AX"}, 791 {"1"}, 792 }, 793 }, 794 { 795 desc: "embedded with empty inline tag", 796 in: []interface{}{ 797 struct { 798 Inline7 `csv:",inline"` 799 }{ 800 Inline7: Inline7{ 801 A: &Inline6{A: Inline7{ 802 A: &Inline6{}, 803 X: 10, 804 }}, 805 X: 1, 806 }, 807 }, 808 }, 809 out: [][]string{ 810 {"X"}, 811 {"1"}, 812 }, 813 }, 814 { 815 desc: "embedded with ptr inline tag", 816 in: []interface{}{ 817 struct { 818 *Inline7 `csv:"A,inline"` 819 }{ 820 Inline7: &Inline7{ 821 A: &Inline6{A: Inline7{ 822 A: &Inline6{}, 823 X: 10, 824 }}, 825 X: 1, 826 }, 827 }, 828 }, 829 out: [][]string{ 830 {"AX"}, 831 {"1"}, 832 }, 833 }, 834 { 835 desc: "inline visibility rules - top field first", 836 in: []interface{}{ 837 struct { 838 AA string 839 F Inline4 `csv:"A,inline"` 840 }{ 841 AA: "1", 842 F: Inline4{A: "10"}, 843 }, 844 }, 845 out: [][]string{ 846 {"AA"}, 847 {"1"}, 848 }, 849 }, 850 { 851 desc: "inline visibility rules - top field last", 852 in: []interface{}{ 853 Inline8{ 854 F: &Inline4{A: "10"}, 855 AA: 1, 856 }, 857 }, 858 out: [][]string{ 859 {"AA"}, 860 {"1"}, 861 }, 862 }, 863 { 864 desc: "ignore inline tag on non struct", 865 in: []interface{}{ 866 struct { 867 X int `csv:",inline"` 868 Y int `csv:"y,inline"` 869 }{ 870 X: 1, 871 Y: 2, 872 }, 873 }, 874 out: [][]string{ 875 {"X", "y"}, 876 {"1", "2"}, 877 }, 878 }, 879 { 880 desc: "registered func - non ptr elem", 881 in: []interface{}{ 882 struct { 883 Int int 884 Pint *int 885 Iface interface{} 886 Piface *interface{} 887 }{ 888 Pint: pint(0), 889 Iface: 34, 890 Piface: pinterface(34), 891 }, 892 }, 893 regFunc: []interface{}{ 894 func(int) ([]byte, error) { return []byte("int"), nil }, 895 }, 896 out: [][]string{ 897 {"Int", "Pint", "Iface", "Piface"}, 898 {"int", "int", "int", "int"}, 899 }, 900 }, 901 { 902 desc: "registered func - ptr elem", 903 in: []interface{}{ 904 &struct { 905 Int int 906 Pint *int 907 Iface interface{} 908 Piface *interface{} 909 }{ 910 Pint: pint(0), 911 Iface: 34, 912 Piface: pinterface(34), 913 }, 914 }, 915 regFunc: []interface{}{ 916 func(int) ([]byte, error) { return []byte("int"), nil }, 917 }, 918 out: [][]string{ 919 {"Int", "Pint", "Iface", "Piface"}, 920 {"int", "int", "int", "int"}, 921 }, 922 }, 923 { 924 desc: "registered func - ptr type - non ptr elem", 925 in: []interface{}{ 926 struct { 927 Int int 928 Pint *int 929 Iface interface{} 930 Piface *interface{} 931 }{ 932 Pint: pint(0), 933 Iface: 34, 934 Piface: pinterface(pint(34)), 935 }, 936 }, 937 regFunc: []interface{}{ 938 func(*int) ([]byte, error) { return []byte("int"), nil }, 939 }, 940 out: [][]string{ 941 {"Int", "Pint", "Iface", "Piface"}, 942 {"0", "int", "34", "int"}, 943 }, 944 }, 945 { 946 desc: "registered func - ptr type - ptr elem", 947 in: []interface{}{ 948 &struct { 949 Int int 950 Pint *int 951 Iface interface{} 952 Piface *interface{} 953 }{ 954 Pint: pint(0), 955 Iface: 34, 956 Piface: pinterface(pint(34)), 957 }, 958 }, 959 regFunc: []interface{}{ 960 func(*int) ([]byte, error) { return []byte("int"), nil }, 961 }, 962 out: [][]string{ 963 {"Int", "Pint", "Iface", "Piface"}, 964 {"int", "int", "34", "int"}, 965 }, 966 }, 967 { 968 desc: "registered func - mixed types - non ptr elem", 969 in: []interface{}{ 970 struct { 971 Int int 972 Pint *int 973 Iface interface{} 974 Piface *interface{} 975 }{ 976 Pint: pint(0), 977 Iface: 34, 978 Piface: pinterface(pint(34)), 979 }, 980 }, 981 regFunc: []interface{}{ 982 func(int) ([]byte, error) { return []byte("int"), nil }, 983 func(*int) ([]byte, error) { return []byte("*int"), nil }, 984 }, 985 out: [][]string{ 986 {"Int", "Pint", "Iface", "Piface"}, 987 {"int", "*int", "int", "*int"}, 988 }, 989 }, 990 { 991 desc: "registered func - mixed types - ptr elem", 992 in: []interface{}{ 993 &struct { 994 Int int 995 Pint *int 996 Iface interface{} 997 Piface *interface{} 998 }{ 999 Pint: pint(0), 1000 Iface: 34, 1001 Piface: pinterface(pint(34)), 1002 }, 1003 }, 1004 regFunc: []interface{}{ 1005 func(int) ([]byte, error) { return []byte("int"), nil }, 1006 func(*int) ([]byte, error) { return []byte("*int"), nil }, 1007 }, 1008 out: [][]string{ 1009 {"Int", "Pint", "Iface", "Piface"}, 1010 {"int", "*int", "int", "*int"}, 1011 }, 1012 }, 1013 { 1014 desc: "registered func - interfaces", 1015 in: []interface{}{ 1016 &struct { 1017 CSVMarshaler Marshaler 1018 Marshaler CSVMarshaler 1019 PMarshaler *CSVMarshaler 1020 CSVTextMarshaler CSVTextMarshaler 1021 PCSVTextMarshaler *CSVTextMarshaler 1022 PtrRecCSVMarshaler PtrRecCSVMarshaler 1023 PtrRecTextMarshaler PtrRecTextMarshaler 1024 }{ 1025 PMarshaler: &CSVMarshaler{}, 1026 PCSVTextMarshaler: &CSVTextMarshaler{}, 1027 }, 1028 }, 1029 regFunc: []interface{}{ 1030 func(Marshaler) ([]byte, error) { return []byte("registered.marshaler"), nil }, 1031 func(encoding.TextMarshaler) ([]byte, error) { return []byte("registered.textmarshaler"), nil }, 1032 }, 1033 out: [][]string{ 1034 {"CSVMarshaler", "Marshaler", "PMarshaler", "CSVTextMarshaler", "PCSVTextMarshaler", "PtrRecCSVMarshaler", "PtrRecTextMarshaler"}, 1035 {"registered.marshaler", "registered.marshaler", "registered.marshaler", "registered.marshaler", "registered.marshaler", "registered.marshaler", "registered.textmarshaler"}, 1036 }, 1037 }, 1038 { 1039 desc: "registered func - interface order", 1040 in: []interface{}{ 1041 &struct { 1042 CSVTextMarshaler CSVTextMarshaler 1043 PCSVTextMarshaler *CSVTextMarshaler 1044 }{ 1045 PCSVTextMarshaler: &CSVTextMarshaler{}, 1046 }, 1047 }, 1048 regFunc: []interface{}{ 1049 func(encoding.TextMarshaler) ([]byte, error) { return []byte("registered.textmarshaler"), nil }, 1050 func(Marshaler) ([]byte, error) { return []byte("registered.marshaler"), nil }, 1051 }, 1052 out: [][]string{ 1053 {"CSVTextMarshaler", "PCSVTextMarshaler"}, 1054 {"registered.textmarshaler", "registered.textmarshaler"}, 1055 }, 1056 }, 1057 { 1058 desc: "registered func - method", 1059 in: []interface{}{ 1060 &struct { 1061 PtrRecCSVMarshaler PtrRecCSVMarshaler 1062 }{}, 1063 struct { 1064 PtrRecCSVMarshaler PtrRecCSVMarshaler 1065 }{}, 1066 }, 1067 regFunc: []interface{}{ 1068 (*PtrRecCSVMarshaler).CSV, 1069 }, 1070 out: [][]string{ 1071 {"PtrRecCSVMarshaler"}, 1072 {"ptrreccsvmarshaler.CSV"}, 1073 {"0"}, 1074 }, 1075 }, 1076 { 1077 desc: "registered func - fallback error", 1078 in: []interface{}{ 1079 struct { 1080 Embedded14 1081 }{}, 1082 }, 1083 regFunc: []interface{}{ 1084 (*Embedded14).MarshalCSV, 1085 }, 1086 err: &UnsupportedTypeError{ 1087 Type: reflect.TypeOf(Embedded14{}), 1088 }, 1089 }, 1090 { 1091 desc: "registered interface func - returning error", 1092 in: []interface{}{ 1093 &struct { 1094 Embedded14 Embedded14 1095 }{}, 1096 }, 1097 regFunc: []interface{}{ 1098 func(Marshaler) ([]byte, error) { return nil, Error }, 1099 }, 1100 err: Error, 1101 }, 1102 { 1103 desc: "registered func - returning error", 1104 in: []interface{}{ 1105 &struct { 1106 A InvalidType 1107 }{}, 1108 }, 1109 regFunc: []interface{}{ 1110 func(*InvalidType) ([]byte, error) { return nil, Error }, 1111 }, 1112 err: Error, 1113 }, 1114 { 1115 desc: "registered func - fallback error on interface", 1116 in: []interface{}{ 1117 struct { 1118 Embedded14 1119 }{}, 1120 }, 1121 regFunc: []interface{}{ 1122 func(m Marshaler) ([]byte, error) { return nil, nil }, 1123 }, 1124 err: &UnsupportedTypeError{ 1125 Type: reflect.TypeOf(Embedded14{}), 1126 }, 1127 }, 1128 { 1129 desc: "marshaler fallback error", 1130 in: []interface{}{ 1131 struct { 1132 Embedded14 1133 }{}, 1134 }, 1135 err: &UnsupportedTypeError{ 1136 Type: reflect.TypeOf(Embedded14{}), 1137 }, 1138 }, 1139 { 1140 desc: "encode different types", 1141 // This doesnt mean the output csv is valid. Generally this is an invalid 1142 // use. However, we need to make sure that the encoder is doing what it is 1143 // asked to... correctly. 1144 in: []interface{}{ 1145 struct { 1146 A int 1147 }{}, 1148 struct { 1149 A int 1150 B string 1151 }{}, 1152 struct { 1153 A int 1154 }{}, 1155 struct{}{}, 1156 }, 1157 out: [][]string{ 1158 {"A"}, 1159 {"0"}, 1160 {"0", ""}, 1161 {"0"}, 1162 {}, 1163 }, 1164 }, 1165 { 1166 desc: "encode interface values", 1167 in: []interface{}{ 1168 struct { 1169 V interface{} 1170 }{1}, 1171 struct { 1172 V interface{} 1173 }{pint(10)}, 1174 struct { 1175 V interface{} 1176 }{ppint(100)}, 1177 struct { 1178 V interface{} 1179 }{pppint(1000)}, 1180 struct { 1181 V *interface{} 1182 }{pinterface(ppint(10000))}, 1183 struct { 1184 V *interface{} 1185 }{func() *interface{} { 1186 var v interface{} = pppint(100000) 1187 var vv interface{} = v 1188 return &vv 1189 }()}, 1190 struct { 1191 V interface{} 1192 }{func() interface{} { 1193 var v interface{} = &CSVMarshaler{} 1194 var vv interface{} = v 1195 return &vv 1196 }()}, 1197 struct { 1198 V interface{} 1199 }{func() interface{} { 1200 var v interface{} = CSVMarshaler{} 1201 var vv interface{} = v 1202 return &vv 1203 }()}, 1204 struct { 1205 V interface{} 1206 }{func() interface{} { 1207 var v interface{} = &CSVMarshaler{} 1208 var vv interface{} = v 1209 return vv 1210 }()}, 1211 struct { 1212 V interface{} 1213 }{ 1214 V: func() interface{} { 1215 return PtrRecCSVMarshaler(5) 1216 }(), 1217 }, 1218 struct { 1219 V interface{} 1220 }{ 1221 V: func() interface{} { 1222 m := PtrRecCSVMarshaler(5) 1223 return &m 1224 }(), 1225 }, 1226 struct { 1227 V interface{} 1228 }{func() interface{} { 1229 var v interface{} 1230 var vv interface{} = v 1231 return &vv 1232 }()}, 1233 }, 1234 out: [][]string{ 1235 {"V"}, 1236 {"1"}, 1237 {"10"}, 1238 {"100"}, 1239 {"1000"}, 1240 {"10000"}, 1241 {"100000"}, 1242 {"csvmarshaler"}, 1243 {"csvmarshaler"}, 1244 {"csvmarshaler"}, 1245 {"5"}, 1246 {"ptrreccsvmarshaler"}, 1247 {""}, 1248 }, 1249 }, 1250 { 1251 desc: "encode NaN", 1252 in: []interface{}{ 1253 struct { 1254 Float float64 1255 }{math.NaN()}, 1256 }, 1257 out: [][]string{ 1258 {"Float"}, 1259 {"NaN"}, 1260 }, 1261 }, 1262 { 1263 desc: "encode NaN with aliased type", 1264 in: []interface{}{ 1265 struct { 1266 Float Float 1267 }{Float(math.NaN())}, 1268 }, 1269 out: [][]string{ 1270 {"Float"}, 1271 {"NaN"}, 1272 }, 1273 }, 1274 { 1275 desc: "empty struct", 1276 in: []interface{}{ 1277 struct{}{}, 1278 }, 1279 out: [][]string{{}, {}}, 1280 }, 1281 { 1282 desc: "value wrapped in interfaces and pointers", 1283 in: []interface{}{ 1284 func() (v interface{}) { v = &struct{ A int }{5}; return v }(), 1285 }, 1286 out: [][]string{{"A"}, {"5"}}, 1287 }, 1288 { 1289 desc: "csv marshaler error", 1290 in: []interface{}{ 1291 struct { 1292 A CSVMarshaler 1293 }{ 1294 A: CSVMarshaler{Err: Error}, 1295 }, 1296 }, 1297 err: &MarshalerError{Type: reflect.TypeOf(CSVMarshaler{}), MarshalerType: "MarshalCSV", Err: Error}, 1298 }, 1299 { 1300 desc: "csv marshaler error as registered error", 1301 in: []interface{}{ 1302 struct { 1303 A CSVMarshaler 1304 }{ 1305 A: CSVMarshaler{Err: Error}, 1306 }, 1307 }, 1308 regFunc: []interface{}{ 1309 CSVMarshaler.MarshalCSV, 1310 }, 1311 err: Error, 1312 }, 1313 { 1314 desc: "text marshaler error", 1315 in: []interface{}{ 1316 struct { 1317 A TextMarshaler 1318 }{ 1319 A: TextMarshaler{Err: Error}, 1320 }, 1321 }, 1322 err: &MarshalerError{Type: reflect.TypeOf(TextMarshaler{}), MarshalerType: "MarshalText", Err: Error}, 1323 }, 1324 { 1325 desc: "text marshaler fallback error - ptr reciever", 1326 in: []interface{}{ 1327 struct { 1328 A Embedded15 1329 }{}, 1330 }, 1331 err: &UnsupportedTypeError{Type: reflect.TypeOf(Embedded15{})}, 1332 }, 1333 { 1334 desc: "text marshaler error as registered func", 1335 in: []interface{}{ 1336 struct { 1337 A TextMarshaler 1338 }{ 1339 A: TextMarshaler{Err: Error}, 1340 }, 1341 }, 1342 regFunc: []interface{}{ 1343 TextMarshaler.MarshalText, 1344 }, 1345 err: Error, 1346 }, 1347 { 1348 desc: "unsupported type", 1349 in: []interface{}{ 1350 InvalidType{}, 1351 }, 1352 err: &UnsupportedTypeError{ 1353 Type: reflect.TypeOf(struct{}{}), 1354 }, 1355 }, 1356 { 1357 desc: "unsupported double pointer type", 1358 in: []interface{}{ 1359 struct { 1360 A **struct{} 1361 }{}, 1362 }, 1363 err: &UnsupportedTypeError{ 1364 Type: reflect.TypeOf(struct{}{}), 1365 }, 1366 }, 1367 { 1368 desc: "unsupported interface type", 1369 in: []interface{}{ 1370 TypeF{V: TypeA{}}, 1371 }, 1372 err: &UnsupportedTypeError{ 1373 Type: reflect.TypeOf(TypeA{}), 1374 }, 1375 }, 1376 { 1377 desc: "encode not a struct", 1378 in: []interface{}{int(1)}, 1379 err: &InvalidEncodeError{ 1380 Type: reflect.TypeOf(int(1)), 1381 }, 1382 }, 1383 { 1384 desc: "encode nil interface", 1385 in: []interface{}{nilIface}, 1386 err: &InvalidEncodeError{ 1387 Type: reflect.TypeOf(nilIface), 1388 }, 1389 }, 1390 { 1391 desc: "encode nil ptr", 1392 in: []interface{}{nilPtr}, 1393 err: &InvalidEncodeError{}, 1394 }, 1395 { 1396 desc: "encode nil interface pointer", 1397 in: []interface{}{nilIfacePtr}, 1398 err: &InvalidEncodeError{}, 1399 }, 1400 } 1401 1402 for _, f := range fixtures { 1403 t.Run(f.desc, func(t *testing.T) { 1404 var buf bytes.Buffer 1405 w := csv.NewWriter(&buf) 1406 enc := NewEncoder(w) 1407 1408 for _, f := range f.regFunc { 1409 enc.Register(f) 1410 } 1411 1412 for _, v := range f.in { 1413 err := enc.Encode(v) 1414 if f.err != nil { 1415 if !reflect.DeepEqual(f.err, err) { 1416 t.Errorf("want err=%v; got %v", f.err, err) 1417 } 1418 return 1419 } else if err != nil { 1420 t.Errorf("want err=nil; got %v", err) 1421 } 1422 } 1423 w.Flush() 1424 if err := w.Error(); err != nil { 1425 t.Errorf("want err=nil; got %v", err) 1426 } 1427 1428 var out bytes.Buffer 1429 if err := csv.NewWriter(&out).WriteAll(f.out); err != nil { 1430 t.Errorf("want err=nil; got %v", err) 1431 } 1432 1433 if buf.String() != out.String() { 1434 t.Errorf("want=%s; got %s", out.String(), buf.String()) 1435 } 1436 }) 1437 } 1438 1439 t.Run("test decoder tags", func(t *testing.T) { 1440 type Test struct { 1441 A int `custom:"1"` 1442 B string `custom:"2"` 1443 C float64 `custom:"-"` 1444 } 1445 1446 test := &Test{ 1447 A: 1, 1448 B: "b", 1449 C: 2.5, 1450 } 1451 1452 var bufs [4]bytes.Buffer 1453 for i := 0; i < 4; i += 2 { 1454 encode(t, &bufs[i], test, "") 1455 encode(t, &bufs[i+1], test, "custom") 1456 } 1457 1458 if b1, b2 := bufs[0].String(), bufs[2].String(); b1 != b2 { 1459 t.Errorf("buffers are not equal: %s vs %s", b1, b2) 1460 } 1461 if b1, b2 := bufs[1].String(), bufs[3].String(); b1 != b2 { 1462 t.Errorf("buffers are not equal: %s vs %s", b1, b2) 1463 } 1464 1465 expected1 := [][]string{ 1466 {"A", "B", "C"}, 1467 {"1", "b", "2.5"}, 1468 } 1469 expected2 := [][]string{ 1470 {"1", "2"}, 1471 {"1", "b"}, 1472 } 1473 1474 if b1, b2 := bufs[0].String(), encodeCSV(t, expected1); b1 != b2 { 1475 t.Errorf("want buf=%s; got %s", b2, b1) 1476 } 1477 if b1, b2 := bufs[1].String(), encodeCSV(t, expected2); b1 != b2 { 1478 t.Errorf("want buf=%s; got %s", b2, b1) 1479 } 1480 }) 1481 1482 t.Run("error messages", func(t *testing.T) { 1483 fixtures := []struct { 1484 desc string 1485 expected string 1486 v interface{} 1487 }{ 1488 { 1489 desc: "invalid encode error message", 1490 expected: "csvutil: Encode(int64)", 1491 v: int64(1), 1492 }, 1493 { 1494 desc: "invalid encode error message with nil interface", 1495 expected: "csvutil: Encode(nil)", 1496 v: nilIface, 1497 }, 1498 { 1499 desc: "invalid encode error message with nil value", 1500 expected: "csvutil: Encode(nil)", 1501 v: nilPtr, 1502 }, 1503 { 1504 desc: "unsupported type error message", 1505 expected: "csvutil: unsupported type: struct {}", 1506 v: struct{ InvalidType }{}, 1507 }, 1508 { 1509 desc: "marshaler error message", 1510 expected: "csvutil: error calling MarshalText for type csvutil.TextMarshaler: " + Error.Error(), 1511 v: struct{ M TextMarshaler }{TextMarshaler{Error}}, 1512 }, 1513 } 1514 1515 for _, f := range fixtures { 1516 t.Run(f.desc, func(t *testing.T) { 1517 err := NewEncoder(csv.NewWriter(bytes.NewBuffer(nil))).Encode(f.v) 1518 if err == nil { 1519 t.Fatal("want err not to be nil") 1520 } 1521 if err.Error() != f.expected { 1522 t.Errorf("want=%s; got %s", f.expected, err.Error()) 1523 } 1524 }) 1525 } 1526 }) 1527 1528 t.Run("EncodeHeader", func(t *testing.T) { 1529 t.Run("no double header with encode", func(t *testing.T) { 1530 var buf bytes.Buffer 1531 w := csv.NewWriter(&buf) 1532 enc := NewEncoder(w) 1533 if err := enc.EncodeHeader(TypeI{}); err != nil { 1534 t.Errorf("want err=nil; got %v", err) 1535 } 1536 if err := enc.Encode(TypeI{}); err != nil { 1537 t.Errorf("want err=nil; got %v", err) 1538 } 1539 w.Flush() 1540 1541 expected := encodeCSV(t, [][]string{ 1542 {"String", "int"}, 1543 {"", ""}, 1544 }) 1545 1546 if buf.String() != expected { 1547 t.Errorf("want out=%s; got %s", expected, buf.String()) 1548 } 1549 }) 1550 1551 t.Run("encode writes header if EncodeHeader fails", func(t *testing.T) { 1552 var buf bytes.Buffer 1553 w := csv.NewWriter(&buf) 1554 enc := NewEncoder(w) 1555 1556 if err := enc.EncodeHeader(InvalidType{}); err == nil { 1557 t.Errorf("expected not nil error") 1558 } 1559 1560 if err := enc.Encode(TypeI{}); err != nil { 1561 t.Errorf("want err=nil; got %v", err) 1562 } 1563 1564 w.Flush() 1565 1566 expected := encodeCSV(t, [][]string{ 1567 {"String", "int"}, 1568 {"", ""}, 1569 }) 1570 1571 if buf.String() != expected { 1572 t.Errorf("want out=%s; got %s", expected, buf.String()) 1573 } 1574 }) 1575 1576 fixtures := []struct { 1577 desc string 1578 in interface{} 1579 tag string 1580 out [][]string 1581 err error 1582 }{ 1583 { 1584 desc: "conflicting fields", 1585 in: &Embedded10{}, 1586 out: [][]string{ 1587 {"Y"}, 1588 }, 1589 }, 1590 { 1591 desc: "custom tag", 1592 in: TypeJ{}, 1593 tag: "json", 1594 out: [][]string{ 1595 {"string", "bool", "Uint", "Float"}, 1596 }, 1597 }, 1598 { 1599 desc: "nil interface ptr value", 1600 in: nilIfacePtr, 1601 out: [][]string{ 1602 { 1603 "int", 1604 "pint", 1605 "int8", 1606 "pint8", 1607 "int16", 1608 "pint16", 1609 "int32", 1610 "pint32", 1611 "int64", 1612 "pint64", 1613 "uint", 1614 "puint", 1615 "uint8", 1616 "puint8", 1617 "uint16", 1618 "puint16", 1619 "uint32", 1620 "puint32", 1621 "uint64", 1622 "puint64", 1623 "float32", 1624 "pfloat32", 1625 "float64", 1626 "pfloat64", 1627 "string", 1628 "pstring", 1629 "bool", 1630 "pbool", 1631 "interface", 1632 "pinterface", 1633 "binary", 1634 "pbinary", 1635 }, 1636 }, 1637 }, 1638 { 1639 desc: "ptr to nil interface ptr value", 1640 in: &nilIfacePtr, 1641 out: [][]string{ 1642 { 1643 "int", 1644 "pint", 1645 "int8", 1646 "pint8", 1647 "int16", 1648 "pint16", 1649 "int32", 1650 "pint32", 1651 "int64", 1652 "pint64", 1653 "uint", 1654 "puint", 1655 "uint8", 1656 "puint8", 1657 "uint16", 1658 "puint16", 1659 "uint32", 1660 "puint32", 1661 "uint64", 1662 "puint64", 1663 "float32", 1664 "pfloat32", 1665 "float64", 1666 "pfloat64", 1667 "string", 1668 "pstring", 1669 "bool", 1670 "pbool", 1671 "interface", 1672 "pinterface", 1673 "binary", 1674 "pbinary", 1675 }, 1676 }, 1677 }, 1678 { 1679 desc: "nil ptr value", 1680 in: nilPtr, 1681 out: [][]string{ 1682 { 1683 "int", 1684 "pint", 1685 "int8", 1686 "pint8", 1687 "int16", 1688 "pint16", 1689 "int32", 1690 "pint32", 1691 "int64", 1692 "pint64", 1693 "uint", 1694 "puint", 1695 "uint8", 1696 "puint8", 1697 "uint16", 1698 "puint16", 1699 "uint32", 1700 "puint32", 1701 "uint64", 1702 "puint64", 1703 "float32", 1704 "pfloat32", 1705 "float64", 1706 "pfloat64", 1707 "string", 1708 "pstring", 1709 "bool", 1710 "pbool", 1711 "interface", 1712 "pinterface", 1713 "binary", 1714 "pbinary", 1715 }, 1716 }, 1717 }, 1718 { 1719 desc: "ptr to nil ptr value", 1720 in: &nilPtr, 1721 out: [][]string{ 1722 { 1723 "int", 1724 "pint", 1725 "int8", 1726 "pint8", 1727 "int16", 1728 "pint16", 1729 "int32", 1730 "pint32", 1731 "int64", 1732 "pint64", 1733 "uint", 1734 "puint", 1735 "uint8", 1736 "puint8", 1737 "uint16", 1738 "puint16", 1739 "uint32", 1740 "puint32", 1741 "uint64", 1742 "puint64", 1743 "float32", 1744 "pfloat32", 1745 "float64", 1746 "pfloat64", 1747 "string", 1748 "pstring", 1749 "bool", 1750 "pbool", 1751 "interface", 1752 "pinterface", 1753 "binary", 1754 "pbinary", 1755 }, 1756 }, 1757 }, 1758 { 1759 desc: "ptr to nil interface", 1760 in: &nilIface, 1761 err: &UnsupportedTypeError{Type: reflect.ValueOf(&nilIface).Type().Elem()}, 1762 }, 1763 { 1764 desc: "nil value", 1765 err: &UnsupportedTypeError{}, 1766 }, 1767 { 1768 desc: "ptr - not a struct", 1769 in: &[]int{}, 1770 err: &UnsupportedTypeError{Type: reflect.TypeOf([]int{})}, 1771 }, 1772 { 1773 desc: "not a struct", 1774 in: int(1), 1775 err: &UnsupportedTypeError{Type: reflect.TypeOf(int(0))}, 1776 }, 1777 } 1778 1779 for _, f := range fixtures { 1780 t.Run(f.desc, func(t *testing.T) { 1781 var buf bytes.Buffer 1782 w := csv.NewWriter(&buf) 1783 1784 enc := NewEncoder(w) 1785 enc.Tag = f.tag 1786 1787 err := enc.EncodeHeader(f.in) 1788 w.Flush() 1789 1790 if !reflect.DeepEqual(err, f.err) { 1791 t.Errorf("want err=%v; got %v", f.err, err) 1792 } 1793 1794 if f.err != nil { 1795 return 1796 } 1797 1798 if expected := encodeCSV(t, f.out); buf.String() != expected { 1799 t.Errorf("want out=%s; got %s", expected, buf.String()) 1800 } 1801 }) 1802 } 1803 }) 1804 1805 t.Run("AutoHeader false", func(t *testing.T) { 1806 var buf bytes.Buffer 1807 w := csv.NewWriter(&buf) 1808 enc := NewEncoder(w) 1809 enc.AutoHeader = false 1810 1811 if err := enc.Encode(TypeG{ 1812 String: "s", 1813 Int: 10, 1814 }); err != nil { 1815 t.Fatalf("want err=nil; got %v", err) 1816 } 1817 w.Flush() 1818 1819 expected := encodeCSV(t, [][]string{{"s", "10"}}) 1820 if expected != buf.String() { 1821 t.Errorf("want %s; got %s", expected, buf.String()) 1822 } 1823 }) 1824 1825 t.Run("fail on type encoding without header", func(t *testing.T) { 1826 var buf bytes.Buffer 1827 w := csv.NewWriter(&buf) 1828 enc := NewEncoder(w) 1829 enc.AutoHeader = false 1830 1831 err := enc.Encode(struct { 1832 Invalid InvalidType 1833 }{}) 1834 1835 expected := &UnsupportedTypeError{Type: reflect.TypeOf(InvalidType{})} 1836 if !reflect.DeepEqual(err, expected) { 1837 t.Errorf("want %v; got %v", expected, err) 1838 } 1839 }) 1840 1841 t.Run("fail while writing header", func(t *testing.T) { 1842 Error := errors.New("error") 1843 enc := NewEncoder(failingWriter{Err: Error}) 1844 if err := enc.EncodeHeader(TypeA{}); err != Error { 1845 t.Errorf("want %v; got %v", Error, err) 1846 } 1847 }) 1848 1849 t.Run("slice and array", func(t *testing.T) { 1850 fixtures := []struct { 1851 desc string 1852 in interface{} 1853 out [][]string 1854 err error 1855 }{ 1856 { 1857 desc: "slice", 1858 in: []TypeI{ 1859 {"1", 1}, 1860 {"2", 2}, 1861 }, 1862 out: [][]string{ 1863 {"String", "int"}, 1864 {"1", "1"}, 1865 {"2", "2"}, 1866 }, 1867 }, 1868 { 1869 desc: "ptr slice", 1870 in: &[]TypeI{ 1871 {"1", 1}, 1872 {"2", 2}, 1873 }, 1874 out: [][]string{ 1875 {"String", "int"}, 1876 {"1", "1"}, 1877 {"2", "2"}, 1878 }, 1879 }, 1880 { 1881 desc: "ptr slice with ptr elements", 1882 in: &[]*TypeI{ 1883 {"1", 1}, 1884 {"2", 2}, 1885 }, 1886 out: [][]string{ 1887 {"String", "int"}, 1888 {"1", "1"}, 1889 {"2", "2"}, 1890 }, 1891 }, 1892 { 1893 desc: "array", 1894 in: [2]TypeI{ 1895 {"1", 1}, 1896 {"2", 2}, 1897 }, 1898 out: [][]string{ 1899 {"String", "int"}, 1900 {"1", "1"}, 1901 {"2", "2"}, 1902 }, 1903 }, 1904 { 1905 desc: "ptr array", 1906 in: &[2]TypeI{ 1907 {"1", 1}, 1908 {"2", 2}, 1909 }, 1910 out: [][]string{ 1911 {"String", "int"}, 1912 {"1", "1"}, 1913 {"2", "2"}, 1914 }, 1915 }, 1916 { 1917 desc: "ptr array with ptr elements", 1918 in: &[2]*TypeI{ 1919 {"1", 1}, 1920 {"2", 2}, 1921 }, 1922 out: [][]string{ 1923 {"String", "int"}, 1924 {"1", "1"}, 1925 {"2", "2"}, 1926 }, 1927 }, 1928 { 1929 desc: "array with default val", 1930 in: [2]TypeI{ 1931 {"1", 1}, 1932 }, 1933 out: [][]string{ 1934 {"String", "int"}, 1935 {"1", "1"}, 1936 {"", ""}, 1937 }, 1938 }, 1939 { 1940 desc: "no auto header on empty slice", 1941 in: []TypeI{}, 1942 out: [][]string{}, 1943 }, 1944 { 1945 desc: "no auto header on empty array", 1946 in: [0]TypeI{}, 1947 out: [][]string{}, 1948 }, 1949 { 1950 desc: "disallow double slice", 1951 in: [][]TypeI{ 1952 { 1953 {"1", 1}, 1954 }, 1955 }, 1956 err: &InvalidEncodeError{Type: reflect.TypeOf([][]TypeI{})}, 1957 }, 1958 { 1959 desc: "disallow double ptr slice", 1960 in: &[][]TypeI{ 1961 { 1962 {"1", 1}, 1963 }, 1964 }, 1965 err: &InvalidEncodeError{Type: reflect.TypeOf(&[][]TypeI{})}, 1966 }, 1967 { 1968 desc: "disallow double ptr slice with ptr slice", 1969 in: &[]*[]TypeI{ 1970 { 1971 {"1", 1}, 1972 }, 1973 }, 1974 err: &InvalidEncodeError{Type: reflect.TypeOf(&[]*[]TypeI{})}, 1975 }, 1976 { 1977 desc: "disallow double array", 1978 in: [2][2]TypeI{ 1979 { 1980 {"1", 1}, 1981 }, 1982 }, 1983 err: &InvalidEncodeError{Type: reflect.TypeOf([2][2]TypeI{})}, 1984 }, 1985 { 1986 desc: "disallow double ptr array", 1987 in: &[2][2]TypeI{ 1988 { 1989 {"1", 1}, 1990 }, 1991 }, 1992 err: &InvalidEncodeError{Type: reflect.TypeOf(&[2][2]TypeI{})}, 1993 }, 1994 { 1995 desc: "disallow interface slice", 1996 in: []interface{}{ 1997 TypeI{"1", 1}, 1998 }, 1999 err: &InvalidEncodeError{Type: reflect.TypeOf([]interface{}{})}, 2000 }, 2001 { 2002 desc: "disallow interface array", 2003 in: [1]interface{}{ 2004 TypeI{"1", 1}, 2005 }, 2006 err: &InvalidEncodeError{Type: reflect.TypeOf([1]interface{}{})}, 2007 }, 2008 } 2009 2010 for _, f := range fixtures { 2011 t.Run(f.desc, func(t *testing.T) { 2012 var buf bytes.Buffer 2013 w := csv.NewWriter(&buf) 2014 err := NewEncoder(w).Encode(f.in) 2015 2016 if f.err != nil { 2017 if !reflect.DeepEqual(f.err, err) { 2018 t.Errorf("want err=%v; got %v", f.err, err) 2019 } 2020 return 2021 } 2022 2023 if err != nil { 2024 t.Fatalf("want err=nil; got %v", err) 2025 } 2026 2027 w.Flush() 2028 if err := w.Error(); err != nil { 2029 t.Errorf("want err=nil; got %v", err) 2030 } 2031 2032 var out bytes.Buffer 2033 if err := csv.NewWriter(&out).WriteAll(f.out); err != nil { 2034 t.Errorf("want err=nil; got %v", err) 2035 } 2036 2037 if buf.String() != out.String() { 2038 t.Errorf("want=%s; got %s", out.String(), buf.String()) 2039 } 2040 }) 2041 } 2042 }) 2043 2044 t.Run("register panics", func(t *testing.T) { 2045 var buf bytes.Buffer 2046 r := csv.NewWriter(&buf) 2047 enc := NewEncoder(r) 2048 2049 fixtures := []struct { 2050 desc string 2051 arg interface{} 2052 }{ 2053 { 2054 desc: "not a func", 2055 arg: 1, 2056 }, 2057 { 2058 desc: "nil", 2059 arg: nil, 2060 }, 2061 { 2062 desc: "T == empty interface", 2063 arg: func(interface{}) ([]byte, error) { return nil, nil }, 2064 }, 2065 { 2066 desc: "first out not bytes", 2067 arg: func(int) (int, error) { return 0, nil }, 2068 }, 2069 { 2070 desc: "second out not error", 2071 arg: func(int) (int, int) { return 0, 0 }, 2072 }, 2073 { 2074 desc: "func with one out value", 2075 arg: func(int) error { return nil }, 2076 }, 2077 { 2078 desc: "func with no returns", 2079 arg: func(int) {}, 2080 }, 2081 } 2082 2083 for _, f := range fixtures { 2084 t.Run(f.desc, func(t *testing.T) { 2085 var e interface{} 2086 func() { 2087 defer func() { 2088 e = recover() 2089 }() 2090 enc.Register(f.arg) 2091 }() 2092 2093 if e == nil { 2094 t.Error("Register was supposed to panic but it didnt") 2095 } 2096 t.Log(e) 2097 }) 2098 } 2099 2100 t.Run("already registered", func(t *testing.T) { 2101 f := func(int) ([]byte, error) { return nil, nil } 2102 enc.Register(f) 2103 2104 var e interface{} 2105 func() { 2106 defer func() { 2107 e = recover() 2108 }() 2109 enc.Register(f) 2110 }() 2111 2112 if e == nil { 2113 t.Error("Register was supposed to panic but it didnt") 2114 } 2115 t.Log(e) 2116 }) 2117 }) 2118} 2119 2120func encode(t *testing.T, buf *bytes.Buffer, v interface{}, tag string) { 2121 w := csv.NewWriter(buf) 2122 enc := NewEncoder(w) 2123 enc.Tag = tag 2124 if err := enc.Encode(v); err != nil { 2125 t.Fatalf("want err=nil; got %v", err) 2126 } 2127 w.Flush() 2128 if err := w.Error(); err != nil { 2129 t.Fatalf("want err=nil; got %v", err) 2130 } 2131} 2132 2133func encodeCSV(t *testing.T, recs [][]string) string { 2134 var buf bytes.Buffer 2135 if err := csv.NewWriter(&buf).WriteAll(recs); err != nil { 2136 t.Fatalf("want err=nil; got %v", err) 2137 } 2138 return buf.String() 2139} 2140 2141type failingWriter struct { 2142 Err error 2143} 2144 2145func (w failingWriter) Write([]string) error { 2146 return w.Err 2147} 2148