1/* 2Copyright 2019 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package listtype 18 19import ( 20 "testing" 21 22 "k8s.io/apimachinery/pkg/util/validation/field" 23 24 "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" 25) 26 27func TestValidateListSetsAndMaps(t *testing.T) { 28 tests := []struct { 29 name string 30 schema *schema.Structural 31 obj map[string]interface{} 32 errors []validationMatch 33 }{ 34 {name: "nil"}, 35 {name: "no schema", obj: make(map[string]interface{})}, 36 {name: "no object", schema: &schema.Structural{}}, 37 {name: "list without schema", 38 obj: map[string]interface{}{ 39 "array": []interface{}{"a", "b", "a"}, 40 }, 41 }, 42 {name: "list without items", 43 obj: map[string]interface{}{ 44 "array": []interface{}{"a", "b", "a"}, 45 }, 46 schema: &schema.Structural{ 47 Generic: schema.Generic{ 48 Type: "object", 49 }, 50 Properties: map[string]schema.Structural{ 51 "array": { 52 Generic: schema.Generic{ 53 Type: "array", 54 }, 55 }, 56 }, 57 }, 58 }, 59 60 {name: "set list with one item", 61 obj: map[string]interface{}{ 62 "array": []interface{}{"a"}, 63 }, 64 schema: &schema.Structural{ 65 Generic: schema.Generic{ 66 Type: "object", 67 }, 68 Properties: map[string]schema.Structural{ 69 "array": { 70 Extensions: schema.Extensions{ 71 XListType: strPtr("set"), 72 }, 73 Generic: schema.Generic{ 74 Type: "array", 75 }, 76 }, 77 }, 78 }, 79 }, 80 {name: "set list with two equal items", 81 obj: map[string]interface{}{ 82 "array": []interface{}{"a", "a"}, 83 }, 84 schema: &schema.Structural{ 85 Generic: schema.Generic{ 86 Type: "object", 87 }, 88 Properties: map[string]schema.Structural{ 89 "array": { 90 Extensions: schema.Extensions{ 91 XListType: strPtr("set"), 92 }, 93 Generic: schema.Generic{ 94 Type: "array", 95 }, 96 }, 97 }, 98 }, 99 errors: []validationMatch{ 100 duplicate("root", "array[1]"), 101 }, 102 }, 103 {name: "set list with two different items", 104 obj: map[string]interface{}{ 105 "array": []interface{}{"a", "b"}, 106 }, 107 schema: &schema.Structural{ 108 Generic: schema.Generic{ 109 Type: "object", 110 }, 111 Properties: map[string]schema.Structural{ 112 "array": { 113 Extensions: schema.Extensions{ 114 XListType: strPtr("set"), 115 }, 116 Generic: schema.Generic{ 117 Type: "array", 118 }, 119 }, 120 }, 121 }, 122 }, 123 {name: "set list with multiple duplicated items", 124 obj: map[string]interface{}{ 125 "array": []interface{}{"a", "a", "b", "c", "d", "c"}, 126 }, 127 schema: &schema.Structural{ 128 Generic: schema.Generic{ 129 Type: "object", 130 }, 131 Properties: map[string]schema.Structural{ 132 "array": { 133 Extensions: schema.Extensions{ 134 XListType: strPtr("set"), 135 }, 136 Generic: schema.Generic{ 137 Type: "array", 138 }, 139 }, 140 }, 141 }, 142 errors: []validationMatch{ 143 duplicate("root", "array[1]"), 144 duplicate("root", "array[5]"), 145 }, 146 }, 147 148 {name: "normal list with items", 149 obj: map[string]interface{}{ 150 "array": []interface{}{"a", "b", "a"}, 151 }, 152 schema: &schema.Structural{ 153 Generic: schema.Generic{ 154 Type: "object", 155 }, 156 Properties: map[string]schema.Structural{ 157 "array": { 158 Generic: schema.Generic{ 159 Type: "array", 160 }, 161 Items: &schema.Structural{ 162 Generic: schema.Generic{ 163 Type: "string", 164 }, 165 }, 166 }, 167 }, 168 }, 169 }, 170 {name: "set list with items", 171 obj: map[string]interface{}{ 172 "array": []interface{}{"a", "b", "a"}, 173 }, 174 schema: &schema.Structural{ 175 Generic: schema.Generic{ 176 Type: "object", 177 }, 178 Properties: map[string]schema.Structural{ 179 "array": { 180 Generic: schema.Generic{ 181 Type: "array", 182 }, 183 Extensions: schema.Extensions{ 184 XListType: strPtr("set"), 185 }, 186 Items: &schema.Structural{ 187 Generic: schema.Generic{ 188 Type: "string", 189 }, 190 }, 191 }, 192 }, 193 }, 194 errors: []validationMatch{ 195 duplicate("root", "array[2]"), 196 }, 197 }, 198 {name: "set list with items under additionalProperties", 199 obj: map[string]interface{}{ 200 "array": []interface{}{"a", "b", "a"}, 201 }, 202 schema: &schema.Structural{ 203 Generic: schema.Generic{ 204 Type: "object", 205 AdditionalProperties: &schema.StructuralOrBool{ 206 Structural: &schema.Structural{ 207 Generic: schema.Generic{ 208 Type: "array", 209 }, 210 Extensions: schema.Extensions{ 211 XListType: strPtr("set"), 212 }, 213 Items: &schema.Structural{ 214 Generic: schema.Generic{ 215 Type: "string", 216 }, 217 }, 218 }, 219 }, 220 }, 221 }, 222 errors: []validationMatch{ 223 duplicate("root[array][2]"), 224 }, 225 }, 226 {name: "set list with items under items", 227 obj: map[string]interface{}{ 228 "array": []interface{}{ 229 []interface{}{"a", "b", "a"}, 230 []interface{}{"b", "b", "a"}, 231 }, 232 }, 233 schema: &schema.Structural{ 234 Generic: schema.Generic{ 235 Type: "object", 236 }, 237 Properties: map[string]schema.Structural{ 238 "array": { 239 Generic: schema.Generic{ 240 Type: "array", 241 }, 242 Items: &schema.Structural{ 243 Generic: schema.Generic{ 244 Type: "array", 245 }, 246 Extensions: schema.Extensions{ 247 XListType: strPtr("set"), 248 }, 249 Items: &schema.Structural{ 250 Generic: schema.Generic{ 251 Type: "string", 252 }, 253 }, 254 }, 255 }, 256 }, 257 }, 258 errors: []validationMatch{ 259 duplicate("root", "array[0][2]"), 260 duplicate("root", "array[1][1]"), 261 }, 262 }, 263 264 {name: "nested set lists", 265 obj: map[string]interface{}{ 266 "array": []interface{}{ 267 "a", "b", "a", []interface{}{"b", "b", "a"}, 268 }, 269 }, 270 schema: &schema.Structural{ 271 Generic: schema.Generic{ 272 Type: "object", 273 }, 274 Properties: map[string]schema.Structural{ 275 "array": { 276 Generic: schema.Generic{ 277 Type: "array", 278 }, 279 Extensions: schema.Extensions{ 280 XListType: strPtr("set"), 281 }, 282 Items: &schema.Structural{ 283 Generic: schema.Generic{ 284 Type: "array", 285 }, 286 Extensions: schema.Extensions{ 287 XListType: strPtr("set"), 288 }, 289 }, 290 }, 291 }, 292 }, 293 errors: []validationMatch{ 294 duplicate("root", "array[2]"), 295 duplicate("root", "array[3][1]"), 296 }, 297 }, 298 299 {name: "set list with compound map items", 300 obj: map[string]interface{}{ 301 "strings": []interface{}{"a", "b", "a"}, 302 "integers": []interface{}{int64(1), int64(2), int64(1)}, 303 "booleans": []interface{}{false, true, true}, 304 "float64": []interface{}{float64(1.0), float64(2.0), float64(2.0)}, 305 "nil": []interface{}{"a", nil, nil}, 306 "empty maps": []interface{}{map[string]interface{}{"a": "b"}, map[string]interface{}{}, map[string]interface{}{}}, 307 "map values": []interface{}{map[string]interface{}{"a": "b"}, map[string]interface{}{"a": "c"}, map[string]interface{}{"a": "b"}}, 308 "nil values": []interface{}{map[string]interface{}{"a": nil}, map[string]interface{}{"b": "c", "a": nil}}, 309 "array": []interface{}{[]interface{}{}, []interface{}{"a"}, []interface{}{"b"}, []interface{}{"a"}}, 310 "nil array": []interface{}{[]interface{}{}, []interface{}{nil}, []interface{}{nil, nil}, []interface{}{nil}, []interface{}{"a"}}, 311 "multiple duplicates": []interface{}{map[string]interface{}{"a": "b"}, map[string]interface{}{"a": "c"}, map[string]interface{}{"a": "b"}, map[string]interface{}{"a": "c"}, map[string]interface{}{"a": "c"}}, 312 }, 313 schema: &schema.Structural{ 314 Generic: schema.Generic{ 315 Type: "object", 316 }, 317 Properties: map[string]schema.Structural{ 318 "strings": { 319 Generic: schema.Generic{ 320 Type: "array", 321 }, 322 Items: &schema.Structural{ 323 Generic: schema.Generic{ 324 Type: "string", 325 }, 326 }, 327 Extensions: schema.Extensions{ 328 XListType: strPtr("set"), 329 }, 330 }, 331 "integers": { 332 Generic: schema.Generic{ 333 Type: "array", 334 }, 335 Items: &schema.Structural{ 336 Generic: schema.Generic{ 337 Type: "integer", 338 }, 339 }, 340 Extensions: schema.Extensions{ 341 XListType: strPtr("set"), 342 }, 343 }, 344 "booleans": { 345 Generic: schema.Generic{ 346 Type: "array", 347 }, 348 Items: &schema.Structural{ 349 Generic: schema.Generic{ 350 Type: "boolean", 351 }, 352 }, 353 Extensions: schema.Extensions{ 354 XListType: strPtr("set"), 355 }, 356 }, 357 "float64": { 358 Generic: schema.Generic{ 359 Type: "array", 360 }, 361 Items: &schema.Structural{ 362 Generic: schema.Generic{ 363 Type: "number", 364 }, 365 }, 366 Extensions: schema.Extensions{ 367 XListType: strPtr("set"), 368 }, 369 }, 370 "nil": { 371 Generic: schema.Generic{ 372 Type: "array", 373 }, Items: &schema.Structural{ 374 Generic: schema.Generic{ 375 Type: "string", 376 Nullable: true, 377 }, 378 }, 379 Extensions: schema.Extensions{ 380 XListType: strPtr("set"), 381 }, 382 }, 383 "empty maps": { 384 Generic: schema.Generic{ 385 Type: "array", 386 }, 387 Items: &schema.Structural{ 388 Generic: schema.Generic{ 389 Type: "object", 390 AdditionalProperties: &schema.StructuralOrBool{ 391 Structural: &schema.Structural{ 392 Generic: schema.Generic{ 393 Type: "string", 394 }, 395 }, 396 }, 397 }, 398 }, 399 Extensions: schema.Extensions{ 400 XListType: strPtr("set"), 401 }, 402 }, 403 "map values": { 404 Generic: schema.Generic{ 405 Type: "array", 406 }, 407 Items: &schema.Structural{ 408 Generic: schema.Generic{ 409 Type: "object", 410 AdditionalProperties: &schema.StructuralOrBool{ 411 Structural: &schema.Structural{ 412 Generic: schema.Generic{ 413 Type: "string", 414 }, 415 }, 416 }, 417 }, 418 }, 419 Extensions: schema.Extensions{ 420 XListType: strPtr("set"), 421 }, 422 }, 423 "nil values": { 424 Generic: schema.Generic{ 425 Type: "array", 426 }, 427 Items: &schema.Structural{ 428 Generic: schema.Generic{ 429 Type: "object", 430 AdditionalProperties: &schema.StructuralOrBool{ 431 Structural: &schema.Structural{ 432 Generic: schema.Generic{ 433 Type: "string", 434 Nullable: true, 435 }, 436 }, 437 }, 438 }, 439 }, 440 Extensions: schema.Extensions{ 441 XListType: strPtr("set"), 442 }, 443 }, 444 "array": { 445 Generic: schema.Generic{ 446 Type: "array", 447 }, 448 Items: &schema.Structural{ 449 Generic: schema.Generic{ 450 Type: "array", 451 }, 452 Items: &schema.Structural{ 453 Generic: schema.Generic{ 454 Type: "string", 455 }, 456 }, 457 }, 458 Extensions: schema.Extensions{ 459 XListType: strPtr("set"), 460 }, 461 }, 462 "nil array": { 463 Generic: schema.Generic{ 464 Type: "array", 465 }, 466 Items: &schema.Structural{ 467 Generic: schema.Generic{ 468 Type: "array", 469 }, 470 Items: &schema.Structural{ 471 Generic: schema.Generic{ 472 Type: "string", 473 Nullable: true, 474 }, 475 }, 476 }, 477 Extensions: schema.Extensions{ 478 XListType: strPtr("set"), 479 }, 480 }, 481 "multiple duplicates": { 482 Generic: schema.Generic{ 483 Type: "array", 484 }, 485 Items: &schema.Structural{ 486 Generic: schema.Generic{ 487 Type: "object", 488 AdditionalProperties: &schema.StructuralOrBool{ 489 Structural: &schema.Structural{ 490 Generic: schema.Generic{ 491 Type: "string", 492 }, 493 }, 494 }, 495 }, 496 }, 497 Extensions: schema.Extensions{ 498 XListType: strPtr("set"), 499 }, 500 }, 501 }, 502 }, 503 errors: []validationMatch{ 504 duplicate("root", "strings[2]"), 505 duplicate("root", "integers[2]"), 506 duplicate("root", "booleans[2]"), 507 duplicate("root", "float64[2]"), 508 duplicate("root", "nil[2]"), 509 duplicate("root", "empty maps[2]"), 510 duplicate("root", "map values[2]"), 511 duplicate("root", "array[3]"), 512 duplicate("root", "nil array[3]"), 513 duplicate("root", "multiple duplicates[2]"), 514 duplicate("root", "multiple duplicates[3]"), 515 }, 516 }, 517 {name: "set list with compound array items", 518 obj: map[string]interface{}{ 519 "array": []interface{}{[]interface{}{}, []interface{}{"a"}, []interface{}{"a"}}, 520 }, 521 schema: &schema.Structural{ 522 Generic: schema.Generic{ 523 Type: "object", 524 }, 525 Properties: map[string]schema.Structural{ 526 "array": { 527 Generic: schema.Generic{ 528 Type: "array", 529 }, 530 Extensions: schema.Extensions{ 531 XListType: strPtr("set"), 532 }, 533 Items: &schema.Structural{ 534 Generic: schema.Generic{ 535 Type: "string", 536 }, 537 }, 538 }, 539 }, 540 }, 541 errors: []validationMatch{ 542 duplicate("root", "array[2]"), 543 }, 544 }, 545 546 {name: "map list with compound map items", 547 obj: map[string]interface{}{ 548 "strings": []interface{}{"a"}, 549 "integers": []interface{}{int64(1)}, 550 "booleans": []interface{}{false}, 551 "float64": []interface{}{float64(1.0)}, 552 "nil": []interface{}{nil}, 553 "array": []interface{}{[]interface{}{"a"}}, 554 "one key": []interface{}{map[string]interface{}{"a": "0", "c": "2"}, map[string]interface{}{"a": "1", "c": "1"}, map[string]interface{}{"a": "1", "c": "2"}, map[string]interface{}{}}, 555 "two keys": []interface{}{map[string]interface{}{"a": "1", "b": "1", "c": "1"}, map[string]interface{}{"a": "1", "b": "2", "c": "2"}, map[string]interface{}{"a": "1", "b": "2", "c": "3"}, map[string]interface{}{}}, 556 "undefined key": []interface{}{map[string]interface{}{"a": "1", "b": "1", "c": "1"}, map[string]interface{}{"a": "1", "c": "2"}, map[string]interface{}{"a": "1", "c": "3"}, map[string]interface{}{}}, 557 "compound key": []interface{}{map[string]interface{}{"a": []interface{}{}, "c": "1"}, map[string]interface{}{"a": nil, "c": "1"}, map[string]interface{}{"a": []interface{}{"a"}, "c": "1"}, map[string]interface{}{"a": []interface{}{"a", int64(42)}, "c": "2"}, map[string]interface{}{"a": []interface{}{"a", int64(42)}, "c": []interface{}{"3"}}}, 558 "nil key": []interface{}{map[string]interface{}{"a": []interface{}{}, "c": "1"}, map[string]interface{}{"a": nil, "c": "1"}, map[string]interface{}{"c": "1"}, map[string]interface{}{"a": nil}}, 559 "nil item": []interface{}{nil, map[string]interface{}{"a": "0", "c": "1"}, map[string]interface{}{"a": nil}, map[string]interface{}{"c": "1"}}, 560 "nil item multiple keys": []interface{}{nil, map[string]interface{}{"b": "0", "c": "1"}, map[string]interface{}{"a": nil}, map[string]interface{}{"c": "1"}}, 561 "multiple duplicates": []interface{}{ 562 map[string]interface{}{"a": []interface{}{}, "c": "1"}, 563 map[string]interface{}{"a": nil, "c": "1"}, 564 map[string]interface{}{"a": []interface{}{"a"}, "c": "1"}, 565 map[string]interface{}{"a": []interface{}{"a", int64(42)}, "c": "2"}, 566 map[string]interface{}{"a": []interface{}{"a", int64(42)}, "c": []interface{}{"3"}}, 567 map[string]interface{}{"a": []interface{}{"a"}, "c": "1", "d": "1"}, 568 map[string]interface{}{"a": []interface{}{"a"}, "c": "1", "d": "2"}, 569 }, 570 }, 571 schema: &schema.Structural{ 572 Generic: schema.Generic{ 573 Type: "object", 574 }, 575 Properties: map[string]schema.Structural{ 576 "strings": { 577 Generic: schema.Generic{ 578 Type: "array", 579 }, 580 Items: &schema.Structural{ 581 Generic: schema.Generic{ 582 Type: "string", 583 }, 584 }, 585 Extensions: schema.Extensions{ 586 XListType: strPtr("map"), 587 XListMapKeys: []string{"a"}, 588 }, 589 }, 590 "integers": { 591 Generic: schema.Generic{ 592 Type: "array", 593 }, 594 Items: &schema.Structural{ 595 Generic: schema.Generic{ 596 Type: "integer", 597 }, 598 }, 599 Extensions: schema.Extensions{ 600 XListType: strPtr("map"), 601 XListMapKeys: []string{"a"}, 602 }, 603 }, 604 "booleans": { 605 Generic: schema.Generic{ 606 Type: "array", 607 }, 608 Items: &schema.Structural{ 609 Generic: schema.Generic{ 610 Type: "boolean", 611 }, 612 }, 613 Extensions: schema.Extensions{ 614 XListType: strPtr("map"), 615 XListMapKeys: []string{"a"}, 616 }, 617 }, 618 "float64": { 619 Generic: schema.Generic{ 620 Type: "array", 621 }, 622 Items: &schema.Structural{ 623 Generic: schema.Generic{ 624 Type: "number", 625 }, 626 }, 627 Extensions: schema.Extensions{ 628 XListType: strPtr("map"), 629 XListMapKeys: []string{"a"}, 630 }, 631 }, 632 "nil": { 633 Generic: schema.Generic{ 634 Type: "array", 635 }, 636 Items: &schema.Structural{ 637 Generic: schema.Generic{ 638 Type: "string", 639 Nullable: true, 640 }, 641 }, 642 Extensions: schema.Extensions{ 643 XListType: strPtr("map"), 644 XListMapKeys: []string{"a"}, 645 }, 646 }, 647 "array": { 648 Generic: schema.Generic{ 649 Type: "array", 650 }, 651 Items: &schema.Structural{ 652 Generic: schema.Generic{ 653 Type: "array", 654 }, 655 Items: &schema.Structural{ 656 Generic: schema.Generic{ 657 Type: "string", 658 }, 659 }, 660 }, 661 Extensions: schema.Extensions{ 662 XListType: strPtr("map"), 663 XListMapKeys: []string{"a"}, 664 }, 665 }, 666 "one key": { 667 Generic: schema.Generic{ 668 Type: "array", 669 }, 670 Items: &schema.Structural{ 671 Generic: schema.Generic{ 672 Type: "object", 673 AdditionalProperties: &schema.StructuralOrBool{ 674 Structural: &schema.Structural{ 675 Generic: schema.Generic{ 676 Type: "string", 677 }, 678 }, 679 }, 680 }, 681 }, 682 Extensions: schema.Extensions{ 683 XListType: strPtr("map"), 684 XListMapKeys: []string{"a"}, 685 }, 686 }, 687 "two keys": { 688 Generic: schema.Generic{ 689 Type: "array", 690 }, 691 Items: &schema.Structural{ 692 Generic: schema.Generic{ 693 Type: "object", 694 AdditionalProperties: &schema.StructuralOrBool{ 695 Structural: &schema.Structural{ 696 Generic: schema.Generic{ 697 Type: "string", 698 }, 699 }, 700 }, 701 }, 702 }, 703 Extensions: schema.Extensions{ 704 XListType: strPtr("map"), 705 XListMapKeys: []string{"a", "b"}, 706 }, 707 }, 708 "undefined key": { 709 Generic: schema.Generic{ 710 Type: "array", 711 }, 712 Items: &schema.Structural{ 713 Generic: schema.Generic{ 714 Type: "object", 715 AdditionalProperties: &schema.StructuralOrBool{ 716 Structural: &schema.Structural{ 717 Generic: schema.Generic{ 718 Type: "string", 719 }, 720 }, 721 }, 722 }, 723 }, 724 Extensions: schema.Extensions{ 725 XListType: strPtr("map"), 726 XListMapKeys: []string{"a", "b"}, 727 }, 728 }, 729 "compound key": { 730 Generic: schema.Generic{ 731 Type: "array", 732 }, 733 Items: &schema.Structural{ 734 Generic: schema.Generic{ 735 Type: "object", 736 AdditionalProperties: &schema.StructuralOrBool{ 737 Structural: &schema.Structural{ 738 Generic: schema.Generic{ 739 Type: "string", 740 Nullable: true, 741 }, 742 }, 743 }, 744 }, 745 }, 746 Extensions: schema.Extensions{ 747 XListType: strPtr("map"), 748 XListMapKeys: []string{"a"}, 749 }, 750 }, 751 "nil key": { 752 Generic: schema.Generic{ 753 Type: "array", 754 }, 755 Items: &schema.Structural{ 756 Generic: schema.Generic{ 757 Type: "object", 758 }, 759 Properties: map[string]schema.Structural{ 760 "a": { 761 Generic: schema.Generic{ 762 Type: "array", 763 Nullable: true, 764 }, 765 Items: &schema.Structural{ 766 Generic: schema.Generic{ 767 Type: "string", 768 }, 769 }, 770 }, 771 "c": { 772 Generic: schema.Generic{ 773 Type: "string", 774 }, 775 }, 776 }, 777 }, 778 Extensions: schema.Extensions{ 779 XListType: strPtr("map"), 780 XListMapKeys: []string{"a"}, 781 }, 782 }, 783 "nil item": { 784 Generic: schema.Generic{ 785 Type: "array", 786 }, 787 Items: &schema.Structural{ 788 Generic: schema.Generic{ 789 Type: "object", 790 }, 791 Properties: map[string]schema.Structural{ 792 "a": { 793 Generic: schema.Generic{ 794 Type: "array", 795 Nullable: true, 796 }, 797 Items: &schema.Structural{ 798 Generic: schema.Generic{ 799 Type: "string", 800 }, 801 }, 802 }, 803 "c": { 804 Generic: schema.Generic{ 805 Type: "string", 806 }, 807 }, 808 }, 809 }, 810 Extensions: schema.Extensions{ 811 XListType: strPtr("map"), 812 XListMapKeys: []string{"a"}, 813 }, 814 }, 815 "nil item multiple keys": { 816 Generic: schema.Generic{ 817 Type: "array", 818 }, 819 Items: &schema.Structural{ 820 Generic: schema.Generic{ 821 Type: "object", 822 }, 823 Properties: map[string]schema.Structural{ 824 "a": { 825 Generic: schema.Generic{ 826 Type: "array", 827 Nullable: true, 828 }, 829 Items: &schema.Structural{ 830 Generic: schema.Generic{ 831 Type: "string", 832 }, 833 }, 834 }, 835 "b": { 836 Generic: schema.Generic{ 837 Type: "string", 838 }, 839 }, 840 "c": { 841 Generic: schema.Generic{ 842 Type: "string", 843 }, 844 }, 845 }, 846 }, 847 Extensions: schema.Extensions{ 848 XListType: strPtr("map"), 849 XListMapKeys: []string{"a", "b"}, 850 }, 851 }, 852 "multiple duplicates": { 853 Generic: schema.Generic{ 854 Type: "array", 855 }, 856 Items: &schema.Structural{ 857 Generic: schema.Generic{ 858 Type: "object", 859 AdditionalProperties: &schema.StructuralOrBool{ 860 Structural: &schema.Structural{ 861 Generic: schema.Generic{ 862 Type: "string", 863 Nullable: true, 864 }, 865 }, 866 }, 867 }, 868 }, 869 Extensions: schema.Extensions{ 870 XListType: strPtr("map"), 871 XListMapKeys: []string{"a"}, 872 }, 873 }, 874 }, 875 }, 876 errors: []validationMatch{ 877 invalid("root", "strings[0]"), 878 invalid("root", "integers[0]"), 879 invalid("root", "booleans[0]"), 880 invalid("root", "float64[0]"), 881 invalid("root", "array[0]"), 882 duplicate("root", "one key[2]"), 883 duplicate("root", "two keys[2]"), 884 duplicate("root", "undefined key[2]"), 885 duplicate("root", "compound key[4]"), 886 duplicate("root", "nil key[3]"), 887 duplicate("root", "nil item[3]"), 888 duplicate("root", "nil item multiple keys[3]"), 889 duplicate("root", "multiple duplicates[4]"), 890 duplicate("root", "multiple duplicates[5]"), 891 }, 892 }, 893 } 894 for _, tt := range tests { 895 t.Run(tt.name, func(t *testing.T) { 896 errs := ValidateListSetsAndMaps(field.NewPath("root"), tt.schema, tt.obj) 897 898 seenErrs := make([]bool, len(errs)) 899 900 for _, expectedError := range tt.errors { 901 found := false 902 for i, err := range errs { 903 if expectedError.matches(err) && !seenErrs[i] { 904 found = true 905 seenErrs[i] = true 906 break 907 } 908 } 909 910 if !found { 911 t.Errorf("expected %v at %v, got %v", expectedError.errorType, expectedError.path.String(), errs) 912 } 913 } 914 915 for i, seen := range seenErrs { 916 if !seen { 917 t.Errorf("unexpected error: %v", errs[i]) 918 } 919 } 920 }) 921 } 922} 923 924type validationMatch struct { 925 path *field.Path 926 errorType field.ErrorType 927} 928 929func (v validationMatch) matches(err *field.Error) bool { 930 return err.Type == v.errorType && err.Field == v.path.String() 931} 932 933func duplicate(path ...string) validationMatch { 934 return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeDuplicate} 935} 936func invalid(path ...string) validationMatch { 937 return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeInvalid} 938} 939 940func strPtr(s string) *string { return &s } 941