github.com/databricks/cli@v0.203.0/bundle/schema/schema_test.go (about) 1 package schema 2 3 import ( 4 "encoding/json" 5 "reflect" 6 "testing" 7 8 "github.com/stretchr/testify/assert" 9 "github.com/stretchr/testify/require" 10 ) 11 12 func TestIntSchema(t *testing.T) { 13 var elemInt int 14 15 expected := 16 `{ 17 "type": "number" 18 }` 19 20 schema, err := New(reflect.TypeOf(elemInt), nil) 21 require.NoError(t, err) 22 23 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 24 assert.NoError(t, err) 25 26 t.Log("[DEBUG] actual: ", string(jsonSchema)) 27 t.Log("[DEBUG] expected: ", expected) 28 assert.Equal(t, expected, string(jsonSchema)) 29 } 30 31 func TestBooleanSchema(t *testing.T) { 32 var elem bool 33 34 expected := 35 `{ 36 "type": "boolean" 37 }` 38 39 schema, err := New(reflect.TypeOf(elem), nil) 40 require.NoError(t, err) 41 42 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 43 assert.NoError(t, err) 44 45 t.Log("[DEBUG] actual: ", string(jsonSchema)) 46 t.Log("[DEBUG] expected: ", expected) 47 assert.Equal(t, expected, string(jsonSchema)) 48 } 49 50 func TestStringSchema(t *testing.T) { 51 var elem string 52 53 expected := 54 `{ 55 "type": "string" 56 }` 57 58 schema, err := New(reflect.TypeOf(elem), nil) 59 require.NoError(t, err) 60 61 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 62 assert.NoError(t, err) 63 64 t.Log("[DEBUG] actual: ", string(jsonSchema)) 65 t.Log("[DEBUG] expected: ", expected) 66 assert.Equal(t, expected, string(jsonSchema)) 67 } 68 69 func TestStructOfPrimitivesSchema(t *testing.T) { 70 type Foo struct { 71 IntVal int `json:"int_val"` 72 Int8Val int8 `json:"int8_val"` 73 Int16Val int16 `json:"int16_val"` 74 Int32Val int32 `json:"int32_val"` 75 Int64Val int64 `json:"int64_val"` 76 77 UIntVal uint `json:"uint_val"` 78 Uint8Val uint8 `json:"uint8_val"` 79 Uint16Val uint16 `json:"uint16_val"` 80 Uint32Val uint32 `json:"uint32_val"` 81 Uint64Val uint64 `json:"uint64_val"` 82 83 Float32Val float32 `json:"float32_val"` 84 Float64Val float64 `json:"float64_val"` 85 86 StringVal string `json:"string_val"` 87 88 BoolVal bool `json:"bool_val"` 89 } 90 91 elem := Foo{} 92 93 schema, err := New(reflect.TypeOf(elem), nil) 94 assert.NoError(t, err) 95 96 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 97 assert.NoError(t, err) 98 99 expected := 100 `{ 101 "type": "object", 102 "properties": { 103 "bool_val": { 104 "type": "boolean" 105 }, 106 "float32_val": { 107 "type": "number" 108 }, 109 "float64_val": { 110 "type": "number" 111 }, 112 "int16_val": { 113 "type": "number" 114 }, 115 "int32_val": { 116 "type": "number" 117 }, 118 "int64_val": { 119 "type": "number" 120 }, 121 "int8_val": { 122 "type": "number" 123 }, 124 "int_val": { 125 "type": "number" 126 }, 127 "string_val": { 128 "type": "string" 129 }, 130 "uint16_val": { 131 "type": "number" 132 }, 133 "uint32_val": { 134 "type": "number" 135 }, 136 "uint64_val": { 137 "type": "number" 138 }, 139 "uint8_val": { 140 "type": "number" 141 }, 142 "uint_val": { 143 "type": "number" 144 } 145 }, 146 "additionalProperties": false, 147 "required": [ 148 "int_val", 149 "int8_val", 150 "int16_val", 151 "int32_val", 152 "int64_val", 153 "uint_val", 154 "uint8_val", 155 "uint16_val", 156 "uint32_val", 157 "uint64_val", 158 "float32_val", 159 "float64_val", 160 "string_val", 161 "bool_val" 162 ] 163 }` 164 165 t.Log("[DEBUG] actual: ", string(jsonSchema)) 166 t.Log("[DEBUG] expected: ", expected) 167 assert.Equal(t, expected, string(jsonSchema)) 168 } 169 170 func TestStructOfStructsSchema(t *testing.T) { 171 type Bar struct { 172 A int `json:"a"` 173 B string `json:"b,string"` 174 } 175 176 type Foo struct { 177 Bar Bar `json:"bar"` 178 } 179 180 type MyStruct struct { 181 Foo Foo `json:"foo"` 182 } 183 184 elem := MyStruct{} 185 186 schema, err := New(reflect.TypeOf(elem), nil) 187 assert.NoError(t, err) 188 189 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 190 assert.NoError(t, err) 191 192 expected := 193 `{ 194 "type": "object", 195 "properties": { 196 "foo": { 197 "type": "object", 198 "properties": { 199 "bar": { 200 "type": "object", 201 "properties": { 202 "a": { 203 "type": "number" 204 }, 205 "b": { 206 "type": "string" 207 } 208 }, 209 "additionalProperties": false, 210 "required": [ 211 "a", 212 "b" 213 ] 214 } 215 }, 216 "additionalProperties": false, 217 "required": [ 218 "bar" 219 ] 220 } 221 }, 222 "additionalProperties": false, 223 "required": [ 224 "foo" 225 ] 226 }` 227 228 t.Log("[DEBUG] actual: ", string(jsonSchema)) 229 t.Log("[DEBUG] expected: ", expected) 230 assert.Equal(t, expected, string(jsonSchema)) 231 } 232 233 func TestStructOfMapsSchema(t *testing.T) { 234 type Bar struct { 235 MyMap map[string]int `json:"my_map"` 236 } 237 238 type Foo struct { 239 Bar Bar `json:"bar"` 240 } 241 242 elem := Foo{} 243 244 schema, err := New(reflect.TypeOf(elem), nil) 245 assert.NoError(t, err) 246 247 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 248 assert.NoError(t, err) 249 250 expected := 251 `{ 252 "type": "object", 253 "properties": { 254 "bar": { 255 "type": "object", 256 "properties": { 257 "my_map": { 258 "type": "object", 259 "additionalProperties": { 260 "type": "number" 261 } 262 } 263 }, 264 "additionalProperties": false, 265 "required": [ 266 "my_map" 267 ] 268 } 269 }, 270 "additionalProperties": false, 271 "required": [ 272 "bar" 273 ] 274 }` 275 276 t.Log("[DEBUG] actual: ", string(jsonSchema)) 277 t.Log("[DEBUG] expected: ", expected) 278 assert.Equal(t, expected, string(jsonSchema)) 279 } 280 281 func TestStructOfSliceSchema(t *testing.T) { 282 type Bar struct { 283 MySlice []string `json:"my_slice"` 284 } 285 286 type Foo struct { 287 Bar Bar `json:"bar"` 288 } 289 290 elem := Foo{} 291 292 schema, err := New(reflect.TypeOf(elem), nil) 293 assert.NoError(t, err) 294 295 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 296 assert.NoError(t, err) 297 298 expected := 299 `{ 300 "type": "object", 301 "properties": { 302 "bar": { 303 "type": "object", 304 "properties": { 305 "my_slice": { 306 "type": "array", 307 "items": { 308 "type": "string" 309 } 310 } 311 }, 312 "additionalProperties": false, 313 "required": [ 314 "my_slice" 315 ] 316 } 317 }, 318 "additionalProperties": false, 319 "required": [ 320 "bar" 321 ] 322 }` 323 324 t.Log("[DEBUG] actual: ", string(jsonSchema)) 325 t.Log("[DEBUG] expected: ", expected) 326 assert.Equal(t, expected, string(jsonSchema)) 327 } 328 329 func TestMapOfPrimitivesSchema(t *testing.T) { 330 var elem map[string]int 331 332 schema, err := New(reflect.TypeOf(elem), nil) 333 assert.NoError(t, err) 334 335 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 336 assert.NoError(t, err) 337 338 expected := 339 `{ 340 "type": "object", 341 "additionalProperties": { 342 "type": "number" 343 } 344 }` 345 346 t.Log("[DEBUG] actual: ", string(jsonSchema)) 347 t.Log("[DEBUG] expected: ", expected) 348 assert.Equal(t, expected, string(jsonSchema)) 349 } 350 351 func TestMapOfStructSchema(t *testing.T) { 352 type Foo struct { 353 MyInt int `json:"my_int"` 354 } 355 356 var elem map[string]Foo 357 358 schema, err := New(reflect.TypeOf(elem), nil) 359 assert.NoError(t, err) 360 361 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 362 assert.NoError(t, err) 363 364 expected := 365 `{ 366 "type": "object", 367 "additionalProperties": { 368 "type": "object", 369 "properties": { 370 "my_int": { 371 "type": "number" 372 } 373 }, 374 "additionalProperties": false, 375 "required": [ 376 "my_int" 377 ] 378 } 379 }` 380 381 t.Log("[DEBUG] actual: ", string(jsonSchema)) 382 t.Log("[DEBUG] expected: ", expected) 383 assert.Equal(t, expected, string(jsonSchema)) 384 } 385 386 func TestMapOfMapSchema(t *testing.T) { 387 var elem map[string]map[string]int 388 389 schema, err := New(reflect.TypeOf(elem), nil) 390 assert.NoError(t, err) 391 392 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 393 assert.NoError(t, err) 394 395 expected := 396 `{ 397 "type": "object", 398 "additionalProperties": { 399 "type": "object", 400 "additionalProperties": { 401 "type": "number" 402 } 403 } 404 }` 405 406 t.Log("[DEBUG] actual: ", string(jsonSchema)) 407 t.Log("[DEBUG] expected: ", expected) 408 assert.Equal(t, expected, string(jsonSchema)) 409 } 410 411 func TestMapOfSliceSchema(t *testing.T) { 412 var elem map[string][]string 413 414 schema, err := New(reflect.TypeOf(elem), nil) 415 assert.NoError(t, err) 416 417 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 418 assert.NoError(t, err) 419 420 expected := 421 `{ 422 "type": "object", 423 "additionalProperties": { 424 "type": "array", 425 "items": { 426 "type": "string" 427 } 428 } 429 }` 430 431 t.Log("[DEBUG] actual: ", string(jsonSchema)) 432 t.Log("[DEBUG] expected: ", expected) 433 assert.Equal(t, expected, string(jsonSchema)) 434 } 435 436 func TestSliceOfPrimitivesSchema(t *testing.T) { 437 var elem []float32 438 439 schema, err := New(reflect.TypeOf(elem), nil) 440 assert.NoError(t, err) 441 442 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 443 assert.NoError(t, err) 444 445 expected := 446 `{ 447 "type": "array", 448 "items": { 449 "type": "number" 450 } 451 }` 452 453 t.Log("[DEBUG] actual: ", string(jsonSchema)) 454 t.Log("[DEBUG] expected: ", expected) 455 assert.Equal(t, expected, string(jsonSchema)) 456 } 457 458 func TestSliceOfSliceSchema(t *testing.T) { 459 var elem [][]string 460 461 schema, err := New(reflect.TypeOf(elem), nil) 462 assert.NoError(t, err) 463 464 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 465 assert.NoError(t, err) 466 467 expected := 468 `{ 469 "type": "array", 470 "items": { 471 "type": "array", 472 "items": { 473 "type": "string" 474 } 475 } 476 }` 477 478 t.Log("[DEBUG] actual: ", string(jsonSchema)) 479 t.Log("[DEBUG] expected: ", expected) 480 assert.Equal(t, expected, string(jsonSchema)) 481 } 482 483 func TestSliceOfMapSchema(t *testing.T) { 484 var elem []map[string]int 485 486 schema, err := New(reflect.TypeOf(elem), nil) 487 assert.NoError(t, err) 488 489 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 490 assert.NoError(t, err) 491 492 expected := 493 `{ 494 "type": "array", 495 "items": { 496 "type": "object", 497 "additionalProperties": { 498 "type": "number" 499 } 500 } 501 }` 502 503 t.Log("[DEBUG] actual: ", string(jsonSchema)) 504 t.Log("[DEBUG] expected: ", expected) 505 assert.Equal(t, expected, string(jsonSchema)) 506 } 507 508 func TestSliceOfStructSchema(t *testing.T) { 509 type Foo struct { 510 MyInt int `json:"my_int"` 511 } 512 513 var elem []Foo 514 515 schema, err := New(reflect.TypeOf(elem), nil) 516 assert.NoError(t, err) 517 518 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 519 assert.NoError(t, err) 520 521 expected := 522 `{ 523 "type": "array", 524 "items": { 525 "type": "object", 526 "properties": { 527 "my_int": { 528 "type": "number" 529 } 530 }, 531 "additionalProperties": false, 532 "required": [ 533 "my_int" 534 ] 535 } 536 }` 537 538 t.Log("[DEBUG] actual: ", string(jsonSchema)) 539 t.Log("[DEBUG] expected: ", expected) 540 assert.Equal(t, expected, string(jsonSchema)) 541 } 542 543 func TestEmbeddedStructSchema(t *testing.T) { 544 type Location struct { 545 Country string `json:"country"` 546 State string `json:"state,omitempty"` 547 } 548 549 type Person struct { 550 Name string `json:"name"` 551 Age int `json:"age,omitempty"` 552 Home Location `json:"home"` 553 } 554 555 type Plot struct { 556 Events map[string]Person `json:"events"` 557 } 558 559 type Story struct { 560 Plot Plot `json:"plot"` 561 *Person 562 Location 563 } 564 565 elem := Story{} 566 567 schema, err := New(reflect.TypeOf(elem), nil) 568 assert.NoError(t, err) 569 570 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 571 assert.NoError(t, err) 572 573 expected := 574 `{ 575 "type": "object", 576 "properties": { 577 "age": { 578 "type": "number" 579 }, 580 "country": { 581 "type": "string" 582 }, 583 "home": { 584 "type": "object", 585 "properties": { 586 "country": { 587 "type": "string" 588 }, 589 "state": { 590 "type": "string" 591 } 592 }, 593 "additionalProperties": false, 594 "required": [ 595 "country" 596 ] 597 }, 598 "name": { 599 "type": "string" 600 }, 601 "plot": { 602 "type": "object", 603 "properties": { 604 "events": { 605 "type": "object", 606 "additionalProperties": { 607 "type": "object", 608 "properties": { 609 "age": { 610 "type": "number" 611 }, 612 "home": { 613 "type": "object", 614 "properties": { 615 "country": { 616 "type": "string" 617 }, 618 "state": { 619 "type": "string" 620 } 621 }, 622 "additionalProperties": false, 623 "required": [ 624 "country" 625 ] 626 }, 627 "name": { 628 "type": "string" 629 } 630 }, 631 "additionalProperties": false, 632 "required": [ 633 "name", 634 "home" 635 ] 636 } 637 } 638 }, 639 "additionalProperties": false, 640 "required": [ 641 "events" 642 ] 643 }, 644 "state": { 645 "type": "string" 646 } 647 }, 648 "additionalProperties": false, 649 "required": [ 650 "plot", 651 "name", 652 "home", 653 "country" 654 ] 655 }` 656 657 t.Log("[DEBUG] actual: ", string(jsonSchema)) 658 t.Log("[DEBUG] expected: ", expected) 659 assert.Equal(t, expected, string(jsonSchema)) 660 } 661 662 func TestErrorWithTrace(t *testing.T) { 663 tracker := newTracker() 664 dummyType := reflect.TypeOf(struct{}{}) 665 err := tracker.errWithTrace("with empty trace", "root") 666 assert.ErrorContains(t, err, "with empty trace. traversal trace: root") 667 668 tracker.push(dummyType, "resources") 669 err = tracker.errWithTrace("with depth = 1", "root") 670 assert.ErrorContains(t, err, "with depth = 1. traversal trace: root -> resources") 671 672 tracker.push(dummyType, "pipelines") 673 tracker.push(dummyType, "datasets") 674 err = tracker.errWithTrace("with depth = 4", "root") 675 assert.ErrorContains(t, err, "with depth = 4. traversal trace: root -> resources -> pipelines -> datasets") 676 } 677 678 func TestNonAnnotatedFieldsAreSkipped(t *testing.T) { 679 type MyStruct struct { 680 Foo string 681 Bar int `json:"bar"` 682 } 683 684 elem := MyStruct{} 685 686 schema, err := New(reflect.TypeOf(elem), nil) 687 require.NoError(t, err) 688 689 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 690 assert.NoError(t, err) 691 692 expectedSchema := 693 `{ 694 "type": "object", 695 "properties": { 696 "bar": { 697 "type": "number" 698 } 699 }, 700 "additionalProperties": false, 701 "required": [ 702 "bar" 703 ] 704 }` 705 706 t.Log("[DEBUG] actual: ", string(jsonSchema)) 707 t.Log("[DEBUG] expected: ", expectedSchema) 708 709 assert.Equal(t, expectedSchema, string(jsonSchema)) 710 } 711 712 func TestDashFieldsAreSkipped(t *testing.T) { 713 type MyStruct struct { 714 Foo string `json:"-"` 715 Bar int `json:"bar"` 716 } 717 718 elem := MyStruct{} 719 720 schema, err := New(reflect.TypeOf(elem), nil) 721 require.NoError(t, err) 722 723 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 724 assert.NoError(t, err) 725 726 expectedSchema := 727 `{ 728 "type": "object", 729 "properties": { 730 "bar": { 731 "type": "number" 732 } 733 }, 734 "additionalProperties": false, 735 "required": [ 736 "bar" 737 ] 738 }` 739 740 t.Log("[DEBUG] actual: ", string(jsonSchema)) 741 t.Log("[DEBUG] expected: ", expectedSchema) 742 743 assert.Equal(t, expectedSchema, string(jsonSchema)) 744 } 745 746 func TestPointerInStructSchema(t *testing.T) { 747 748 type Bar struct { 749 PtrVal2 *int `json:"ptr_val2"` 750 } 751 752 type Foo struct { 753 PtrInt *int `json:"ptr_int"` 754 PtrString *string `json:"ptr_string"` 755 FloatVal float32 `json:"float_val"` 756 PtrBar *Bar `json:"ptr_bar"` 757 Bar *Bar `json:"bar"` 758 } 759 760 elem := Foo{} 761 762 schema, err := New(reflect.TypeOf(elem), nil) 763 require.NoError(t, err) 764 765 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 766 assert.NoError(t, err) 767 768 expectedSchema := 769 `{ 770 "type": "object", 771 "properties": { 772 "bar": { 773 "type": "object", 774 "properties": { 775 "ptr_val2": { 776 "type": "number" 777 } 778 }, 779 "additionalProperties": false, 780 "required": [ 781 "ptr_val2" 782 ] 783 }, 784 "float_val": { 785 "type": "number" 786 }, 787 "ptr_bar": { 788 "type": "object", 789 "properties": { 790 "ptr_val2": { 791 "type": "number" 792 } 793 }, 794 "additionalProperties": false, 795 "required": [ 796 "ptr_val2" 797 ] 798 }, 799 "ptr_int": { 800 "type": "number" 801 }, 802 "ptr_string": { 803 "type": "string" 804 } 805 }, 806 "additionalProperties": false, 807 "required": [ 808 "ptr_int", 809 "ptr_string", 810 "float_val", 811 "ptr_bar", 812 "bar" 813 ] 814 }` 815 816 t.Log("[DEBUG] actual: ", string(jsonSchema)) 817 t.Log("[DEBUG] expected: ", expectedSchema) 818 819 assert.Equal(t, expectedSchema, string(jsonSchema)) 820 } 821 822 func TestGenericSchema(t *testing.T) { 823 type Person struct { 824 Name string `json:"name"` 825 Age int `json:"age,omitempty"` 826 } 827 828 type Plot struct { 829 Stakes []string `json:"stakes"` 830 Deaths []Person `json:"deaths"` 831 Murders map[string]Person `json:"murders"` 832 } 833 834 type Wedding struct { 835 Hidden string `json:","` 836 Groom Person `json:"groom"` 837 Bride Person `json:"bride"` 838 Plots []Plot `json:"plots"` 839 } 840 841 type Story struct { 842 Hero *Person `json:"hero"` 843 Villian Person `json:"villian,omitempty"` 844 Weddings []Wedding `json:"weddings"` 845 } 846 847 elem := Story{} 848 849 schema, err := New(reflect.TypeOf(elem), nil) 850 assert.NoError(t, err) 851 852 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 853 assert.NoError(t, err) 854 855 expected := 856 `{ 857 "type": "object", 858 "properties": { 859 "hero": { 860 "type": "object", 861 "properties": { 862 "age": { 863 "type": "number" 864 }, 865 "name": { 866 "type": "string" 867 } 868 }, 869 "additionalProperties": false, 870 "required": [ 871 "name" 872 ] 873 }, 874 "villian": { 875 "type": "object", 876 "properties": { 877 "age": { 878 "type": "number" 879 }, 880 "name": { 881 "type": "string" 882 } 883 }, 884 "additionalProperties": false, 885 "required": [ 886 "name" 887 ] 888 }, 889 "weddings": { 890 "type": "array", 891 "items": { 892 "type": "object", 893 "properties": { 894 "bride": { 895 "type": "object", 896 "properties": { 897 "age": { 898 "type": "number" 899 }, 900 "name": { 901 "type": "string" 902 } 903 }, 904 "additionalProperties": false, 905 "required": [ 906 "name" 907 ] 908 }, 909 "groom": { 910 "type": "object", 911 "properties": { 912 "age": { 913 "type": "number" 914 }, 915 "name": { 916 "type": "string" 917 } 918 }, 919 "additionalProperties": false, 920 "required": [ 921 "name" 922 ] 923 }, 924 "plots": { 925 "type": "array", 926 "items": { 927 "type": "object", 928 "properties": { 929 "deaths": { 930 "type": "array", 931 "items": { 932 "type": "object", 933 "properties": { 934 "age": { 935 "type": "number" 936 }, 937 "name": { 938 "type": "string" 939 } 940 }, 941 "additionalProperties": false, 942 "required": [ 943 "name" 944 ] 945 } 946 }, 947 "murders": { 948 "type": "object", 949 "additionalProperties": { 950 "type": "object", 951 "properties": { 952 "age": { 953 "type": "number" 954 }, 955 "name": { 956 "type": "string" 957 } 958 }, 959 "additionalProperties": false, 960 "required": [ 961 "name" 962 ] 963 } 964 }, 965 "stakes": { 966 "type": "array", 967 "items": { 968 "type": "string" 969 } 970 } 971 }, 972 "additionalProperties": false, 973 "required": [ 974 "stakes", 975 "deaths", 976 "murders" 977 ] 978 } 979 } 980 }, 981 "additionalProperties": false, 982 "required": [ 983 "groom", 984 "bride", 985 "plots" 986 ] 987 } 988 } 989 }, 990 "additionalProperties": false, 991 "required": [ 992 "hero", 993 "weddings" 994 ] 995 }` 996 997 t.Log("[DEBUG] actual: ", string(jsonSchema)) 998 t.Log("[DEBUG] expected: ", expected) 999 assert.Equal(t, expected, string(jsonSchema)) 1000 } 1001 1002 func TestFieldsWithoutOmitEmptyAreRequired(t *testing.T) { 1003 1004 type Papaya struct { 1005 A int `json:"a,string,omitempty"` 1006 B string `json:"b"` 1007 } 1008 1009 type MyStruct struct { 1010 Foo string `json:"-,omitempty"` 1011 Bar int `json:"bar"` 1012 Apple int `json:"apple,omitempty"` 1013 Mango int `json:",omitempty"` 1014 Guava int `json:","` 1015 Papaya *Papaya `json:"papaya,"` 1016 } 1017 1018 elem := MyStruct{} 1019 1020 schema, err := New(reflect.TypeOf(elem), nil) 1021 require.NoError(t, err) 1022 1023 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 1024 assert.NoError(t, err) 1025 1026 expectedSchema := 1027 `{ 1028 "type": "object", 1029 "properties": { 1030 "apple": { 1031 "type": "number" 1032 }, 1033 "bar": { 1034 "type": "number" 1035 }, 1036 "papaya": { 1037 "type": "object", 1038 "properties": { 1039 "a": { 1040 "type": "number" 1041 }, 1042 "b": { 1043 "type": "string" 1044 } 1045 }, 1046 "additionalProperties": false, 1047 "required": [ 1048 "b" 1049 ] 1050 } 1051 }, 1052 "additionalProperties": false, 1053 "required": [ 1054 "bar", 1055 "papaya" 1056 ] 1057 }` 1058 1059 t.Log("[DEBUG] actual: ", string(jsonSchema)) 1060 t.Log("[DEBUG] expected: ", expectedSchema) 1061 1062 assert.Equal(t, expectedSchema, string(jsonSchema)) 1063 } 1064 1065 func TestDocIngestionForObject(t *testing.T) { 1066 docs := &Docs{ 1067 Description: "docs for root", 1068 Properties: map[string]*Docs{ 1069 "my_struct": { 1070 Description: "docs for my struct", 1071 Properties: map[string]*Docs{ 1072 "a": { 1073 Description: "docs for a", 1074 }, 1075 "c": { 1076 Description: "docs for c which does not exist on my_struct", 1077 }, 1078 }, 1079 }, 1080 }, 1081 } 1082 1083 type MyStruct struct { 1084 A string `json:"a"` 1085 B int `json:"b"` 1086 } 1087 1088 type Root struct { 1089 MyStruct *MyStruct `json:"my_struct"` 1090 } 1091 1092 elem := Root{} 1093 1094 schema, err := New(reflect.TypeOf(elem), docs) 1095 require.NoError(t, err) 1096 1097 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 1098 assert.NoError(t, err) 1099 1100 expectedSchema := 1101 `{ 1102 "type": "object", 1103 "description": "docs for root", 1104 "properties": { 1105 "my_struct": { 1106 "type": "object", 1107 "description": "docs for my struct", 1108 "properties": { 1109 "a": { 1110 "type": "string", 1111 "description": "docs for a" 1112 }, 1113 "b": { 1114 "type": "number" 1115 } 1116 }, 1117 "additionalProperties": false, 1118 "required": [ 1119 "a", 1120 "b" 1121 ] 1122 } 1123 }, 1124 "additionalProperties": false, 1125 "required": [ 1126 "my_struct" 1127 ] 1128 }` 1129 1130 t.Log("[DEBUG] actual: ", string(jsonSchema)) 1131 t.Log("[DEBUG] expected: ", expectedSchema) 1132 1133 assert.Equal(t, expectedSchema, string(jsonSchema)) 1134 } 1135 1136 func TestDocIngestionForSlice(t *testing.T) { 1137 docs := &Docs{ 1138 Description: "docs for root", 1139 Properties: map[string]*Docs{ 1140 "my_slice": { 1141 Description: "docs for my slice", 1142 Items: &Docs{ 1143 Properties: map[string]*Docs{ 1144 "guava": { 1145 Description: "docs for guava", 1146 }, 1147 "pineapple": { 1148 Description: "docs for pineapple", 1149 }, 1150 "watermelon": { 1151 Description: "docs for watermelon which does not exist in schema", 1152 }, 1153 }, 1154 }, 1155 }, 1156 }, 1157 } 1158 1159 type Bar struct { 1160 Guava int `json:"guava"` 1161 Pineapple int `json:"pineapple"` 1162 } 1163 1164 type Root struct { 1165 MySlice []Bar `json:"my_slice"` 1166 } 1167 1168 elem := Root{} 1169 1170 schema, err := New(reflect.TypeOf(elem), docs) 1171 require.NoError(t, err) 1172 1173 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 1174 assert.NoError(t, err) 1175 1176 expectedSchema := 1177 `{ 1178 "type": "object", 1179 "description": "docs for root", 1180 "properties": { 1181 "my_slice": { 1182 "type": "array", 1183 "description": "docs for my slice", 1184 "items": { 1185 "type": "object", 1186 "properties": { 1187 "guava": { 1188 "type": "number", 1189 "description": "docs for guava" 1190 }, 1191 "pineapple": { 1192 "type": "number", 1193 "description": "docs for pineapple" 1194 } 1195 }, 1196 "additionalProperties": false, 1197 "required": [ 1198 "guava", 1199 "pineapple" 1200 ] 1201 } 1202 } 1203 }, 1204 "additionalProperties": false, 1205 "required": [ 1206 "my_slice" 1207 ] 1208 }` 1209 1210 t.Log("[DEBUG] actual: ", string(jsonSchema)) 1211 t.Log("[DEBUG] expected: ", expectedSchema) 1212 1213 assert.Equal(t, expectedSchema, string(jsonSchema)) 1214 } 1215 1216 func TestDocIngestionForMap(t *testing.T) { 1217 docs := &Docs{ 1218 Description: "docs for root", 1219 Properties: map[string]*Docs{ 1220 "my_map": { 1221 Description: "docs for my map", 1222 AdditionalProperties: &Docs{ 1223 Properties: map[string]*Docs{ 1224 "apple": { 1225 Description: "docs for apple", 1226 }, 1227 "mango": { 1228 Description: "docs for mango", 1229 }, 1230 "watermelon": { 1231 Description: "docs for watermelon which does not exist in schema", 1232 }, 1233 "papaya": { 1234 Description: "docs for papaya which does not exist in schema", 1235 }, 1236 }, 1237 }, 1238 }, 1239 }, 1240 } 1241 1242 type Foo struct { 1243 Apple int `json:"apple"` 1244 Mango int `json:"mango"` 1245 } 1246 1247 type Root struct { 1248 MyMap map[string]*Foo `json:"my_map"` 1249 } 1250 1251 elem := Root{} 1252 1253 schema, err := New(reflect.TypeOf(elem), docs) 1254 require.NoError(t, err) 1255 1256 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 1257 assert.NoError(t, err) 1258 1259 expectedSchema := 1260 `{ 1261 "type": "object", 1262 "description": "docs for root", 1263 "properties": { 1264 "my_map": { 1265 "type": "object", 1266 "description": "docs for my map", 1267 "additionalProperties": { 1268 "type": "object", 1269 "properties": { 1270 "apple": { 1271 "type": "number", 1272 "description": "docs for apple" 1273 }, 1274 "mango": { 1275 "type": "number", 1276 "description": "docs for mango" 1277 } 1278 }, 1279 "additionalProperties": false, 1280 "required": [ 1281 "apple", 1282 "mango" 1283 ] 1284 } 1285 } 1286 }, 1287 "additionalProperties": false, 1288 "required": [ 1289 "my_map" 1290 ] 1291 }` 1292 1293 t.Log("[DEBUG] actual: ", string(jsonSchema)) 1294 t.Log("[DEBUG] expected: ", expectedSchema) 1295 1296 assert.Equal(t, expectedSchema, string(jsonSchema)) 1297 } 1298 1299 func TestDocIngestionForTopLevelPrimitive(t *testing.T) { 1300 docs := &Docs{ 1301 Description: "docs for root", 1302 Properties: map[string]*Docs{ 1303 "my_val": { 1304 Description: "docs for my val", 1305 }, 1306 }, 1307 } 1308 1309 type Root struct { 1310 MyVal int `json:"my_val"` 1311 } 1312 1313 elem := Root{} 1314 1315 schema, err := New(reflect.TypeOf(elem), docs) 1316 require.NoError(t, err) 1317 1318 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 1319 assert.NoError(t, err) 1320 1321 expectedSchema := 1322 `{ 1323 "type": "object", 1324 "description": "docs for root", 1325 "properties": { 1326 "my_val": { 1327 "type": "number", 1328 "description": "docs for my val" 1329 } 1330 }, 1331 "additionalProperties": false, 1332 "required": [ 1333 "my_val" 1334 ] 1335 }` 1336 1337 t.Log("[DEBUG] actual: ", string(jsonSchema)) 1338 t.Log("[DEBUG] expected: ", expectedSchema) 1339 1340 assert.Equal(t, expectedSchema, string(jsonSchema)) 1341 } 1342 1343 func TestErrorOnMapWithoutStringKey(t *testing.T) { 1344 type Foo struct { 1345 Bar map[int]string `json:"bar"` 1346 } 1347 elem := Foo{} 1348 _, err := New(reflect.TypeOf(elem), nil) 1349 assert.ErrorContains(t, err, "only strings map keys are valid. key type: int") 1350 } 1351 1352 func TestErrorIfStructRefersToItself(t *testing.T) { 1353 type Foo struct { 1354 MyFoo *Foo `json:"my_foo"` 1355 } 1356 1357 elem := Foo{} 1358 _, err := New(reflect.TypeOf(elem), nil) 1359 assert.ErrorContains(t, err, "cycle detected. traversal trace: root -> my_foo") 1360 } 1361 1362 func TestErrorIfStructHasLoop(t *testing.T) { 1363 type Apple struct { 1364 MyVal int `json:"my_val"` 1365 MyMango struct { 1366 MyGuava struct { 1367 MyPapaya struct { 1368 MyApple *Apple `json:"my_apple"` 1369 } `json:"my_papaya"` 1370 } `json:"my_guava"` 1371 } `json:"my_mango"` 1372 } 1373 1374 elem := Apple{} 1375 _, err := New(reflect.TypeOf(elem), nil) 1376 assert.ErrorContains(t, err, "cycle detected. traversal trace: root -> my_mango -> my_guava -> my_papaya -> my_apple") 1377 } 1378 1379 func TestInterfaceGeneratesEmptySchema(t *testing.T) { 1380 type Foo struct { 1381 Apple int `json:"apple"` 1382 Mango interface{} `json:"mango"` 1383 } 1384 1385 elem := Foo{} 1386 1387 schema, err := New(reflect.TypeOf(elem), nil) 1388 assert.NoError(t, err) 1389 1390 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 1391 assert.NoError(t, err) 1392 1393 expected := 1394 `{ 1395 "type": "object", 1396 "properties": { 1397 "apple": { 1398 "type": "number" 1399 }, 1400 "mango": {} 1401 }, 1402 "additionalProperties": false, 1403 "required": [ 1404 "apple", 1405 "mango" 1406 ] 1407 }` 1408 1409 t.Log("[DEBUG] actual: ", string(jsonSchema)) 1410 t.Log("[DEBUG] expected: ", expected) 1411 assert.Equal(t, expected, string(jsonSchema)) 1412 } 1413 1414 func TestBundleReadOnlytag(t *testing.T) { 1415 type Pokemon struct { 1416 Pikachu string `json:"pikachu" bundle:"readonly"` 1417 Raichu string `json:"raichu"` 1418 } 1419 1420 type Foo struct { 1421 Pokemon *Pokemon `json:"pokemon"` 1422 Apple int `json:"apple"` 1423 Mango string `json:"mango" bundle:"readonly"` 1424 } 1425 1426 elem := Foo{} 1427 1428 schema, err := New(reflect.TypeOf(elem), nil) 1429 assert.NoError(t, err) 1430 1431 jsonSchema, err := json.MarshalIndent(schema, " ", " ") 1432 assert.NoError(t, err) 1433 1434 expected := 1435 `{ 1436 "type": "object", 1437 "properties": { 1438 "apple": { 1439 "type": "number" 1440 }, 1441 "pokemon": { 1442 "type": "object", 1443 "properties": { 1444 "raichu": { 1445 "type": "string" 1446 } 1447 }, 1448 "additionalProperties": false, 1449 "required": [ 1450 "raichu" 1451 ] 1452 } 1453 }, 1454 "additionalProperties": false, 1455 "required": [ 1456 "pokemon", 1457 "apple" 1458 ] 1459 }` 1460 1461 t.Log("[DEBUG] actual: ", string(jsonSchema)) 1462 t.Log("[DEBUG] expected: ", expected) 1463 assert.Equal(t, expected, string(jsonSchema)) 1464 }