1/* 2Copyright 2016 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 generators 18 19import ( 20 "bytes" 21 "fmt" 22 "path/filepath" 23 "strings" 24 "testing" 25 26 "github.com/stretchr/testify/assert" 27 28 "k8s.io/gengo/generator" 29 "k8s.io/gengo/namer" 30 "k8s.io/gengo/parser" 31 "k8s.io/gengo/types" 32) 33 34func construct(t *testing.T, files map[string]string, testNamer namer.Namer) (*parser.Builder, types.Universe, []*types.Type) { 35 b := parser.New() 36 for name, src := range files { 37 if err := b.AddFileForTest(filepath.Dir(name), name, []byte(src)); err != nil { 38 t.Fatal(err) 39 } 40 } 41 u, err := b.FindTypes() 42 if err != nil { 43 t.Fatal(err) 44 } 45 orderer := namer.Orderer{Namer: testNamer} 46 o := orderer.OrderUniverse(u) 47 return b, u, o 48} 49 50func testOpenAPITypeWriter(t *testing.T, code string) (error, error, *assert.Assertions, *bytes.Buffer, *bytes.Buffer) { 51 assert := assert.New(t) 52 var testFiles = map[string]string{ 53 "base/foo/bar.go": code, 54 } 55 rawNamer := namer.NewRawNamer("o", nil) 56 namers := namer.NameSystems{ 57 "raw": namer.NewRawNamer("", nil), 58 "private": &namer.NameStrategy{ 59 Join: func(pre string, in []string, post string) string { 60 return strings.Join(in, "_") 61 }, 62 PrependPackageNames: 4, // enough to fully qualify from k8s.io/api/... 63 }, 64 } 65 builder, universe, _ := construct(t, testFiles, rawNamer) 66 context, err := generator.NewContext(builder, namers, "raw") 67 if err != nil { 68 t.Fatal(err) 69 } 70 blahT := universe.Type(types.Name{Package: "base/foo", Name: "Blah"}) 71 72 callBuffer := &bytes.Buffer{} 73 callSW := generator.NewSnippetWriter(callBuffer, context, "$", "$") 74 callError := newOpenAPITypeWriter(callSW, context).generateCall(blahT) 75 76 funcBuffer := &bytes.Buffer{} 77 funcSW := generator.NewSnippetWriter(funcBuffer, context, "$", "$") 78 funcError := newOpenAPITypeWriter(funcSW, context).generate(blahT) 79 80 return callError, funcError, assert, callBuffer, funcBuffer 81} 82 83func TestSimple(t *testing.T) { 84 callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 85package foo 86 87// Blah is a test. 88// +k8s:openapi-gen=true 89// +k8s:openapi-gen=x-kubernetes-type-tag:type_test 90type Blah struct { 91 // A simple string 92 String string 93 // A simple int 94 Int int `+"`"+`json:",omitempty"`+"`"+` 95 // An int considered string simple int 96 IntString int `+"`"+`json:",string"`+"`"+` 97 // A simple int64 98 Int64 int64 99 // A simple int32 100 Int32 int32 101 // A simple int16 102 Int16 int16 103 // A simple int8 104 Int8 int8 105 // A simple int 106 Uint uint 107 // A simple int64 108 Uint64 uint64 109 // A simple int32 110 Uint32 uint32 111 // A simple int16 112 Uint16 uint16 113 // A simple int8 114 Uint8 uint8 115 // A simple byte 116 Byte byte 117 // A simple boolean 118 Bool bool 119 // A simple float64 120 Float64 float64 121 // A simple float32 122 Float32 float32 123 // a base64 encoded characters 124 ByteArray []byte 125 // a member with an extension 126 // +k8s:openapi-gen=x-kubernetes-member-tag:member_test 127 WithExtension string 128 // a member with struct tag as extension 129 // +patchStrategy=merge 130 // +patchMergeKey=pmk 131 WithStructTagExtension string `+"`"+`patchStrategy:"merge" patchMergeKey:"pmk"`+"`"+` 132 // a member with a list type 133 // +listType=atomic 134 // +default=["foo", "bar"] 135 WithListType []string 136 // a member with a map type 137 // +listType=atomic 138 // +default={"foo": "bar", "fizz": "buzz"} 139 Map map[string]string 140 // a member with a string pointer 141 // +default="foo" 142 StringPointer *string 143 // an int member with a default 144 // +default=1 145 OmittedInt int `+"`"+`json:"omitted,omitempty"`+"`"+` 146} 147 `) 148 if callErr != nil { 149 t.Fatal(callErr) 150 } 151 if funcErr != nil { 152 t.Fatal(funcErr) 153 } 154 assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), 155`, callBuffer.String()) 156 assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 157return common.OpenAPIDefinition{ 158Schema: spec.Schema{ 159SchemaProps: spec.SchemaProps{ 160Description: "Blah is a test.", 161Type: []string{"object"}, 162Properties: map[string]spec.Schema{ 163"String": { 164SchemaProps: spec.SchemaProps{ 165Description: "A simple string", 166Default: "", 167Type: []string{"string"}, 168Format: "", 169}, 170}, 171"Int64": { 172SchemaProps: spec.SchemaProps{ 173Description: "A simple int64", 174Default: 0, 175Type: []string{"integer"}, 176Format: "int64", 177}, 178}, 179"Int32": { 180SchemaProps: spec.SchemaProps{ 181Description: "A simple int32", 182Default: 0, 183Type: []string{"integer"}, 184Format: "int32", 185}, 186}, 187"Int16": { 188SchemaProps: spec.SchemaProps{ 189Description: "A simple int16", 190Default: 0, 191Type: []string{"integer"}, 192Format: "int32", 193}, 194}, 195"Int8": { 196SchemaProps: spec.SchemaProps{ 197Description: "A simple int8", 198Default: 0, 199Type: []string{"integer"}, 200Format: "byte", 201}, 202}, 203"Uint": { 204SchemaProps: spec.SchemaProps{ 205Description: "A simple int", 206Default: 0, 207Type: []string{"integer"}, 208Format: "int32", 209}, 210}, 211"Uint64": { 212SchemaProps: spec.SchemaProps{ 213Description: "A simple int64", 214Default: 0, 215Type: []string{"integer"}, 216Format: "int64", 217}, 218}, 219"Uint32": { 220SchemaProps: spec.SchemaProps{ 221Description: "A simple int32", 222Default: 0, 223Type: []string{"integer"}, 224Format: "int64", 225}, 226}, 227"Uint16": { 228SchemaProps: spec.SchemaProps{ 229Description: "A simple int16", 230Default: 0, 231Type: []string{"integer"}, 232Format: "int32", 233}, 234}, 235"Uint8": { 236SchemaProps: spec.SchemaProps{ 237Description: "A simple int8", 238Default: 0, 239Type: []string{"integer"}, 240Format: "byte", 241}, 242}, 243"Byte": { 244SchemaProps: spec.SchemaProps{ 245Description: "A simple byte", 246Default: 0, 247Type: []string{"integer"}, 248Format: "byte", 249}, 250}, 251"Bool": { 252SchemaProps: spec.SchemaProps{ 253Description: "A simple boolean", 254Default: false, 255Type: []string{"boolean"}, 256Format: "", 257}, 258}, 259"Float64": { 260SchemaProps: spec.SchemaProps{ 261Description: "A simple float64", 262Default: 0, 263Type: []string{"number"}, 264Format: "double", 265}, 266}, 267"Float32": { 268SchemaProps: spec.SchemaProps{ 269Description: "A simple float32", 270Default: 0, 271Type: []string{"number"}, 272Format: "float", 273}, 274}, 275"ByteArray": { 276SchemaProps: spec.SchemaProps{ 277Description: "a base64 encoded characters", 278Type: []string{"string"}, 279Format: "byte", 280}, 281}, 282"WithExtension": { 283VendorExtensible: spec.VendorExtensible{ 284Extensions: spec.Extensions{ 285"x-kubernetes-member-tag": "member_test", 286}, 287}, 288SchemaProps: spec.SchemaProps{ 289Description: "a member with an extension", 290Default: "", 291Type: []string{"string"}, 292Format: "", 293}, 294}, 295"WithStructTagExtension": { 296VendorExtensible: spec.VendorExtensible{ 297Extensions: spec.Extensions{ 298"x-kubernetes-patch-merge-key": "pmk", 299"x-kubernetes-patch-strategy": "merge", 300}, 301}, 302SchemaProps: spec.SchemaProps{ 303Description: "a member with struct tag as extension", 304Default: "", 305Type: []string{"string"}, 306Format: "", 307}, 308}, 309"WithListType": { 310VendorExtensible: spec.VendorExtensible{ 311Extensions: spec.Extensions{ 312"x-kubernetes-list-type": "atomic", 313}, 314}, 315SchemaProps: spec.SchemaProps{ 316Description: "a member with a list type", 317Default: []interface {}{"foo", "bar"}, 318Type: []string{"array"}, 319Items: &spec.SchemaOrArray{ 320Schema: &spec.Schema{ 321SchemaProps: spec.SchemaProps{ 322Default: "", 323Type: []string{"string"}, 324Format: "", 325}, 326}, 327}, 328}, 329}, 330"Map": { 331VendorExtensible: spec.VendorExtensible{ 332Extensions: spec.Extensions{ 333"x-kubernetes-list-type": "atomic", 334}, 335}, 336SchemaProps: spec.SchemaProps{ 337Description: "a member with a map type", 338Default: map[string]interface {}{"fizz":"buzz", "foo":"bar"}, 339Type: []string{"object"}, 340AdditionalProperties: &spec.SchemaOrBool{ 341Allows: true, 342Schema: &spec.Schema{ 343SchemaProps: spec.SchemaProps{ 344Default: "", 345Type: []string{"string"}, 346Format: "", 347}, 348}, 349}, 350}, 351}, 352"StringPointer": { 353SchemaProps: spec.SchemaProps{ 354Description: "a member with a string pointer", 355Default: "foo", 356Type: []string{"string"}, 357Format: "", 358}, 359}, 360"omitted": { 361SchemaProps: spec.SchemaProps{ 362Description: "an int member with a default", 363Default: 1, 364Type: []string{"integer"}, 365Format: "int32", 366}, 367}, 368}, 369Required: []string{"String","Int64","Int32","Int16","Int8","Uint","Uint64","Uint32","Uint16","Uint8","Byte","Bool","Float64","Float32","ByteArray","WithExtension","WithStructTagExtension","WithListType","Map","StringPointer"}, 370}, 371VendorExtensible: spec.VendorExtensible{ 372Extensions: spec.Extensions{ 373"x-kubernetes-type-tag": "type_test", 374}, 375}, 376}, 377} 378} 379 380`, funcBuffer.String()) 381} 382 383func TestEmptyProperties(t *testing.T) { 384 callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 385package foo 386 387// Blah demonstrate a struct without fields. 388type Blah struct { 389} 390 `) 391 if callErr != nil { 392 t.Fatal(callErr) 393 } 394 if funcErr != nil { 395 t.Fatal(funcErr) 396 } 397 assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), 398`, callBuffer.String()) 399 assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 400return common.OpenAPIDefinition{ 401Schema: spec.Schema{ 402SchemaProps: spec.SchemaProps{ 403Description: "Blah demonstrate a struct without fields.", 404Type: []string{"object"}, 405}, 406}, 407} 408} 409 410`, funcBuffer.String()) 411} 412 413func TestNestedStruct(t *testing.T) { 414 callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 415package foo 416 417// Nested is used as struct field 418type Nested struct { 419 // A simple string 420 String string 421} 422 423// Blah demonstrate a struct with struct field. 424type Blah struct { 425 // A struct field 426 Field Nested 427} 428 `) 429 if callErr != nil { 430 t.Fatal(callErr) 431 } 432 if funcErr != nil { 433 t.Fatal(funcErr) 434 } 435 assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), 436`, callBuffer.String()) 437 assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 438return common.OpenAPIDefinition{ 439Schema: spec.Schema{ 440SchemaProps: spec.SchemaProps{ 441Description: "Blah demonstrate a struct with struct field.", 442Type: []string{"object"}, 443Properties: map[string]spec.Schema{ 444"Field": { 445SchemaProps: spec.SchemaProps{ 446Description: "A struct field", 447Default: map[string]interface {}{}, 448Ref: ref("base/foo.Nested"), 449}, 450}, 451}, 452Required: []string{"Field"}, 453}, 454}, 455Dependencies: []string{ 456"base/foo.Nested",}, 457} 458} 459 460`, funcBuffer.String()) 461} 462 463func TestNestedStructPointer(t *testing.T) { 464 callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 465package foo 466 467// Nested is used as struct pointer field 468type Nested struct { 469 // A simple string 470 String string 471} 472 473// Blah demonstrate a struct with struct pointer field. 474type Blah struct { 475 // A struct pointer field 476 Field *Nested 477} 478 `) 479 if callErr != nil { 480 t.Fatal(callErr) 481 } 482 if funcErr != nil { 483 t.Fatal(funcErr) 484 } 485 assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), 486`, callBuffer.String()) 487 assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 488return common.OpenAPIDefinition{ 489Schema: spec.Schema{ 490SchemaProps: spec.SchemaProps{ 491Description: "Blah demonstrate a struct with struct pointer field.", 492Type: []string{"object"}, 493Properties: map[string]spec.Schema{ 494"Field": { 495SchemaProps: spec.SchemaProps{ 496Description: "A struct pointer field", 497Ref: ref("base/foo.Nested"), 498}, 499}, 500}, 501Required: []string{"Field"}, 502}, 503}, 504Dependencies: []string{ 505"base/foo.Nested",}, 506} 507} 508 509`, funcBuffer.String()) 510} 511 512func TestEmbeddedStruct(t *testing.T) { 513 callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 514package foo 515 516// Nested is used as embedded struct field 517type Nested struct { 518 // A simple string 519 String string 520} 521 522// Blah demonstrate a struct with embedded struct field. 523type Blah struct { 524 // An embedded struct field 525 Nested 526} 527 `) 528 if callErr != nil { 529 t.Fatal(callErr) 530 } 531 if funcErr != nil { 532 t.Fatal(funcErr) 533 } 534 assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), 535`, callBuffer.String()) 536 assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 537return common.OpenAPIDefinition{ 538Schema: spec.Schema{ 539SchemaProps: spec.SchemaProps{ 540Description: "Blah demonstrate a struct with embedded struct field.", 541Type: []string{"object"}, 542Properties: map[string]spec.Schema{ 543"Nested": { 544SchemaProps: spec.SchemaProps{ 545Description: "An embedded struct field", 546Default: map[string]interface {}{}, 547Ref: ref("base/foo.Nested"), 548}, 549}, 550}, 551Required: []string{"Nested"}, 552}, 553}, 554Dependencies: []string{ 555"base/foo.Nested",}, 556} 557} 558 559`, funcBuffer.String()) 560} 561 562func TestEmbeddedInlineStruct(t *testing.T) { 563 callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 564package foo 565 566// Nested is used as embedded inline struct field 567type Nested struct { 568 // A simple string 569 String string 570} 571 572// Blah demonstrate a struct with embedded inline struct field. 573type Blah struct { 574 // An embedded inline struct field 575 Nested `+"`"+`json:",inline,omitempty"`+"`"+` 576} 577 `) 578 if callErr != nil { 579 t.Fatal(callErr) 580 } 581 if funcErr != nil { 582 t.Fatal(funcErr) 583 } 584 assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), 585`, callBuffer.String()) 586 assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 587return common.OpenAPIDefinition{ 588Schema: spec.Schema{ 589SchemaProps: spec.SchemaProps{ 590Description: "Blah demonstrate a struct with embedded inline struct field.", 591Type: []string{"object"}, 592Properties: map[string]spec.Schema{ 593"String": { 594SchemaProps: spec.SchemaProps{ 595Description: "A simple string", 596Default: "", 597Type: []string{"string"}, 598Format: "", 599}, 600}, 601}, 602Required: []string{"String"}, 603}, 604}, 605} 606} 607 608`, funcBuffer.String()) 609} 610 611func TestEmbeddedInlineStructPointer(t *testing.T) { 612 callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 613package foo 614 615// Nested is used as embedded inline struct pointer field. 616type Nested struct { 617 // A simple string 618 String string 619} 620 621// Blah demonstrate a struct with embedded inline struct pointer field. 622type Blah struct { 623 // An embedded inline struct pointer field 624 *Nested `+"`"+`json:",inline,omitempty"`+"`"+` 625} 626 `) 627 if callErr != nil { 628 t.Fatal(callErr) 629 } 630 if funcErr != nil { 631 t.Fatal(funcErr) 632 } 633 assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), 634`, callBuffer.String()) 635 assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 636return common.OpenAPIDefinition{ 637Schema: spec.Schema{ 638SchemaProps: spec.SchemaProps{ 639Description: "Blah demonstrate a struct with embedded inline struct pointer field.", 640Type: []string{"object"}, 641Properties: map[string]spec.Schema{ 642"String": { 643SchemaProps: spec.SchemaProps{ 644Description: "A simple string", 645Default: "", 646Type: []string{"string"}, 647Format: "", 648}, 649}, 650}, 651Required: []string{"String"}, 652}, 653}, 654} 655} 656 657`, funcBuffer.String()) 658} 659 660func TestNestedMapString(t *testing.T) { 661 callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 662package foo 663 664// Map sample tests openAPIGen.generateMapProperty method. 665type Blah struct { 666 // A sample String to String map 667 StringToArray map[string]map[string]string 668} 669 `) 670 if callErr != nil { 671 t.Fatal(callErr) 672 } 673 if funcErr != nil { 674 t.Fatal(funcErr) 675 } 676 assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), 677`, callBuffer.String()) 678 assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 679return common.OpenAPIDefinition{ 680Schema: spec.Schema{ 681SchemaProps: spec.SchemaProps{ 682Description: "Map sample tests openAPIGen.generateMapProperty method.", 683Type: []string{"object"}, 684Properties: map[string]spec.Schema{ 685"StringToArray": { 686SchemaProps: spec.SchemaProps{ 687Description: "A sample String to String map", 688Type: []string{"object"}, 689AdditionalProperties: &spec.SchemaOrBool{ 690Allows: true, 691Schema: &spec.Schema{ 692SchemaProps: spec.SchemaProps{ 693Type: []string{"object"}, 694AdditionalProperties: &spec.SchemaOrBool{ 695Allows: true, 696Schema: &spec.Schema{ 697SchemaProps: spec.SchemaProps{ 698Default: "", 699Type: []string{"string"}, 700Format: "", 701}, 702}, 703}, 704}, 705}, 706}, 707}, 708}, 709}, 710Required: []string{"StringToArray"}, 711}, 712}, 713} 714} 715 716`, funcBuffer.String()) 717} 718 719func TestNestedMapInt(t *testing.T) { 720 callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 721package foo 722 723// Map sample tests openAPIGen.generateMapProperty method. 724type Blah struct { 725 // A sample String to String map 726 StringToArray map[string]map[string]int 727} 728 `) 729 if callErr != nil { 730 t.Fatal(callErr) 731 } 732 if funcErr != nil { 733 t.Fatal(funcErr) 734 } 735 assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), 736`, callBuffer.String()) 737 assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 738return common.OpenAPIDefinition{ 739Schema: spec.Schema{ 740SchemaProps: spec.SchemaProps{ 741Description: "Map sample tests openAPIGen.generateMapProperty method.", 742Type: []string{"object"}, 743Properties: map[string]spec.Schema{ 744"StringToArray": { 745SchemaProps: spec.SchemaProps{ 746Description: "A sample String to String map", 747Type: []string{"object"}, 748AdditionalProperties: &spec.SchemaOrBool{ 749Allows: true, 750Schema: &spec.Schema{ 751SchemaProps: spec.SchemaProps{ 752Type: []string{"object"}, 753AdditionalProperties: &spec.SchemaOrBool{ 754Allows: true, 755Schema: &spec.Schema{ 756SchemaProps: spec.SchemaProps{ 757Default: 0, 758Type: []string{"integer"}, 759Format: "int32", 760}, 761}, 762}, 763}, 764}, 765}, 766}, 767}, 768}, 769Required: []string{"StringToArray"}, 770}, 771}, 772} 773} 774 775`, funcBuffer.String()) 776} 777 778func TestNestedMapBoolean(t *testing.T) { 779 callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 780package foo 781 782// Map sample tests openAPIGen.generateMapProperty method. 783type Blah struct { 784 // A sample String to String map 785 StringToArray map[string]map[string]bool 786} 787 `) 788 if callErr != nil { 789 t.Fatal(callErr) 790 } 791 if funcErr != nil { 792 t.Fatal(funcErr) 793 } 794 assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), 795`, callBuffer.String()) 796 assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 797return common.OpenAPIDefinition{ 798Schema: spec.Schema{ 799SchemaProps: spec.SchemaProps{ 800Description: "Map sample tests openAPIGen.generateMapProperty method.", 801Type: []string{"object"}, 802Properties: map[string]spec.Schema{ 803"StringToArray": { 804SchemaProps: spec.SchemaProps{ 805Description: "A sample String to String map", 806Type: []string{"object"}, 807AdditionalProperties: &spec.SchemaOrBool{ 808Allows: true, 809Schema: &spec.Schema{ 810SchemaProps: spec.SchemaProps{ 811Type: []string{"object"}, 812AdditionalProperties: &spec.SchemaOrBool{ 813Allows: true, 814Schema: &spec.Schema{ 815SchemaProps: spec.SchemaProps{ 816Default: false, 817Type: []string{"boolean"}, 818Format: "", 819}, 820}, 821}, 822}, 823}, 824}, 825}, 826}, 827}, 828Required: []string{"StringToArray"}, 829}, 830}, 831} 832} 833 834`, funcBuffer.String()) 835} 836 837func TestFailingSample1(t *testing.T) { 838 _, funcErr, assert, _, _ := testOpenAPITypeWriter(t, ` 839package foo 840 841// Map sample tests openAPIGen.generateMapProperty method. 842type Blah struct { 843 // A sample String to String map 844 StringToArray map[string]map[string]map[int]string 845} 846 `) 847 if assert.Error(funcErr, "An error was expected") { 848 assert.Equal(funcErr, fmt.Errorf("failed to generate map property in base/foo.Blah: StringToArray: map with non-string keys are not supported by OpenAPI in map[int]string")) 849 } 850} 851 852func TestFailingSample2(t *testing.T) { 853 _, funcErr, assert, _, _ := testOpenAPITypeWriter(t, ` 854package foo 855 856// Map sample tests openAPIGen.generateMapProperty method. 857type Blah struct { 858 // A sample String to String map 859 StringToArray map[int]string 860} `) 861 if assert.Error(funcErr, "An error was expected") { 862 assert.Equal(funcErr, fmt.Errorf("failed to generate map property in base/foo.Blah: StringToArray: map with non-string keys are not supported by OpenAPI in map[int]string")) 863 } 864} 865 866func TestFailingDefaultEnforced(t *testing.T) { 867 tests := []struct { 868 definition string 869 expectedError error 870 }{ 871 { 872 definition: ` 873package foo 874 875type Blah struct { 876 // +default=5 877 Int int 878} `, 879 expectedError: fmt.Errorf("failed to generate default in base/foo.Blah: Int: invalid default value (5) for non-pointer/non-omitempty. If specified, must be: 0"), 880 }, 881 { 882 definition: ` 883package foo 884 885type Blah struct { 886 // +default={"foo": 5} 887 Struct struct{ 888 foo int 889 } 890} `, 891 expectedError: fmt.Errorf(`failed to generate default in base/foo.Blah: Struct: invalid default value (map[string]interface {}{"foo":5}) for non-pointer/non-omitempty. If specified, must be: {}`), 892 }, 893 { 894 definition: ` 895package foo 896 897type Blah struct { 898 List []Item 899 900} 901 902// +default="foo" 903type Item string `, 904 expectedError: fmt.Errorf(`failed to generate slice property in base/foo.Blah: List: invalid default value ("foo") for non-pointer/non-omitempty. If specified, must be: ""`), 905 }, 906 { 907 definition: ` 908package foo 909 910type Blah struct { 911 Map map[string]Item 912 913} 914 915// +default="foo" 916type Item string `, 917 expectedError: fmt.Errorf(`failed to generate map property in base/foo.Blah: Map: invalid default value ("foo") for non-pointer/non-omitempty. If specified, must be: ""`), 918 }, 919 } 920 921 for i, test := range tests { 922 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 923 _, funcErr, assert, _, _ := testOpenAPITypeWriter(t, test.definition) 924 if assert.Error(funcErr, "An error was expected") { 925 assert.Equal(funcErr, test.expectedError) 926 } 927 }) 928 } 929} 930 931func TestCustomDef(t *testing.T) { 932 callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 933package foo 934 935import openapi "k8s.io/kube-openapi/pkg/common" 936 937type Blah struct { 938} 939 940func (_ Blah) OpenAPIDefinition() openapi.OpenAPIDefinition { 941 return openapi.OpenAPIDefinition{ 942 Schema: spec.Schema{ 943 SchemaProps: spec.SchemaProps{ 944 Type: []string{"string"}, 945 Format: "date-time", 946 }, 947 }, 948 } 949} 950`) 951 if callErr != nil { 952 t.Fatal(callErr) 953 } 954 if funcErr != nil { 955 t.Fatal(funcErr) 956 } 957 assert.Equal(`"base/foo.Blah": foo.Blah{}.OpenAPIDefinition(), 958`, callBuffer.String()) 959 assert.Equal(``, funcBuffer.String()) 960} 961 962func TestCustomDefV3(t *testing.T) { 963 callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 964package foo 965 966import openapi "k8s.io/kube-openapi/pkg/common" 967 968type Blah struct { 969} 970 971func (_ Blah) OpenAPIV3Definition() openapi.OpenAPIDefinition { 972 return openapi.OpenAPIDefinition{ 973 Schema: spec.Schema{ 974 SchemaProps: spec.SchemaProps{ 975 Type: []string{"string"}, 976 Format: "date-time", 977 }, 978 }, 979 } 980} 981`) 982 if callErr != nil { 983 t.Fatal(callErr) 984 } 985 if funcErr != nil { 986 t.Fatal(funcErr) 987 } 988 assert.Equal(`"base/foo.Blah": foo.Blah{}.OpenAPIV3Definition(), 989`, callBuffer.String()) 990 assert.Equal(``, funcBuffer.String()) 991} 992 993func TestCustomDefV2AndV3(t *testing.T) { 994 callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 995package foo 996 997import openapi "k8s.io/kube-openapi/pkg/common" 998 999type Blah struct { 1000} 1001 1002func (_ Blah) OpenAPIV3Definition() openapi.OpenAPIDefinition { 1003 return openapi.OpenAPIDefinition{ 1004 Schema: spec.Schema{ 1005 SchemaProps: spec.SchemaProps{ 1006 Type: []string{"string"}, 1007 Format: "date-time", 1008 }, 1009 }, 1010 } 1011} 1012 1013func (_ Blah) OpenAPIDefinition() openapi.OpenAPIDefinition { 1014 return openapi.OpenAPIDefinition{ 1015 Schema: spec.Schema{ 1016 SchemaProps: spec.SchemaProps{ 1017 Type: []string{"string"}, 1018 Format: "date-time", 1019 }, 1020 }, 1021 } 1022} 1023`) 1024 if callErr != nil { 1025 t.Fatal(callErr) 1026 } 1027 if funcErr != nil { 1028 t.Fatal(funcErr) 1029 } 1030 assert.Equal(`"base/foo.Blah": common.EmbedOpenAPIDefinitionIntoV2Extension(foo.Blah{}.OpenAPIV3Definition(), foo.Blah{}.OpenAPIDefinition()), 1031`, callBuffer.String()) 1032 assert.Equal(``, funcBuffer.String()) 1033} 1034 1035func TestCustomDefs(t *testing.T) { 1036 callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 1037package foo 1038 1039// Blah is a custom type 1040type Blah struct { 1041} 1042 1043func (_ Blah) OpenAPISchemaType() []string { return []string{"string"} } 1044func (_ Blah) OpenAPISchemaFormat() string { return "date-time" } 1045`) 1046 if callErr != nil { 1047 t.Fatal(callErr) 1048 } 1049 if funcErr != nil { 1050 t.Fatal(funcErr) 1051 } 1052 assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), 1053`, callBuffer.String()) 1054 assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 1055return common.OpenAPIDefinition{ 1056Schema: spec.Schema{ 1057SchemaProps: spec.SchemaProps{ 1058Description: "Blah is a custom type", 1059Type:foo.Blah{}.OpenAPISchemaType(), 1060Format:foo.Blah{}.OpenAPISchemaFormat(), 1061}, 1062}, 1063} 1064} 1065 1066`, funcBuffer.String()) 1067} 1068 1069func TestCustomDefsV3(t *testing.T) { 1070 callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 1071package foo 1072 1073import openapi "k8s.io/kube-openapi/pkg/common" 1074 1075// Blah is a custom type 1076type Blah struct { 1077} 1078 1079func (_ Blah) OpenAPIV3Definition() openapi.OpenAPIDefinition { 1080 return openapi.OpenAPIDefinition{ 1081 Schema: spec.Schema{ 1082 SchemaProps: spec.SchemaProps{ 1083 Type: []string{"string"}, 1084 Format: "date-time", 1085 }, 1086 }, 1087 } 1088} 1089 1090func (_ Blah) OpenAPISchemaType() []string { return []string{"string"} } 1091func (_ Blah) OpenAPISchemaFormat() string { return "date-time" } 1092`) 1093 if callErr != nil { 1094 t.Fatal(callErr) 1095 } 1096 if funcErr != nil { 1097 t.Fatal(funcErr) 1098 } 1099 assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), 1100`, callBuffer.String()) 1101 assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 1102return common.EmbedOpenAPIDefinitionIntoV2Extension(foo.Blah{}.OpenAPIV3Definition(), common.OpenAPIDefinition{ 1103Schema: spec.Schema{ 1104SchemaProps: spec.SchemaProps{ 1105Description: "Blah is a custom type", 1106Type:foo.Blah{}.OpenAPISchemaType(), 1107Format:foo.Blah{}.OpenAPISchemaFormat(), 1108}, 1109}, 1110}) 1111} 1112 1113`, funcBuffer.String()) 1114} 1115 1116func TestPointer(t *testing.T) { 1117 callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 1118package foo 1119 1120// PointerSample demonstrate pointer's properties 1121type Blah struct { 1122 // A string pointer 1123 StringPointer *string 1124 // A struct pointer 1125 StructPointer *Blah 1126 // A slice pointer 1127 SlicePointer *[]string 1128 // A map pointer 1129 MapPointer *map[string]string 1130} 1131 `) 1132 if callErr != nil { 1133 t.Fatal(callErr) 1134 } 1135 if funcErr != nil { 1136 t.Fatal(funcErr) 1137 } 1138 assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), 1139`, callBuffer.String()) 1140 assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 1141return common.OpenAPIDefinition{ 1142Schema: spec.Schema{ 1143SchemaProps: spec.SchemaProps{ 1144Description: "PointerSample demonstrate pointer's properties", 1145Type: []string{"object"}, 1146Properties: map[string]spec.Schema{ 1147"StringPointer": { 1148SchemaProps: spec.SchemaProps{ 1149Description: "A string pointer", 1150Type: []string{"string"}, 1151Format: "", 1152}, 1153}, 1154"StructPointer": { 1155SchemaProps: spec.SchemaProps{ 1156Description: "A struct pointer", 1157Ref: ref("base/foo.Blah"), 1158}, 1159}, 1160"SlicePointer": { 1161SchemaProps: spec.SchemaProps{ 1162Description: "A slice pointer", 1163Type: []string{"array"}, 1164Items: &spec.SchemaOrArray{ 1165Schema: &spec.Schema{ 1166SchemaProps: spec.SchemaProps{ 1167Default: "", 1168Type: []string{"string"}, 1169Format: "", 1170}, 1171}, 1172}, 1173}, 1174}, 1175"MapPointer": { 1176SchemaProps: spec.SchemaProps{ 1177Description: "A map pointer", 1178Type: []string{"object"}, 1179AdditionalProperties: &spec.SchemaOrBool{ 1180Allows: true, 1181Schema: &spec.Schema{ 1182SchemaProps: spec.SchemaProps{ 1183Default: "", 1184Type: []string{"string"}, 1185Format: "", 1186}, 1187}, 1188}, 1189}, 1190}, 1191}, 1192Required: []string{"StringPointer","StructPointer","SlicePointer","MapPointer"}, 1193}, 1194}, 1195Dependencies: []string{ 1196"base/foo.Blah",}, 1197} 1198} 1199 1200`, funcBuffer.String()) 1201} 1202 1203func TestNestedLists(t *testing.T) { 1204 callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 1205package foo 1206 1207// Blah is a test. 1208// +k8s:openapi-gen=true 1209// +k8s:openapi-gen=x-kubernetes-type-tag:type_test 1210type Blah struct { 1211 // Nested list 1212 NestedList [][]int64 1213} 1214`) 1215 if callErr != nil { 1216 t.Fatal(callErr) 1217 } 1218 if funcErr != nil { 1219 t.Fatal(funcErr) 1220 } 1221 assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), 1222`, callBuffer.String()) 1223 assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 1224return common.OpenAPIDefinition{ 1225Schema: spec.Schema{ 1226SchemaProps: spec.SchemaProps{ 1227Description: "Blah is a test.", 1228Type: []string{"object"}, 1229Properties: map[string]spec.Schema{ 1230"NestedList": { 1231SchemaProps: spec.SchemaProps{ 1232Description: "Nested list", 1233Type: []string{"array"}, 1234Items: &spec.SchemaOrArray{ 1235Schema: &spec.Schema{ 1236SchemaProps: spec.SchemaProps{ 1237Type: []string{"array"}, 1238Items: &spec.SchemaOrArray{ 1239Schema: &spec.Schema{ 1240SchemaProps: spec.SchemaProps{ 1241Default: 0, 1242Type: []string{"integer"}, 1243Format: "int64", 1244}, 1245}, 1246}, 1247}, 1248}, 1249}, 1250}, 1251}, 1252}, 1253Required: []string{"NestedList"}, 1254}, 1255VendorExtensible: spec.VendorExtensible{ 1256Extensions: spec.Extensions{ 1257"x-kubernetes-type-tag": "type_test", 1258}, 1259}, 1260}, 1261} 1262} 1263 1264`, funcBuffer.String()) 1265} 1266 1267func TestNestListOfMaps(t *testing.T) { 1268 callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 1269package foo 1270 1271// Blah is a test. 1272// +k8s:openapi-gen=true 1273// +k8s:openapi-gen=x-kubernetes-type-tag:type_test 1274type Blah struct { 1275 // Nested list of maps 1276 NestedListOfMaps [][]map[string]string 1277} 1278`) 1279 if callErr != nil { 1280 t.Fatal(callErr) 1281 } 1282 if funcErr != nil { 1283 t.Fatal(funcErr) 1284 } 1285 assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), 1286`, callBuffer.String()) 1287 assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 1288return common.OpenAPIDefinition{ 1289Schema: spec.Schema{ 1290SchemaProps: spec.SchemaProps{ 1291Description: "Blah is a test.", 1292Type: []string{"object"}, 1293Properties: map[string]spec.Schema{ 1294"NestedListOfMaps": { 1295SchemaProps: spec.SchemaProps{ 1296Description: "Nested list of maps", 1297Type: []string{"array"}, 1298Items: &spec.SchemaOrArray{ 1299Schema: &spec.Schema{ 1300SchemaProps: spec.SchemaProps{ 1301Type: []string{"array"}, 1302Items: &spec.SchemaOrArray{ 1303Schema: &spec.Schema{ 1304SchemaProps: spec.SchemaProps{ 1305Type: []string{"object"}, 1306AdditionalProperties: &spec.SchemaOrBool{ 1307Allows: true, 1308Schema: &spec.Schema{ 1309SchemaProps: spec.SchemaProps{ 1310Default: "", 1311Type: []string{"string"}, 1312Format: "", 1313}, 1314}, 1315}, 1316}, 1317}, 1318}, 1319}, 1320}, 1321}, 1322}, 1323}, 1324}, 1325Required: []string{"NestedListOfMaps"}, 1326}, 1327VendorExtensible: spec.VendorExtensible{ 1328Extensions: spec.Extensions{ 1329"x-kubernetes-type-tag": "type_test", 1330}, 1331}, 1332}, 1333} 1334} 1335 1336`, funcBuffer.String()) 1337} 1338 1339func TestExtensions(t *testing.T) { 1340 callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 1341package foo 1342 1343// Blah is a test. 1344// +k8s:openapi-gen=true 1345// +k8s:openapi-gen=x-kubernetes-type-tag:type_test 1346type Blah struct { 1347 // a member with a list type with two map keys 1348 // +listType=map 1349 // +listMapKey=port 1350 // +listMapKey=protocol 1351 WithListField []string 1352 1353 // another member with a list type with one map key 1354 // +listType=map 1355 // +listMapKey=port 1356 WithListField2 []string 1357} 1358 `) 1359 if callErr != nil { 1360 t.Fatal(callErr) 1361 } 1362 if funcErr != nil { 1363 t.Fatal(funcErr) 1364 } 1365 assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), 1366`, callBuffer.String()) 1367 assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 1368return common.OpenAPIDefinition{ 1369Schema: spec.Schema{ 1370SchemaProps: spec.SchemaProps{ 1371Description: "Blah is a test.", 1372Type: []string{"object"}, 1373Properties: map[string]spec.Schema{ 1374"WithListField": { 1375VendorExtensible: spec.VendorExtensible{ 1376Extensions: spec.Extensions{ 1377"x-kubernetes-list-map-keys": []interface{}{ 1378"port", 1379"protocol", 1380}, 1381"x-kubernetes-list-type": "map", 1382}, 1383}, 1384SchemaProps: spec.SchemaProps{ 1385Description: "a member with a list type with two map keys", 1386Type: []string{"array"}, 1387Items: &spec.SchemaOrArray{ 1388Schema: &spec.Schema{ 1389SchemaProps: spec.SchemaProps{ 1390Default: "", 1391Type: []string{"string"}, 1392Format: "", 1393}, 1394}, 1395}, 1396}, 1397}, 1398"WithListField2": { 1399VendorExtensible: spec.VendorExtensible{ 1400Extensions: spec.Extensions{ 1401"x-kubernetes-list-map-keys": []interface{}{ 1402"port", 1403}, 1404"x-kubernetes-list-type": "map", 1405}, 1406}, 1407SchemaProps: spec.SchemaProps{ 1408Description: "another member with a list type with one map key", 1409Type: []string{"array"}, 1410Items: &spec.SchemaOrArray{ 1411Schema: &spec.Schema{ 1412SchemaProps: spec.SchemaProps{ 1413Default: "", 1414Type: []string{"string"}, 1415Format: "", 1416}, 1417}, 1418}, 1419}, 1420}, 1421}, 1422Required: []string{"WithListField","WithListField2"}, 1423}, 1424VendorExtensible: spec.VendorExtensible{ 1425Extensions: spec.Extensions{ 1426"x-kubernetes-type-tag": "type_test", 1427}, 1428}, 1429}, 1430} 1431} 1432 1433`, funcBuffer.String()) 1434} 1435 1436func TestUnion(t *testing.T) { 1437 callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 1438package foo 1439 1440// Blah is a test. 1441// +k8s:openapi-gen=true 1442// +k8s:openapi-gen=x-kubernetes-type-tag:type_test 1443// +union 1444type Blah struct { 1445 // +unionDiscriminator 1446 Discriminator *string `+"`"+`json:"discriminator"`+"`"+` 1447 // +optional 1448 Numeric int `+"`"+`json:"numeric"`+"`"+` 1449 // +optional 1450 String string `+"`"+`json:"string"`+"`"+` 1451 // +optional 1452 Float float64 `+"`"+`json:"float"`+"`"+` 1453} 1454 `) 1455 if callErr != nil { 1456 t.Fatal(callErr) 1457 } 1458 if funcErr != nil { 1459 t.Fatal(funcErr) 1460 } 1461 assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), 1462`, callBuffer.String()) 1463 assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 1464return common.OpenAPIDefinition{ 1465Schema: spec.Schema{ 1466SchemaProps: spec.SchemaProps{ 1467Description: "Blah is a test.", 1468Type: []string{"object"}, 1469Properties: map[string]spec.Schema{ 1470"discriminator": { 1471SchemaProps: spec.SchemaProps{ 1472Type: []string{"string"}, 1473Format: "", 1474}, 1475}, 1476"numeric": { 1477SchemaProps: spec.SchemaProps{ 1478Default: 0, 1479Type: []string{"integer"}, 1480Format: "int32", 1481}, 1482}, 1483"string": { 1484SchemaProps: spec.SchemaProps{ 1485Default: "", 1486Type: []string{"string"}, 1487Format: "", 1488}, 1489}, 1490"float": { 1491SchemaProps: spec.SchemaProps{ 1492Default: 0, 1493Type: []string{"number"}, 1494Format: "double", 1495}, 1496}, 1497}, 1498Required: []string{"discriminator"}, 1499}, 1500VendorExtensible: spec.VendorExtensible{ 1501Extensions: spec.Extensions{ 1502"x-kubernetes-type-tag": "type_test", 1503"x-kubernetes-unions": []interface{}{ 1504map[string]interface{}{ 1505"discriminator": "discriminator", 1506"fields-to-discriminateBy": map[string]interface{}{ 1507"float": "Float", 1508"numeric": "Numeric", 1509"string": "String", 1510}, 1511}, 1512}, 1513}, 1514}, 1515}, 1516} 1517} 1518 1519`, funcBuffer.String()) 1520} 1521