k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/generators/openapi_test.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package generators 18 19 import ( 20 "bytes" 21 "fmt" 22 "go/format" 23 "path" 24 "strings" 25 "testing" 26 27 "github.com/google/go-cmp/cmp" 28 "golang.org/x/tools/go/packages" 29 "golang.org/x/tools/go/packages/packagestest" 30 "k8s.io/gengo/v2/generator" 31 "k8s.io/gengo/v2/namer" 32 "k8s.io/gengo/v2/parser" 33 "k8s.io/gengo/v2/types" 34 ) 35 36 func construct(t *testing.T, cfg *packages.Config, nameSystems namer.NameSystems, defaultSystem string, pkg string) *generator.Context { 37 p := parser.New() 38 if err := p.LoadPackagesWithConfigForTesting(cfg, pkg); err != nil { 39 t.Fatalf("failed to load package: %v", err) 40 } 41 c, err := generator.NewContext(p, nameSystems, defaultSystem) 42 if err != nil { 43 t.Fatalf("failed to make a context: %v", err) 44 } 45 return c 46 } 47 48 func testOpenAPITypeWriter(t *testing.T, cfg *packages.Config) (error, error, *bytes.Buffer, *bytes.Buffer, []string) { 49 pkgBase := "example.com/base" 50 // `path` vs. `filepath` because packages use '/' 51 inputPkg := path.Join(pkgBase, "foo") 52 outputPkg := path.Join(pkgBase, "output") 53 imports := generator.NewImportTrackerForPackage(outputPkg) 54 rawNamer := namer.NewRawNamer(outputPkg, imports) 55 namers := namer.NameSystems{ 56 "raw": rawNamer, 57 "private": &namer.NameStrategy{ 58 Join: func(pre string, in []string, post string) string { 59 return strings.Join(in, "_") 60 }, 61 PrependPackageNames: 4, // enough to fully qualify from k8s.io/api/... 62 }, 63 } 64 context := construct(t, cfg, namers, "raw", inputPkg) 65 universe := context.Universe 66 blahT := universe.Type(types.Name{Package: inputPkg, Name: "Blah"}) 67 68 callBuffer := &bytes.Buffer{} 69 callSW := generator.NewSnippetWriter(callBuffer, context, "$", "$") 70 callError := newOpenAPITypeWriter(callSW, context).generateCall(blahT) 71 72 funcBuffer := &bytes.Buffer{} 73 funcSW := generator.NewSnippetWriter(funcBuffer, context, "$", "$") 74 funcError := newOpenAPITypeWriter(funcSW, context).generate(blahT) 75 76 return callError, funcError, callBuffer, funcBuffer, imports.ImportLines() 77 } 78 79 // NOTE: the usual order of arguments for an assertion would be want, got, but 80 // this helper function flips that in favor of callsite readability. 81 func assertEqual(t *testing.T, got, want string) { 82 t.Helper() 83 want = strings.TrimSpace(want) 84 got = strings.TrimSpace(got) 85 if !cmp.Equal(want, got) { 86 t.Errorf("Wrong result:\n%s", cmp.Diff(want, got)) 87 } 88 } 89 90 func TestSimple(t *testing.T) { 91 inputFile := ` 92 package foo 93 94 // Blah is a test. 95 // +k8s:openapi-gen=true 96 // +k8s:openapi-gen=x-kubernetes-type-tag:type_test 97 type Blah struct { 98 // A simple string 99 String string 100 // A simple int 101 Int int ` + "`" + `json:",omitempty"` + "`" + ` 102 // An int considered string simple int 103 IntString int ` + "`" + `json:",string"` + "`" + ` 104 // A simple int64 105 Int64 int64 106 // A simple int32 107 Int32 int32 108 // A simple int16 109 Int16 int16 110 // A simple int8 111 Int8 int8 112 // A simple int 113 Uint uint 114 // A simple int64 115 Uint64 uint64 116 // A simple int32 117 Uint32 uint32 118 // A simple int16 119 Uint16 uint16 120 // A simple int8 121 Uint8 uint8 122 // A simple byte 123 Byte byte 124 // A simple boolean 125 Bool bool 126 // A simple float64 127 Float64 float64 128 // A simple float32 129 Float32 float32 130 // a base64 encoded characters 131 ByteArray []byte 132 // a member with an extension 133 // +k8s:openapi-gen=x-kubernetes-member-tag:member_test 134 WithExtension string 135 // a member with struct tag as extension 136 // +patchStrategy=merge 137 // +patchMergeKey=pmk 138 WithStructTagExtension string ` + "`" + `patchStrategy:"merge" patchMergeKey:"pmk"` + "`" + ` 139 // a member with a list type 140 // +listType=atomic 141 // +default=["foo", "bar"] 142 WithListType []string 143 // a member with a map type 144 // +listType=atomic 145 // +default={"foo": "bar", "fizz": "buzz"} 146 Map map[string]string 147 // a member with a string pointer 148 // +default="foo" 149 StringPointer *string 150 // an int member with a default 151 // +default=1 152 OmittedInt int ` + "`" + `json:"omitted,omitempty"` + "`" + ` 153 }` 154 155 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 156 e := packagestest.Export(t, x, []packagestest.Module{{ 157 Name: "example.com/base/foo", 158 Files: map[string]interface{}{ 159 "foo.go": inputFile, 160 }, 161 }}) 162 defer e.Cleanup() 163 164 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 165 if callErr != nil { 166 t.Fatal(callErr) 167 } 168 if funcErr != nil { 169 t.Fatal(funcErr) 170 } 171 assertEqual(t, callBuffer.String(), 172 `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) 173 174 assertEqual(t, funcBuffer.String(), 175 `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 176 return common.OpenAPIDefinition{ 177 Schema: spec.Schema{ 178 SchemaProps: spec.SchemaProps{ 179 Description: "Blah is a test.", 180 Type: []string{"object"}, 181 Properties: map[string]spec.Schema{ 182 "String": { 183 SchemaProps: spec.SchemaProps{ 184 Description: "A simple string", 185 Default: "", 186 Type: []string{"string"}, 187 Format: "", 188 }, 189 }, 190 "Int64": { 191 SchemaProps: spec.SchemaProps{ 192 Description: "A simple int64", 193 Default: 0, 194 Type: []string{"integer"}, 195 Format: "int64", 196 }, 197 }, 198 "Int32": { 199 SchemaProps: spec.SchemaProps{ 200 Description: "A simple int32", 201 Default: 0, 202 Type: []string{"integer"}, 203 Format: "int32", 204 }, 205 }, 206 "Int16": { 207 SchemaProps: spec.SchemaProps{ 208 Description: "A simple int16", 209 Default: 0, 210 Type: []string{"integer"}, 211 Format: "int32", 212 }, 213 }, 214 "Int8": { 215 SchemaProps: spec.SchemaProps{ 216 Description: "A simple int8", 217 Default: 0, 218 Type: []string{"integer"}, 219 Format: "byte", 220 }, 221 }, 222 "Uint": { 223 SchemaProps: spec.SchemaProps{ 224 Description: "A simple int", 225 Default: 0, 226 Type: []string{"integer"}, 227 Format: "int32", 228 }, 229 }, 230 "Uint64": { 231 SchemaProps: spec.SchemaProps{ 232 Description: "A simple int64", 233 Default: 0, 234 Type: []string{"integer"}, 235 Format: "int64", 236 }, 237 }, 238 "Uint32": { 239 SchemaProps: spec.SchemaProps{ 240 Description: "A simple int32", 241 Default: 0, 242 Type: []string{"integer"}, 243 Format: "int64", 244 }, 245 }, 246 "Uint16": { 247 SchemaProps: spec.SchemaProps{ 248 Description: "A simple int16", 249 Default: 0, 250 Type: []string{"integer"}, 251 Format: "int32", 252 }, 253 }, 254 "Uint8": { 255 SchemaProps: spec.SchemaProps{ 256 Description: "A simple int8", 257 Default: 0, 258 Type: []string{"integer"}, 259 Format: "byte", 260 }, 261 }, 262 "Byte": { 263 SchemaProps: spec.SchemaProps{ 264 Description: "A simple byte", 265 Default: 0, 266 Type: []string{"integer"}, 267 Format: "byte", 268 }, 269 }, 270 "Bool": { 271 SchemaProps: spec.SchemaProps{ 272 Description: "A simple boolean", 273 Default: false, 274 Type: []string{"boolean"}, 275 Format: "", 276 }, 277 }, 278 "Float64": { 279 SchemaProps: spec.SchemaProps{ 280 Description: "A simple float64", 281 Default: 0, 282 Type: []string{"number"}, 283 Format: "double", 284 }, 285 }, 286 "Float32": { 287 SchemaProps: spec.SchemaProps{ 288 Description: "A simple float32", 289 Default: 0, 290 Type: []string{"number"}, 291 Format: "float", 292 }, 293 }, 294 "ByteArray": { 295 SchemaProps: spec.SchemaProps{ 296 Description: "a base64 encoded characters", 297 Type: []string{"string"}, 298 Format: "byte", 299 }, 300 }, 301 "WithExtension": { 302 VendorExtensible: spec.VendorExtensible{ 303 Extensions: spec.Extensions{ 304 "x-kubernetes-member-tag": "member_test", 305 }, 306 }, 307 SchemaProps: spec.SchemaProps{ 308 Description: "a member with an extension", 309 Default: "", 310 Type: []string{"string"}, 311 Format: "", 312 }, 313 }, 314 "WithStructTagExtension": { 315 VendorExtensible: spec.VendorExtensible{ 316 Extensions: spec.Extensions{ 317 "x-kubernetes-patch-merge-key": "pmk", 318 "x-kubernetes-patch-strategy": "merge", 319 }, 320 }, 321 SchemaProps: spec.SchemaProps{ 322 Description: "a member with struct tag as extension", 323 Default: "", 324 Type: []string{"string"}, 325 Format: "", 326 }, 327 }, 328 "WithListType": { 329 VendorExtensible: spec.VendorExtensible{ 330 Extensions: spec.Extensions{ 331 "x-kubernetes-list-type": "atomic", 332 }, 333 }, 334 SchemaProps: spec.SchemaProps{ 335 Description: "a member with a list type", 336 Default: []interface {}{"foo", "bar"}, 337 Type: []string{"array"}, 338 Items: &spec.SchemaOrArray{ 339 Schema: &spec.Schema{ 340 SchemaProps: spec.SchemaProps{ 341 Default: "", 342 Type: []string{"string"}, 343 Format: "", 344 }, 345 }, 346 }, 347 }, 348 }, 349 "Map": { 350 VendorExtensible: spec.VendorExtensible{ 351 Extensions: spec.Extensions{ 352 "x-kubernetes-list-type": "atomic", 353 }, 354 }, 355 SchemaProps: spec.SchemaProps{ 356 Description: "a member with a map type", 357 Default: map[string]interface {}{"fizz":"buzz", "foo":"bar"}, 358 Type: []string{"object"}, 359 AdditionalProperties: &spec.SchemaOrBool{ 360 Allows: true, 361 Schema: &spec.Schema{ 362 SchemaProps: spec.SchemaProps{ 363 Default: "", 364 Type: []string{"string"}, 365 Format: "", 366 }, 367 }, 368 }, 369 }, 370 }, 371 "StringPointer": { 372 SchemaProps: spec.SchemaProps{ 373 Description: "a member with a string pointer", 374 Default: "foo", 375 Type: []string{"string"}, 376 Format: "", 377 }, 378 }, 379 "omitted": { 380 SchemaProps: spec.SchemaProps{ 381 Description: "an int member with a default", 382 Default: 1, 383 Type: []string{"integer"}, 384 Format: "int32", 385 }, 386 }, 387 }, 388 Required: []string{"String","Int64","Int32","Int16","Int8","Uint","Uint64","Uint32","Uint16","Uint8","Byte","Bool","Float64","Float32","ByteArray","WithExtension","WithStructTagExtension","WithListType","Map","StringPointer"}, 389 }, 390 VendorExtensible: spec.VendorExtensible{ 391 Extensions: spec.Extensions{ 392 "x-kubernetes-type-tag": "type_test", 393 }, 394 }, 395 }, 396 } 397 }`) 398 }) 399 } 400 401 func TestEmptyProperties(t *testing.T) { 402 inputFile := ` 403 package foo 404 405 // Blah demonstrate a struct without fields. 406 type Blah struct { 407 }` 408 409 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 410 e := packagestest.Export(t, x, []packagestest.Module{{ 411 Name: "example.com/base/foo", 412 Files: map[string]interface{}{ 413 "foo.go": inputFile, 414 }, 415 }}) 416 defer e.Cleanup() 417 418 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 419 if callErr != nil { 420 t.Fatal(callErr) 421 } 422 if funcErr != nil { 423 t.Fatal(funcErr) 424 } 425 assertEqual(t, callBuffer.String(), 426 `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) 427 assertEqual(t, funcBuffer.String(), 428 `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 429 return common.OpenAPIDefinition{ 430 Schema: spec.Schema{ 431 SchemaProps: spec.SchemaProps{ 432 Description: "Blah demonstrate a struct without fields.", 433 Type: []string{"object"}, 434 }, 435 }, 436 } 437 }`) 438 }) 439 } 440 441 func TestNestedStruct(t *testing.T) { 442 inputFile := ` 443 package foo 444 445 // Nested is used as struct field 446 type Nested struct { 447 // A simple string 448 String string 449 } 450 451 // Blah demonstrate a struct with struct field. 452 type Blah struct { 453 // A struct field 454 Field Nested 455 }` 456 457 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 458 e := packagestest.Export(t, x, []packagestest.Module{{ 459 Name: "example.com/base/foo", 460 Files: map[string]interface{}{ 461 "foo.go": inputFile, 462 }, 463 }}) 464 defer e.Cleanup() 465 466 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 467 if callErr != nil { 468 t.Fatal(callErr) 469 } 470 if funcErr != nil { 471 t.Fatal(funcErr) 472 } 473 assertEqual(t, callBuffer.String(), 474 `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) 475 assertEqual(t, funcBuffer.String(), 476 `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 477 return common.OpenAPIDefinition{ 478 Schema: spec.Schema{ 479 SchemaProps: spec.SchemaProps{ 480 Description: "Blah demonstrate a struct with struct field.", 481 Type: []string{"object"}, 482 Properties: map[string]spec.Schema{ 483 "Field": { 484 SchemaProps: spec.SchemaProps{ 485 Description: "A struct field", 486 Default: map[string]interface {}{}, 487 Ref: ref("example.com/base/foo.Nested"), 488 }, 489 }, 490 }, 491 Required: []string{"Field"}, 492 }, 493 }, 494 Dependencies: []string{ 495 "example.com/base/foo.Nested",}, 496 } 497 }`) 498 }) 499 } 500 501 func TestNestedStructPointer(t *testing.T) { 502 inputFile := ` 503 package foo 504 505 // Nested is used as struct pointer field 506 type Nested struct { 507 // A simple string 508 String string 509 } 510 511 // Blah demonstrate a struct with struct pointer field. 512 type Blah struct { 513 // A struct pointer field 514 Field *Nested 515 }` 516 517 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 518 e := packagestest.Export(t, x, []packagestest.Module{{ 519 Name: "example.com/base/foo", 520 Files: map[string]interface{}{ 521 "foo.go": inputFile, 522 }, 523 }}) 524 defer e.Cleanup() 525 526 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 527 528 if callErr != nil { 529 t.Fatal(callErr) 530 } 531 if funcErr != nil { 532 t.Fatal(funcErr) 533 } 534 assertEqual(t, callBuffer.String(), 535 `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) 536 assertEqual(t, funcBuffer.String(), 537 `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 538 return common.OpenAPIDefinition{ 539 Schema: spec.Schema{ 540 SchemaProps: spec.SchemaProps{ 541 Description: "Blah demonstrate a struct with struct pointer field.", 542 Type: []string{"object"}, 543 Properties: map[string]spec.Schema{ 544 "Field": { 545 SchemaProps: spec.SchemaProps{ 546 Description: "A struct pointer field", 547 Ref: ref("example.com/base/foo.Nested"), 548 }, 549 }, 550 }, 551 Required: []string{"Field"}, 552 }, 553 }, 554 Dependencies: []string{ 555 "example.com/base/foo.Nested",}, 556 } 557 }`) 558 }) 559 } 560 561 func TestEmbeddedStruct(t *testing.T) { 562 inputFile := ` 563 package foo 564 565 // Nested is used as embedded struct field 566 type Nested struct { 567 // A simple string 568 String string 569 } 570 571 // Blah demonstrate a struct with embedded struct field. 572 type Blah struct { 573 // An embedded struct field 574 Nested 575 }` 576 577 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 578 e := packagestest.Export(t, x, []packagestest.Module{{ 579 Name: "example.com/base/foo", 580 Files: map[string]interface{}{ 581 "foo.go": inputFile, 582 }, 583 }}) 584 defer e.Cleanup() 585 586 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 587 if callErr != nil { 588 t.Fatal(callErr) 589 } 590 if funcErr != nil { 591 t.Fatal(funcErr) 592 } 593 assertEqual(t, callBuffer.String(), 594 `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) 595 assertEqual(t, funcBuffer.String(), 596 `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 597 return common.OpenAPIDefinition{ 598 Schema: spec.Schema{ 599 SchemaProps: spec.SchemaProps{ 600 Description: "Blah demonstrate a struct with embedded struct field.", 601 Type: []string{"object"}, 602 Properties: map[string]spec.Schema{ 603 "Nested": { 604 SchemaProps: spec.SchemaProps{ 605 Description: "An embedded struct field", 606 Default: map[string]interface {}{}, 607 Ref: ref("example.com/base/foo.Nested"), 608 }, 609 }, 610 }, 611 Required: []string{"Nested"}, 612 }, 613 }, 614 Dependencies: []string{ 615 "example.com/base/foo.Nested",}, 616 } 617 }`) 618 }) 619 } 620 621 func TestSingleEmbeddedStruct(t *testing.T) { 622 inputFile := ` 623 package foo 624 625 import "time" 626 627 // Nested is used as embedded struct field 628 type Nested struct { 629 // A simple string 630 time.Duration 631 } 632 633 // Blah demonstrate a struct with embedded struct field. 634 type Blah struct { 635 // An embedded struct field 636 // +default="10ms" 637 Nested ` + "`" + `json:"nested,omitempty" protobuf:"bytes,5,opt,name=nested"` + "`" + ` 638 }` 639 640 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 641 e := packagestest.Export(t, x, []packagestest.Module{{ 642 Name: "example.com/base/foo", 643 Files: map[string]interface{}{ 644 "foo.go": inputFile, 645 }, 646 }}) 647 defer e.Cleanup() 648 649 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 650 if callErr != nil { 651 t.Fatal(callErr) 652 } 653 if funcErr != nil { 654 t.Fatal(funcErr) 655 } 656 assertEqual(t, callBuffer.String(), 657 `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) 658 assertEqual(t, funcBuffer.String(), 659 `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 660 return common.OpenAPIDefinition{ 661 Schema: spec.Schema{ 662 SchemaProps: spec.SchemaProps{ 663 Description: "Blah demonstrate a struct with embedded struct field.", 664 Type: []string{"object"}, 665 Properties: map[string]spec.Schema{ 666 "nested": { 667 SchemaProps: spec.SchemaProps{ 668 Description: "An embedded struct field", 669 Default: "10ms", 670 Ref: ref("example.com/base/foo.Nested"), 671 }, 672 }, 673 }, 674 }, 675 }, 676 Dependencies: []string{ 677 "example.com/base/foo.Nested",}, 678 } 679 }`) 680 }) 681 } 682 683 func TestEmbeddedInlineStruct(t *testing.T) { 684 inputFile := ` 685 package foo 686 687 // Nested is used as embedded inline struct field 688 type Nested struct { 689 // A simple string 690 String string 691 } 692 693 // Blah demonstrate a struct with embedded inline struct field. 694 type Blah struct { 695 // An embedded inline struct field 696 Nested ` + "`" + `json:",inline,omitempty"` + "`" + ` 697 }` 698 699 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 700 e := packagestest.Export(t, x, []packagestest.Module{{ 701 Name: "example.com/base/foo", 702 Files: map[string]interface{}{ 703 "foo.go": inputFile, 704 }, 705 }}) 706 defer e.Cleanup() 707 708 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 709 if callErr != nil { 710 t.Fatal(callErr) 711 } 712 if funcErr != nil { 713 t.Fatal(funcErr) 714 } 715 assertEqual(t, callBuffer.String(), 716 `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) 717 assertEqual(t, funcBuffer.String(), 718 `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 719 return common.OpenAPIDefinition{ 720 Schema: spec.Schema{ 721 SchemaProps: spec.SchemaProps{ 722 Description: "Blah demonstrate a struct with embedded inline struct field.", 723 Type: []string{"object"}, 724 Properties: map[string]spec.Schema{ 725 "String": { 726 SchemaProps: spec.SchemaProps{ 727 Description: "A simple string", 728 Default: "", 729 Type: []string{"string"}, 730 Format: "", 731 }, 732 }, 733 }, 734 Required: []string{"String"}, 735 }, 736 }, 737 } 738 }`) 739 }) 740 } 741 742 func TestEmbeddedInlineStructPointer(t *testing.T) { 743 inputFile := ` 744 package foo 745 746 // Nested is used as embedded inline struct pointer field. 747 type Nested struct { 748 // A simple string 749 String string 750 } 751 752 // Blah demonstrate a struct with embedded inline struct pointer field. 753 type Blah struct { 754 // An embedded inline struct pointer field 755 *Nested ` + "`" + `json:",inline,omitempty"` + "`" + ` 756 }` 757 758 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 759 e := packagestest.Export(t, x, []packagestest.Module{{ 760 Name: "example.com/base/foo", 761 Files: map[string]interface{}{ 762 "foo.go": inputFile, 763 }, 764 }}) 765 defer e.Cleanup() 766 767 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 768 if callErr != nil { 769 t.Fatal(callErr) 770 } 771 if funcErr != nil { 772 t.Fatal(funcErr) 773 } 774 assertEqual(t, callBuffer.String(), 775 `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) 776 assertEqual(t, funcBuffer.String(), 777 `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 778 return common.OpenAPIDefinition{ 779 Schema: spec.Schema{ 780 SchemaProps: spec.SchemaProps{ 781 Description: "Blah demonstrate a struct with embedded inline struct pointer field.", 782 Type: []string{"object"}, 783 Properties: map[string]spec.Schema{ 784 "String": { 785 SchemaProps: spec.SchemaProps{ 786 Description: "A simple string", 787 Default: "", 788 Type: []string{"string"}, 789 Format: "", 790 }, 791 }, 792 }, 793 Required: []string{"String"}, 794 }, 795 }, 796 } 797 }`) 798 }) 799 } 800 801 func TestNestedMapString(t *testing.T) { 802 inputFile := ` 803 package foo 804 805 // Map sample tests openAPIGen.generateMapProperty method. 806 type Blah struct { 807 // A sample String to String map 808 StringToArray map[string]map[string]string 809 }` 810 811 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 812 e := packagestest.Export(t, x, []packagestest.Module{{ 813 Name: "example.com/base/foo", 814 Files: map[string]interface{}{ 815 "foo.go": inputFile, 816 }, 817 }}) 818 defer e.Cleanup() 819 820 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 821 if callErr != nil { 822 t.Fatal(callErr) 823 } 824 if funcErr != nil { 825 t.Fatal(funcErr) 826 } 827 assertEqual(t, callBuffer.String(), 828 `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) 829 assertEqual(t, funcBuffer.String(), 830 `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 831 return common.OpenAPIDefinition{ 832 Schema: spec.Schema{ 833 SchemaProps: spec.SchemaProps{ 834 Description: "Map sample tests openAPIGen.generateMapProperty method.", 835 Type: []string{"object"}, 836 Properties: map[string]spec.Schema{ 837 "StringToArray": { 838 SchemaProps: spec.SchemaProps{ 839 Description: "A sample String to String map", 840 Type: []string{"object"}, 841 AdditionalProperties: &spec.SchemaOrBool{ 842 Allows: true, 843 Schema: &spec.Schema{ 844 SchemaProps: spec.SchemaProps{ 845 Type: []string{"object"}, 846 AdditionalProperties: &spec.SchemaOrBool{ 847 Allows: true, 848 Schema: &spec.Schema{ 849 SchemaProps: spec.SchemaProps{ 850 Default: "", 851 Type: []string{"string"}, 852 Format: "", 853 }, 854 }, 855 }, 856 }, 857 }, 858 }, 859 }, 860 }, 861 }, 862 Required: []string{"StringToArray"}, 863 }, 864 }, 865 } 866 }`) 867 }) 868 } 869 870 func TestNestedMapInt(t *testing.T) { 871 inputFile := ` 872 package foo 873 874 // Map sample tests openAPIGen.generateMapProperty method. 875 type Blah struct { 876 // A sample String to String map 877 StringToArray map[string]map[string]int 878 }` 879 880 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 881 e := packagestest.Export(t, x, []packagestest.Module{{ 882 Name: "example.com/base/foo", 883 Files: map[string]interface{}{ 884 "foo.go": inputFile, 885 }, 886 }}) 887 defer e.Cleanup() 888 889 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 890 if callErr != nil { 891 t.Fatal(callErr) 892 } 893 if funcErr != nil { 894 t.Fatal(funcErr) 895 } 896 assertEqual(t, callBuffer.String(), 897 `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) 898 assertEqual(t, funcBuffer.String(), 899 `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 900 return common.OpenAPIDefinition{ 901 Schema: spec.Schema{ 902 SchemaProps: spec.SchemaProps{ 903 Description: "Map sample tests openAPIGen.generateMapProperty method.", 904 Type: []string{"object"}, 905 Properties: map[string]spec.Schema{ 906 "StringToArray": { 907 SchemaProps: spec.SchemaProps{ 908 Description: "A sample String to String map", 909 Type: []string{"object"}, 910 AdditionalProperties: &spec.SchemaOrBool{ 911 Allows: true, 912 Schema: &spec.Schema{ 913 SchemaProps: spec.SchemaProps{ 914 Type: []string{"object"}, 915 AdditionalProperties: &spec.SchemaOrBool{ 916 Allows: true, 917 Schema: &spec.Schema{ 918 SchemaProps: spec.SchemaProps{ 919 Default: 0, 920 Type: []string{"integer"}, 921 Format: "int32", 922 }, 923 }, 924 }, 925 }, 926 }, 927 }, 928 }, 929 }, 930 }, 931 Required: []string{"StringToArray"}, 932 }, 933 }, 934 } 935 }`) 936 }) 937 } 938 939 func TestNestedMapBoolean(t *testing.T) { 940 inputFile := ` 941 package foo 942 943 // Map sample tests openAPIGen.generateMapProperty method. 944 type Blah struct { 945 // A sample String to String map 946 StringToArray map[string]map[string]bool 947 }` 948 949 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 950 e := packagestest.Export(t, x, []packagestest.Module{{ 951 Name: "example.com/base/foo", 952 Files: map[string]interface{}{ 953 "foo.go": inputFile, 954 }, 955 }}) 956 defer e.Cleanup() 957 958 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 959 if callErr != nil { 960 t.Fatal(callErr) 961 } 962 if funcErr != nil { 963 t.Fatal(funcErr) 964 } 965 assertEqual(t, callBuffer.String(), 966 `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) 967 assertEqual(t, funcBuffer.String(), 968 `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 969 return common.OpenAPIDefinition{ 970 Schema: spec.Schema{ 971 SchemaProps: spec.SchemaProps{ 972 Description: "Map sample tests openAPIGen.generateMapProperty method.", 973 Type: []string{"object"}, 974 Properties: map[string]spec.Schema{ 975 "StringToArray": { 976 SchemaProps: spec.SchemaProps{ 977 Description: "A sample String to String map", 978 Type: []string{"object"}, 979 AdditionalProperties: &spec.SchemaOrBool{ 980 Allows: true, 981 Schema: &spec.Schema{ 982 SchemaProps: spec.SchemaProps{ 983 Type: []string{"object"}, 984 AdditionalProperties: &spec.SchemaOrBool{ 985 Allows: true, 986 Schema: &spec.Schema{ 987 SchemaProps: spec.SchemaProps{ 988 Default: false, 989 Type: []string{"boolean"}, 990 Format: "", 991 }, 992 }, 993 }, 994 }, 995 }, 996 }, 997 }, 998 }, 999 }, 1000 Required: []string{"StringToArray"}, 1001 }, 1002 }, 1003 } 1004 }`) 1005 }) 1006 } 1007 1008 func TestFailingSample1(t *testing.T) { 1009 inputFile := ` 1010 package foo 1011 1012 // Map sample tests openAPIGen.generateMapProperty method. 1013 type Blah struct { 1014 // A sample String to String map 1015 StringToArray map[string]map[string]map[int]string 1016 }` 1017 1018 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 1019 e := packagestest.Export(t, x, []packagestest.Module{{ 1020 Name: "example.com/base/foo", 1021 Files: map[string]interface{}{ 1022 "foo.go": inputFile, 1023 }, 1024 }}) 1025 defer e.Cleanup() 1026 1027 _, funcErr, _, _, _ := testOpenAPITypeWriter(t, e.Config) 1028 if funcErr == nil { 1029 t.Fatalf("An error was expected") 1030 } 1031 assertEqual(t, 1032 "failed to generate map property in example.com/base/foo.Blah: StringToArray: map with non-string keys are not supported by OpenAPI in map[int]string", 1033 funcErr.Error()) 1034 }) 1035 } 1036 1037 func TestFailingSample2(t *testing.T) { 1038 inputFile := ` 1039 package foo 1040 1041 // Map sample tests openAPIGen.generateMapProperty method. 1042 type Blah struct { 1043 // A sample String to String map 1044 StringToArray map[int]string 1045 }` 1046 1047 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 1048 e := packagestest.Export(t, x, []packagestest.Module{{ 1049 Name: "example.com/base/foo", 1050 Files: map[string]interface{}{ 1051 "foo.go": inputFile, 1052 }, 1053 }}) 1054 defer e.Cleanup() 1055 1056 _, funcErr, _, _, _ := testOpenAPITypeWriter(t, e.Config) 1057 if funcErr == nil { 1058 t.Fatalf("An error was expected") 1059 } 1060 assertEqual(t, 1061 "failed to generate map property in example.com/base/foo.Blah: StringToArray: map with non-string keys are not supported by OpenAPI in map[int]string", 1062 funcErr.Error()) 1063 }) 1064 } 1065 1066 func TestFailingDefaultEnforced(t *testing.T) { 1067 tests := []struct { 1068 definition string 1069 expectedError string 1070 }{{ 1071 definition: ` 1072 package foo 1073 1074 type Blah struct { 1075 // +default=5 1076 Int int 1077 }`, 1078 expectedError: "failed to generate default in example.com/base/foo.Blah: Int: invalid default value (5) for non-pointer/non-omitempty. If specified, must be: 0", 1079 }, { 1080 definition: ` 1081 package foo 1082 1083 type Blah struct { 1084 // +default={"foo": 5} 1085 Struct struct{ 1086 foo int 1087 } 1088 }`, 1089 expectedError: `failed to generate default in example.com/base/foo.Blah: Struct: invalid default value (map[string]interface {}{"foo":5}) for non-pointer/non-omitempty. If specified, must be: {}`, 1090 }, { 1091 definition: ` 1092 package foo 1093 1094 type Blah struct { 1095 List []Item 1096 1097 } 1098 1099 // +default="foo" 1100 type Item string`, 1101 expectedError: `failed to generate slice property in example.com/base/foo.Blah: List: invalid default value ("foo") for non-pointer/non-omitempty. If specified, must be: ""`, 1102 }, { 1103 definition: ` 1104 package foo 1105 1106 type Blah struct { 1107 Map map[string]Item 1108 1109 } 1110 1111 // +default="foo" 1112 type Item string`, 1113 expectedError: `failed to generate map property in example.com/base/foo.Blah: Map: invalid default value ("foo") for non-pointer/non-omitempty. If specified, must be: ""`, 1114 }} 1115 1116 for i, test := range tests { 1117 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 1118 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 1119 e := packagestest.Export(t, x, []packagestest.Module{{ 1120 Name: "example.com/base/foo", 1121 Files: map[string]interface{}{ 1122 "foo.go": test.definition, 1123 }, 1124 }}) 1125 defer e.Cleanup() 1126 1127 _, funcErr, _, _, _ := testOpenAPITypeWriter(t, e.Config) 1128 if funcErr == nil { 1129 t.Fatalf("An error was expected") 1130 } 1131 assertEqual(t, test.expectedError, funcErr.Error()) 1132 }) 1133 }) 1134 } 1135 } 1136 1137 func TestCustomDef(t *testing.T) { 1138 inputFile := ` 1139 package foo 1140 1141 import openapi "k8s.io/kube-openapi/pkg/common" 1142 1143 type Blah struct { 1144 } 1145 1146 func (_ Blah) OpenAPIDefinition() openapi.OpenAPIDefinition { 1147 return openapi.OpenAPIDefinition{ 1148 Schema: spec.Schema{ 1149 SchemaProps: spec.SchemaProps{ 1150 Type: []string{"string"}, 1151 Format: "date-time", 1152 }, 1153 }, 1154 } 1155 }` 1156 commonFile := `package common 1157 1158 type OpenAPIDefinition struct {}` 1159 1160 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 1161 e := packagestest.Export(t, x, []packagestest.Module{{ 1162 Name: "example.com/base/foo", 1163 Files: map[string]interface{}{ 1164 "foo.go": inputFile, 1165 }, 1166 }, { 1167 Name: "k8s.io/kube-openapi/pkg/common", 1168 Files: map[string]interface{}{ 1169 "common.go": commonFile, 1170 }, 1171 }}) 1172 defer e.Cleanup() 1173 1174 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 1175 if callErr != nil { 1176 t.Fatal(callErr) 1177 } 1178 if funcErr != nil { 1179 t.Fatal(funcErr) 1180 } 1181 assertEqual(t, callBuffer.String(), 1182 `"example.com/base/foo.Blah": foo.Blah{}.OpenAPIDefinition(),`) 1183 assertEqual(t, "", funcBuffer.String()) 1184 }) 1185 } 1186 1187 func TestCustomDefV3(t *testing.T) { 1188 inputFile := ` 1189 package foo 1190 1191 import openapi "k8s.io/kube-openapi/pkg/common" 1192 1193 type Blah struct { 1194 } 1195 1196 func (_ Blah) OpenAPIV3Definition() openapi.OpenAPIDefinition { 1197 return openapi.OpenAPIDefinition{ 1198 Schema: spec.Schema{ 1199 SchemaProps: spec.SchemaProps{ 1200 Type: []string{"string"}, 1201 Format: "date-time", 1202 }, 1203 }, 1204 } 1205 }` 1206 commonFile := `package common 1207 1208 type OpenAPIDefinition struct {}` 1209 1210 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 1211 e := packagestest.Export(t, x, []packagestest.Module{{ 1212 Name: "example.com/base/foo", 1213 Files: map[string]interface{}{ 1214 "foo.go": inputFile, 1215 }, 1216 }, { 1217 Name: "k8s.io/kube-openapi/pkg/common", 1218 Files: map[string]interface{}{ 1219 "common.go": commonFile, 1220 }, 1221 }}) 1222 defer e.Cleanup() 1223 1224 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 1225 if callErr != nil { 1226 t.Fatal(callErr) 1227 } 1228 if funcErr != nil { 1229 t.Fatal(funcErr) 1230 } 1231 assertEqual(t, callBuffer.String(), 1232 `"example.com/base/foo.Blah": foo.Blah{}.OpenAPIV3Definition(),`) 1233 assertEqual(t, "", funcBuffer.String()) 1234 }) 1235 } 1236 1237 func TestCustomDefV2AndV3(t *testing.T) { 1238 inputFile := ` 1239 package foo 1240 1241 import openapi "k8s.io/kube-openapi/pkg/common" 1242 1243 type Blah struct { 1244 } 1245 1246 func (_ Blah) OpenAPIV3Definition() openapi.OpenAPIDefinition { 1247 return openapi.OpenAPIDefinition{ 1248 Schema: spec.Schema{ 1249 SchemaProps: spec.SchemaProps{ 1250 Type: []string{"string"}, 1251 Format: "date-time", 1252 }, 1253 }, 1254 } 1255 } 1256 1257 func (_ Blah) OpenAPIDefinition() openapi.OpenAPIDefinition { 1258 return openapi.OpenAPIDefinition{ 1259 Schema: spec.Schema{ 1260 SchemaProps: spec.SchemaProps{ 1261 Type: []string{"string"}, 1262 Format: "date-time", 1263 }, 1264 }, 1265 } 1266 }` 1267 commonFile := `package common 1268 1269 type OpenAPIDefinition struct {}` 1270 1271 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 1272 e := packagestest.Export(t, x, []packagestest.Module{{ 1273 Name: "example.com/base/foo", 1274 Files: map[string]interface{}{ 1275 "foo.go": inputFile, 1276 }, 1277 }, { 1278 Name: "k8s.io/kube-openapi/pkg/common", 1279 Files: map[string]interface{}{ 1280 "common.go": commonFile, 1281 }, 1282 }}) 1283 defer e.Cleanup() 1284 1285 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 1286 if callErr != nil { 1287 t.Fatal(callErr) 1288 } 1289 if funcErr != nil { 1290 t.Fatal(funcErr) 1291 } 1292 assertEqual(t, callBuffer.String(), 1293 `"example.com/base/foo.Blah": common.EmbedOpenAPIDefinitionIntoV2Extension(foo.Blah{}.OpenAPIV3Definition(), foo.Blah{}.OpenAPIDefinition()),`) 1294 assertEqual(t, "", funcBuffer.String()) 1295 }) 1296 } 1297 1298 func TestCustomDefs(t *testing.T) { 1299 inputFile := ` 1300 package foo 1301 1302 // Blah is a custom type 1303 type Blah struct { 1304 } 1305 1306 func (_ Blah) OpenAPISchemaType() []string { return []string{"string"} } 1307 func (_ Blah) OpenAPISchemaFormat() string { return "date-time" }` 1308 1309 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 1310 e := packagestest.Export(t, x, []packagestest.Module{{ 1311 Name: "example.com/base/foo", 1312 Files: map[string]interface{}{ 1313 "foo.go": inputFile, 1314 }, 1315 }}) 1316 defer e.Cleanup() 1317 1318 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 1319 if callErr != nil { 1320 t.Fatal(callErr) 1321 } 1322 if funcErr != nil { 1323 t.Fatal(funcErr) 1324 } 1325 assertEqual(t, callBuffer.String(), 1326 `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) 1327 assertEqual(t, funcBuffer.String(), 1328 `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 1329 return common.OpenAPIDefinition{ 1330 Schema: spec.Schema{ 1331 SchemaProps: spec.SchemaProps{ 1332 Description: "Blah is a custom type", 1333 Type:foo.Blah{}.OpenAPISchemaType(), 1334 Format:foo.Blah{}.OpenAPISchemaFormat(), 1335 }, 1336 }, 1337 } 1338 }`) 1339 }) 1340 } 1341 1342 func TestCustomDefsV3(t *testing.T) { 1343 inputFile := ` 1344 package foo 1345 1346 import openapi "k8s.io/kube-openapi/pkg/common" 1347 1348 // Blah is a custom type 1349 type Blah struct { 1350 } 1351 1352 func (_ Blah) OpenAPIV3Definition() openapi.OpenAPIDefinition { 1353 return openapi.OpenAPIDefinition{ 1354 Schema: spec.Schema{ 1355 SchemaProps: spec.SchemaProps{ 1356 Type: []string{"string"}, 1357 Format: "date-time", 1358 }, 1359 }, 1360 } 1361 } 1362 1363 func (_ Blah) OpenAPISchemaType() []string { return []string{"string"} } 1364 func (_ Blah) OpenAPISchemaFormat() string { return "date-time" }` 1365 commonFile := `package common 1366 1367 type OpenAPIDefinition struct {}` 1368 1369 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 1370 e := packagestest.Export(t, x, []packagestest.Module{{ 1371 Name: "example.com/base/foo", 1372 Files: map[string]interface{}{ 1373 "foo.go": inputFile, 1374 }, 1375 }, { 1376 Name: "k8s.io/kube-openapi/pkg/common", 1377 Files: map[string]interface{}{ 1378 "common.go": commonFile, 1379 }, 1380 }}) 1381 defer e.Cleanup() 1382 1383 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 1384 if callErr != nil { 1385 t.Fatal(callErr) 1386 } 1387 if funcErr != nil { 1388 t.Fatal(funcErr) 1389 } 1390 assertEqual(t, callBuffer.String(), 1391 `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) 1392 assertEqual(t, funcBuffer.String(), 1393 `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 1394 return common.EmbedOpenAPIDefinitionIntoV2Extension(foo.Blah{}.OpenAPIV3Definition(), common.OpenAPIDefinition{ 1395 Schema: spec.Schema{ 1396 SchemaProps: spec.SchemaProps{ 1397 Description: "Blah is a custom type", 1398 Type:foo.Blah{}.OpenAPISchemaType(), 1399 Format:foo.Blah{}.OpenAPISchemaFormat(), 1400 }, 1401 }, 1402 }) 1403 }`) 1404 }) 1405 } 1406 1407 func TestV3OneOfTypes(t *testing.T) { 1408 inputFile := ` 1409 package foo 1410 1411 // Blah is a custom type 1412 type Blah struct { 1413 } 1414 1415 func (_ Blah) OpenAPISchemaType() []string { return []string{"string"} } 1416 func (_ Blah) OpenAPISchemaFormat() string { return "date-time" } 1417 func (_ Blah) OpenAPIV3OneOfTypes() []string { return []string{"string", "number"} }` 1418 1419 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 1420 e := packagestest.Export(t, x, []packagestest.Module{{ 1421 Name: "example.com/base/foo", 1422 Files: map[string]interface{}{ 1423 "foo.go": inputFile, 1424 }, 1425 }}) 1426 defer e.Cleanup() 1427 1428 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 1429 if callErr != nil { 1430 t.Fatal(callErr) 1431 } 1432 if funcErr != nil { 1433 t.Fatal(funcErr) 1434 } 1435 assertEqual(t, callBuffer.String(), 1436 `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) 1437 assertEqual(t, funcBuffer.String(), 1438 `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 1439 return common.EmbedOpenAPIDefinitionIntoV2Extension(common.OpenAPIDefinition{ 1440 Schema: spec.Schema{ 1441 SchemaProps: spec.SchemaProps{ 1442 Description: "Blah is a custom type", 1443 OneOf:common.GenerateOpenAPIV3OneOfSchema(foo.Blah{}.OpenAPIV3OneOfTypes()), 1444 Format:foo.Blah{}.OpenAPISchemaFormat(), 1445 }, 1446 }, 1447 },common.OpenAPIDefinition{ 1448 Schema: spec.Schema{ 1449 SchemaProps: spec.SchemaProps{ 1450 Description: "Blah is a custom type", 1451 Type:foo.Blah{}.OpenAPISchemaType(), 1452 Format:foo.Blah{}.OpenAPISchemaFormat(), 1453 }, 1454 }, 1455 }) 1456 }`) 1457 }) 1458 } 1459 1460 func TestPointer(t *testing.T) { 1461 inputFile := ` 1462 package foo 1463 1464 // PointerSample demonstrate pointer's properties 1465 type Blah struct { 1466 // A string pointer 1467 StringPointer *string 1468 // A struct pointer 1469 StructPointer *Blah 1470 // A slice pointer 1471 SlicePointer *[]string 1472 // A map pointer 1473 MapPointer *map[string]string 1474 }` 1475 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 1476 e := packagestest.Export(t, x, []packagestest.Module{{ 1477 Name: "example.com/base/foo", 1478 Files: map[string]interface{}{ 1479 "foo.go": inputFile, 1480 }, 1481 }}) 1482 defer e.Cleanup() 1483 1484 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 1485 if callErr != nil { 1486 t.Fatal(callErr) 1487 } 1488 if funcErr != nil { 1489 t.Fatal(funcErr) 1490 } 1491 assertEqual(t, callBuffer.String(), 1492 `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) 1493 assertEqual(t, funcBuffer.String(), 1494 `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 1495 return common.OpenAPIDefinition{ 1496 Schema: spec.Schema{ 1497 SchemaProps: spec.SchemaProps{ 1498 Description: "PointerSample demonstrate pointer's properties", 1499 Type: []string{"object"}, 1500 Properties: map[string]spec.Schema{ 1501 "StringPointer": { 1502 SchemaProps: spec.SchemaProps{ 1503 Description: "A string pointer", 1504 Type: []string{"string"}, 1505 Format: "", 1506 }, 1507 }, 1508 "StructPointer": { 1509 SchemaProps: spec.SchemaProps{ 1510 Description: "A struct pointer", 1511 Ref: ref("example.com/base/foo.Blah"), 1512 }, 1513 }, 1514 "SlicePointer": { 1515 SchemaProps: spec.SchemaProps{ 1516 Description: "A slice pointer", 1517 Type: []string{"array"}, 1518 Items: &spec.SchemaOrArray{ 1519 Schema: &spec.Schema{ 1520 SchemaProps: spec.SchemaProps{ 1521 Default: "", 1522 Type: []string{"string"}, 1523 Format: "", 1524 }, 1525 }, 1526 }, 1527 }, 1528 }, 1529 "MapPointer": { 1530 SchemaProps: spec.SchemaProps{ 1531 Description: "A map pointer", 1532 Type: []string{"object"}, 1533 AdditionalProperties: &spec.SchemaOrBool{ 1534 Allows: true, 1535 Schema: &spec.Schema{ 1536 SchemaProps: spec.SchemaProps{ 1537 Default: "", 1538 Type: []string{"string"}, 1539 Format: "", 1540 }, 1541 }, 1542 }, 1543 }, 1544 }, 1545 }, 1546 Required: []string{"StringPointer","StructPointer","SlicePointer","MapPointer"}, 1547 }, 1548 }, 1549 Dependencies: []string{ 1550 "example.com/base/foo.Blah",}, 1551 } 1552 }`) 1553 }) 1554 } 1555 1556 func TestNestedLists(t *testing.T) { 1557 inputFile := ` 1558 package foo 1559 1560 // Blah is a test. 1561 // +k8s:openapi-gen=true 1562 // +k8s:openapi-gen=x-kubernetes-type-tag:type_test 1563 type Blah struct { 1564 // Nested list 1565 NestedList [][]int64 1566 }` 1567 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 1568 e := packagestest.Export(t, x, []packagestest.Module{{ 1569 Name: "example.com/base/foo", 1570 Files: map[string]interface{}{ 1571 "foo.go": inputFile, 1572 }, 1573 }}) 1574 defer e.Cleanup() 1575 1576 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 1577 if callErr != nil { 1578 t.Fatal(callErr) 1579 } 1580 if funcErr != nil { 1581 t.Fatal(funcErr) 1582 } 1583 assertEqual(t, callBuffer.String(), 1584 `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) 1585 assertEqual(t, funcBuffer.String(), 1586 `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 1587 return common.OpenAPIDefinition{ 1588 Schema: spec.Schema{ 1589 SchemaProps: spec.SchemaProps{ 1590 Description: "Blah is a test.", 1591 Type: []string{"object"}, 1592 Properties: map[string]spec.Schema{ 1593 "NestedList": { 1594 SchemaProps: spec.SchemaProps{ 1595 Description: "Nested list", 1596 Type: []string{"array"}, 1597 Items: &spec.SchemaOrArray{ 1598 Schema: &spec.Schema{ 1599 SchemaProps: spec.SchemaProps{ 1600 Type: []string{"array"}, 1601 Items: &spec.SchemaOrArray{ 1602 Schema: &spec.Schema{ 1603 SchemaProps: spec.SchemaProps{ 1604 Default: 0, 1605 Type: []string{"integer"}, 1606 Format: "int64", 1607 }, 1608 }, 1609 }, 1610 }, 1611 }, 1612 }, 1613 }, 1614 }, 1615 }, 1616 Required: []string{"NestedList"}, 1617 }, 1618 VendorExtensible: spec.VendorExtensible{ 1619 Extensions: spec.Extensions{ 1620 "x-kubernetes-type-tag": "type_test", 1621 }, 1622 }, 1623 }, 1624 } 1625 }`) 1626 }) 1627 } 1628 1629 func TestNestListOfMaps(t *testing.T) { 1630 inputFile := ` 1631 package foo 1632 1633 // Blah is a test. 1634 // +k8s:openapi-gen=true 1635 // +k8s:openapi-gen=x-kubernetes-type-tag:type_test 1636 type Blah struct { 1637 // Nested list of maps 1638 NestedListOfMaps [][]map[string]string 1639 }` 1640 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 1641 e := packagestest.Export(t, x, []packagestest.Module{{ 1642 Name: "example.com/base/foo", 1643 Files: map[string]interface{}{ 1644 "foo.go": inputFile, 1645 }, 1646 }}) 1647 defer e.Cleanup() 1648 1649 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 1650 if callErr != nil { 1651 t.Fatal(callErr) 1652 } 1653 if funcErr != nil { 1654 t.Fatal(funcErr) 1655 } 1656 assertEqual(t, callBuffer.String(), 1657 `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) 1658 assertEqual(t, funcBuffer.String(), 1659 `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 1660 return common.OpenAPIDefinition{ 1661 Schema: spec.Schema{ 1662 SchemaProps: spec.SchemaProps{ 1663 Description: "Blah is a test.", 1664 Type: []string{"object"}, 1665 Properties: map[string]spec.Schema{ 1666 "NestedListOfMaps": { 1667 SchemaProps: spec.SchemaProps{ 1668 Description: "Nested list of maps", 1669 Type: []string{"array"}, 1670 Items: &spec.SchemaOrArray{ 1671 Schema: &spec.Schema{ 1672 SchemaProps: spec.SchemaProps{ 1673 Type: []string{"array"}, 1674 Items: &spec.SchemaOrArray{ 1675 Schema: &spec.Schema{ 1676 SchemaProps: spec.SchemaProps{ 1677 Type: []string{"object"}, 1678 AdditionalProperties: &spec.SchemaOrBool{ 1679 Allows: true, 1680 Schema: &spec.Schema{ 1681 SchemaProps: spec.SchemaProps{ 1682 Default: "", 1683 Type: []string{"string"}, 1684 Format: "", 1685 }, 1686 }, 1687 }, 1688 }, 1689 }, 1690 }, 1691 }, 1692 }, 1693 }, 1694 }, 1695 }, 1696 }, 1697 Required: []string{"NestedListOfMaps"}, 1698 }, 1699 VendorExtensible: spec.VendorExtensible{ 1700 Extensions: spec.Extensions{ 1701 "x-kubernetes-type-tag": "type_test", 1702 }, 1703 }, 1704 }, 1705 } 1706 }`) 1707 }) 1708 } 1709 1710 func TestExtensions(t *testing.T) { 1711 inputFile := ` 1712 package foo 1713 1714 // Blah is a test. 1715 // +k8s:openapi-gen=true 1716 // +k8s:openapi-gen=x-kubernetes-type-tag:type_test 1717 type Blah struct { 1718 // a member with a list type with two map keys 1719 // +listType=map 1720 // +listMapKey=port 1721 // +listMapKey=protocol 1722 WithListField []string 1723 1724 // another member with a list type with one map key 1725 // +listType=map 1726 // +listMapKey=port 1727 WithListField2 []string 1728 }` 1729 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 1730 e := packagestest.Export(t, x, []packagestest.Module{{ 1731 Name: "example.com/base/foo", 1732 Files: map[string]interface{}{ 1733 "foo.go": inputFile, 1734 }, 1735 }}) 1736 defer e.Cleanup() 1737 1738 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 1739 if callErr != nil { 1740 t.Fatal(callErr) 1741 } 1742 if funcErr != nil { 1743 t.Fatal(funcErr) 1744 } 1745 assertEqual(t, callBuffer.String(), 1746 `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) 1747 assertEqual(t, funcBuffer.String(), 1748 `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 1749 return common.OpenAPIDefinition{ 1750 Schema: spec.Schema{ 1751 SchemaProps: spec.SchemaProps{ 1752 Description: "Blah is a test.", 1753 Type: []string{"object"}, 1754 Properties: map[string]spec.Schema{ 1755 "WithListField": { 1756 VendorExtensible: spec.VendorExtensible{ 1757 Extensions: spec.Extensions{ 1758 "x-kubernetes-list-map-keys": []interface{}{ 1759 "port", 1760 "protocol", 1761 }, 1762 "x-kubernetes-list-type": "map", 1763 }, 1764 }, 1765 SchemaProps: spec.SchemaProps{ 1766 Description: "a member with a list type with two map keys", 1767 Type: []string{"array"}, 1768 Items: &spec.SchemaOrArray{ 1769 Schema: &spec.Schema{ 1770 SchemaProps: spec.SchemaProps{ 1771 Default: "", 1772 Type: []string{"string"}, 1773 Format: "", 1774 }, 1775 }, 1776 }, 1777 }, 1778 }, 1779 "WithListField2": { 1780 VendorExtensible: spec.VendorExtensible{ 1781 Extensions: spec.Extensions{ 1782 "x-kubernetes-list-map-keys": []interface{}{ 1783 "port", 1784 }, 1785 "x-kubernetes-list-type": "map", 1786 }, 1787 }, 1788 SchemaProps: spec.SchemaProps{ 1789 Description: "another member with a list type with one map key", 1790 Type: []string{"array"}, 1791 Items: &spec.SchemaOrArray{ 1792 Schema: &spec.Schema{ 1793 SchemaProps: spec.SchemaProps{ 1794 Default: "", 1795 Type: []string{"string"}, 1796 Format: "", 1797 }, 1798 }, 1799 }, 1800 }, 1801 }, 1802 }, 1803 Required: []string{"WithListField","WithListField2"}, 1804 }, 1805 VendorExtensible: spec.VendorExtensible{ 1806 Extensions: spec.Extensions{ 1807 "x-kubernetes-type-tag": "type_test", 1808 }, 1809 }, 1810 }, 1811 } 1812 }`) 1813 }) 1814 } 1815 1816 func TestUnion(t *testing.T) { 1817 inputFile := ` 1818 package foo 1819 1820 // Blah is a test. 1821 // +k8s:openapi-gen=true 1822 // +k8s:openapi-gen=x-kubernetes-type-tag:type_test 1823 // +union 1824 type Blah struct { 1825 // +unionDiscriminator 1826 Discriminator *string ` + "`" + `json:"discriminator"` + "`" + ` 1827 // +optional 1828 Numeric int ` + "`" + `json:"numeric"` + "`" + ` 1829 // +optional 1830 String string ` + "`" + `json:"string"` + "`" + ` 1831 // +optional 1832 Float float64 ` + "`" + `json:"float"` + "`" + ` 1833 }` 1834 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 1835 e := packagestest.Export(t, x, []packagestest.Module{{ 1836 Name: "example.com/base/foo", 1837 Files: map[string]interface{}{ 1838 "foo.go": inputFile, 1839 }, 1840 }}) 1841 defer e.Cleanup() 1842 1843 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 1844 if callErr != nil { 1845 t.Fatal(callErr) 1846 } 1847 if funcErr != nil { 1848 t.Fatal(funcErr) 1849 } 1850 assertEqual(t, callBuffer.String(), 1851 `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) 1852 assertEqual(t, funcBuffer.String(), 1853 `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 1854 return common.OpenAPIDefinition{ 1855 Schema: spec.Schema{ 1856 SchemaProps: spec.SchemaProps{ 1857 Description: "Blah is a test.", 1858 Type: []string{"object"}, 1859 Properties: map[string]spec.Schema{ 1860 "discriminator": { 1861 SchemaProps: spec.SchemaProps{ 1862 Type: []string{"string"}, 1863 Format: "", 1864 }, 1865 }, 1866 "numeric": { 1867 SchemaProps: spec.SchemaProps{ 1868 Default: 0, 1869 Type: []string{"integer"}, 1870 Format: "int32", 1871 }, 1872 }, 1873 "string": { 1874 SchemaProps: spec.SchemaProps{ 1875 Default: "", 1876 Type: []string{"string"}, 1877 Format: "", 1878 }, 1879 }, 1880 "float": { 1881 SchemaProps: spec.SchemaProps{ 1882 Default: 0, 1883 Type: []string{"number"}, 1884 Format: "double", 1885 }, 1886 }, 1887 }, 1888 Required: []string{"discriminator"}, 1889 }, 1890 VendorExtensible: spec.VendorExtensible{ 1891 Extensions: spec.Extensions{ 1892 "x-kubernetes-type-tag": "type_test", 1893 "x-kubernetes-unions": []interface{}{ 1894 map[string]interface{}{ 1895 "discriminator": "discriminator", 1896 "fields-to-discriminateBy": map[string]interface{}{ 1897 "float": "Float", 1898 "numeric": "Numeric", 1899 "string": "String", 1900 }, 1901 }, 1902 }, 1903 }, 1904 }, 1905 }, 1906 } 1907 }`) 1908 }) 1909 } 1910 1911 func TestEnumAlias(t *testing.T) { 1912 inputFile := ` 1913 package foo 1914 1915 import "example.com/base/bar" 1916 1917 // EnumType is the enumType. 1918 // +enum 1919 type EnumType = bar.EnumType 1920 1921 // EnumA is a. 1922 const EnumA EnumType = bar.EnumA 1923 // EnumB is b. 1924 const EnumB EnumType = bar.EnumB 1925 1926 // Blah is a test. 1927 // +k8s:openapi-gen=true 1928 type Blah struct { 1929 // Value is the value. 1930 Value EnumType 1931 }` 1932 otherFile := ` 1933 package bar 1934 1935 // EnumType is the enumType. 1936 // +enum 1937 type EnumType string 1938 1939 // EnumA is a. 1940 const EnumA EnumType = "a" 1941 // EnumB is b. 1942 const EnumB EnumType = "b"` 1943 1944 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 1945 e := packagestest.Export(t, x, []packagestest.Module{{ 1946 Name: "example.com/base/foo", 1947 Files: map[string]interface{}{ 1948 "foo.go": inputFile, 1949 }, 1950 }, { 1951 Name: "example.com/base/bar", 1952 Files: map[string]interface{}{ 1953 "bar.go": otherFile, 1954 }, 1955 }}) 1956 defer e.Cleanup() 1957 1958 callErr, funcErr, _, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 1959 if callErr != nil { 1960 t.Fatal(callErr) 1961 } 1962 if funcErr != nil { 1963 t.Fatal(funcErr) 1964 } 1965 assertEqual(t, funcBuffer.String(), 1966 `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 1967 return common.OpenAPIDefinition{ 1968 Schema: spec.Schema{ 1969 SchemaProps: spec.SchemaProps{ 1970 Description: "Blah is a test.", 1971 Type: []string{"object"}, 1972 Properties: map[string]spec.Schema{ 1973 "Value": { 1974 SchemaProps: spec.SchemaProps{`+"\n"+ 1975 "Description: \"Value is the value.\\n\\nPossible enum values:\\n - `\\\"a\\\"` is a.\\n - `\\\"b\\\"` is b.\","+` 1976 Default: "", 1977 Type: []string{"string"}, 1978 Format: "", 1979 Enum: []interface{}{"a", "b"}, 1980 }, 1981 }, 1982 }, 1983 Required: []string{"Value"}, 1984 }, 1985 }, 1986 } 1987 }`) 1988 }) 1989 } 1990 1991 func TestEnum(t *testing.T) { 1992 inputFile := ` 1993 package foo 1994 1995 // EnumType is the enumType. 1996 // +enum 1997 type EnumType string 1998 1999 // EnumA is a. 2000 const EnumA EnumType = "a" 2001 // EnumB is b. 2002 const EnumB EnumType = "b" 2003 2004 // Blah is a test. 2005 // +k8s:openapi-gen=true 2006 // +k8s:openapi-gen=x-kubernetes-type-tag:type_test 2007 type Blah struct { 2008 // Value is the value. 2009 Value EnumType 2010 NoCommentEnum EnumType 2011 // +optional 2012 OptionalEnum *EnumType 2013 List []EnumType 2014 Map map[string]EnumType 2015 }` 2016 2017 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 2018 e := packagestest.Export(t, x, []packagestest.Module{{ 2019 Name: "example.com/base/foo", 2020 Files: map[string]interface{}{ 2021 "foo.go": inputFile, 2022 }, 2023 }}) 2024 defer e.Cleanup() 2025 2026 callErr, funcErr, _, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 2027 if callErr != nil { 2028 t.Fatal(callErr) 2029 } 2030 if funcErr != nil { 2031 t.Fatal(funcErr) 2032 } 2033 assertEqual(t, funcBuffer.String(), 2034 `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 2035 return common.OpenAPIDefinition{ 2036 Schema: spec.Schema{ 2037 SchemaProps: spec.SchemaProps{ 2038 Description: "Blah is a test.", 2039 Type: []string{"object"}, 2040 Properties: map[string]spec.Schema{ 2041 "Value": { 2042 SchemaProps: spec.SchemaProps{`+"\n"+ 2043 "Description: \"Value is the value.\\n\\nPossible enum values:\\n - `\\\"a\\\"` is a.\\n - `\\\"b\\\"` is b.\","+` 2044 Default: "", 2045 Type: []string{"string"}, 2046 Format: "", 2047 Enum: []interface{}{"a", "b"}, 2048 }, 2049 }, 2050 "NoCommentEnum": { 2051 SchemaProps: spec.SchemaProps{`+"\n"+ 2052 "Description: \"Possible enum values:\\n - `\\\"a\\\"` is a.\\n - `\\\"b\\\"` is b.\","+` 2053 Default: "", 2054 Type: []string{"string"}, 2055 Format: "", 2056 Enum: []interface{}{"a", "b"}, 2057 }, 2058 }, 2059 "OptionalEnum": { 2060 SchemaProps: spec.SchemaProps{`+"\n"+ 2061 "Description: \"Possible enum values:\\n - `\\\"a\\\"` is a.\\n - `\\\"b\\\"` is b.\","+` 2062 Type: []string{"string"}, 2063 Format: "", 2064 Enum: []interface{}{"a", "b"}, 2065 }, 2066 }, 2067 "List": { 2068 SchemaProps: spec.SchemaProps{ 2069 Type: []string{"array"}, 2070 Items: &spec.SchemaOrArray{ 2071 Schema: &spec.Schema{ 2072 SchemaProps: spec.SchemaProps{ 2073 Default: "", 2074 Type: []string{"string"}, 2075 Format: "", 2076 Enum: []interface{}{"a", "b"}, 2077 }, 2078 }, 2079 }, 2080 }, 2081 }, 2082 "Map": { 2083 SchemaProps: spec.SchemaProps{ 2084 Type: []string{"object"}, 2085 AdditionalProperties: &spec.SchemaOrBool{ 2086 Allows: true, 2087 Schema: &spec.Schema{ 2088 SchemaProps: spec.SchemaProps{ 2089 Default: "", 2090 Type: []string{"string"}, 2091 Format: "", 2092 Enum: []interface{}{"a", "b"}, 2093 }, 2094 }, 2095 }, 2096 }, 2097 }, 2098 }, 2099 Required: []string{"Value","NoCommentEnum","List","Map"}, 2100 }, 2101 VendorExtensible: spec.VendorExtensible{ 2102 Extensions: spec.Extensions{ 2103 "x-kubernetes-type-tag": "type_test", 2104 }, 2105 }, 2106 }, 2107 } 2108 }`) 2109 }) 2110 } 2111 2112 func TestSymbolReference(t *testing.T) { 2113 inputFile := ` 2114 package foo 2115 2116 // +k8s:openapi-gen=true 2117 type Blah struct { 2118 // +default="A Default Value" 2119 // +optional 2120 Value *string 2121 2122 // User constant local to the output package fully qualified 2123 // +default=ref(example.com/base/output.MyConst) 2124 // +optional 2125 FullyQualifiedOutputValue *string 2126 2127 // Local to types but not to output 2128 // +default=ref(MyConst) 2129 // +optional 2130 LocalValue *string 2131 2132 // +default=ref(example.com/base/foo.MyConst) 2133 // +optional 2134 FullyQualifiedLocalValue *string 2135 2136 // +default=ref(k8s.io/api/v1.TerminationPathDefault) 2137 // +optional 2138 FullyQualifiedExternalValue *string 2139 }` 2140 2141 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 2142 e := packagestest.Export(t, x, []packagestest.Module{{ 2143 Name: "example.com/base/foo", 2144 Files: map[string]interface{}{ 2145 "foo.go": inputFile, 2146 }, 2147 }}) 2148 defer e.Cleanup() 2149 2150 callErr, funcErr, _, funcBuffer, imports := testOpenAPITypeWriter(t, e.Config) 2151 if funcErr != nil { 2152 t.Fatalf("Unexpected funcErr: %v", funcErr) 2153 } 2154 if callErr != nil { 2155 t.Fatalf("Unexpected callErr: %v", callErr) 2156 } 2157 expImports := []string{ 2158 `foo "example.com/base/foo"`, 2159 `v1 "k8s.io/api/v1"`, 2160 `common "k8s.io/kube-openapi/pkg/common"`, 2161 `spec "k8s.io/kube-openapi/pkg/validation/spec"`, 2162 } 2163 if !cmp.Equal(imports, expImports) { 2164 t.Errorf("wrong imports:\n%s", cmp.Diff(expImports, imports)) 2165 } 2166 2167 if formatted, err := format.Source(funcBuffer.Bytes()); err != nil { 2168 t.Fatal(err) 2169 } else { 2170 assertEqual(t, string(formatted), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 2171 return common.OpenAPIDefinition{ 2172 Schema: spec.Schema{ 2173 SchemaProps: spec.SchemaProps{ 2174 Type: []string{"object"}, 2175 Properties: map[string]spec.Schema{ 2176 "Value": { 2177 SchemaProps: spec.SchemaProps{ 2178 Default: "A Default Value", 2179 Type: []string{"string"}, 2180 Format: "", 2181 }, 2182 }, 2183 "FullyQualifiedOutputValue": { 2184 SchemaProps: spec.SchemaProps{ 2185 Description: "User constant local to the output package fully qualified", 2186 Default: MyConst, 2187 Type: []string{"string"}, 2188 Format: "", 2189 }, 2190 }, 2191 "LocalValue": { 2192 SchemaProps: spec.SchemaProps{ 2193 Description: "Local to types but not to output", 2194 Default: foo.MyConst, 2195 Type: []string{"string"}, 2196 Format: "", 2197 }, 2198 }, 2199 "FullyQualifiedLocalValue": { 2200 SchemaProps: spec.SchemaProps{ 2201 Default: foo.MyConst, 2202 Type: []string{"string"}, 2203 Format: "", 2204 }, 2205 }, 2206 "FullyQualifiedExternalValue": { 2207 SchemaProps: spec.SchemaProps{ 2208 Default: v1.TerminationPathDefault, 2209 Type: []string{"string"}, 2210 Format: "", 2211 }, 2212 }, 2213 }, 2214 }, 2215 }, 2216 } 2217 }`) 2218 } 2219 }) 2220 } 2221 2222 // Show that types with unmarshalJSON in their hierarchy do not have struct 2223 // defaults enforced, and that aliases and embededd types are respected 2224 func TestMustEnforceDefaultStruct(t *testing.T) { 2225 inputFile := ` 2226 package foo 2227 2228 type Time struct { 2229 value interface{} 2230 } 2231 2232 2233 type TimeWithoutUnmarshal struct { 2234 value interface{} 2235 } 2236 2237 func (_ TimeWithoutUnmarshal) OpenAPISchemaType() []string { return []string{"string"} } 2238 func (_ TimeWithoutUnmarshal) OpenAPISchemaFormat() string { return "date-time" } 2239 2240 func (_ Time) UnmarshalJSON([]byte) error { 2241 return nil 2242 } 2243 2244 2245 func (_ Time) OpenAPISchemaType() []string { return []string{"string"} } 2246 func (_ Time) OpenAPISchemaFormat() string { return "date-time" } 2247 2248 // Time with UnmarshalJSON defined on pointer instead of struct 2249 type MicroTime struct { 2250 value interface{} 2251 } 2252 2253 func (t *MicroTime) UnmarshalJSON([]byte) error { 2254 return nil 2255 } 2256 2257 func (_ MicroTime) OpenAPISchemaType() []string { return []string{"string"} } 2258 func (_ MicroTime) OpenAPISchemaFormat() string { return "date-time" } 2259 2260 type Int64 int64 2261 2262 type Duration struct { 2263 Int64 2264 } 2265 2266 func (_ Duration) OpenAPISchemaType() []string { return []string{"string"} } 2267 func (_ Duration) OpenAPISchemaFormat() string { return "" } 2268 2269 type NothingSpecial struct { 2270 Field string 2271 } 2272 2273 // +k8s:openapi-gen=true 2274 type Blah struct { 2275 Embedded Duration 2276 PointerUnmarshal MicroTime 2277 StructUnmarshal Time 2278 NoUnmarshal TimeWithoutUnmarshal 2279 Regular NothingSpecial 2280 }` 2281 2282 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 2283 e := packagestest.Export(t, x, []packagestest.Module{{ 2284 Name: "example.com/base/foo", 2285 Files: map[string]interface{}{ 2286 "foo.go": inputFile, 2287 }, 2288 }}) 2289 defer e.Cleanup() 2290 2291 callErr, funcErr, _, funcBuffer, imports := testOpenAPITypeWriter(t, e.Config) 2292 if funcErr != nil { 2293 t.Fatalf("Unexpected funcErr: %v", funcErr) 2294 } 2295 if callErr != nil { 2296 t.Fatalf("Unexpected callErr: %v", callErr) 2297 } 2298 expImports := []string{ 2299 `foo "example.com/base/foo"`, 2300 `common "k8s.io/kube-openapi/pkg/common"`, 2301 `spec "k8s.io/kube-openapi/pkg/validation/spec"`, 2302 } 2303 if !cmp.Equal(imports, expImports) { 2304 t.Errorf("wrong imports:\n%s", cmp.Diff(expImports, imports)) 2305 } 2306 2307 if formatted, err := format.Source(funcBuffer.Bytes()); err != nil { 2308 t.Fatal(err) 2309 } else { 2310 assertEqual(t, string(formatted), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 2311 return common.OpenAPIDefinition{ 2312 Schema: spec.Schema{ 2313 SchemaProps: spec.SchemaProps{ 2314 Type: []string{"object"}, 2315 Properties: map[string]spec.Schema{ 2316 "Embedded": { 2317 SchemaProps: spec.SchemaProps{ 2318 Default: 0, 2319 Ref: ref("example.com/base/foo.Duration"), 2320 }, 2321 }, 2322 "PointerUnmarshal": { 2323 SchemaProps: spec.SchemaProps{ 2324 Ref: ref("example.com/base/foo.MicroTime"), 2325 }, 2326 }, 2327 "StructUnmarshal": { 2328 SchemaProps: spec.SchemaProps{ 2329 Ref: ref("example.com/base/foo.Time"), 2330 }, 2331 }, 2332 "NoUnmarshal": { 2333 SchemaProps: spec.SchemaProps{ 2334 Default: map[string]interface{}{}, 2335 Ref: ref("example.com/base/foo.TimeWithoutUnmarshal"), 2336 }, 2337 }, 2338 "Regular": { 2339 SchemaProps: spec.SchemaProps{ 2340 Default: map[string]interface{}{}, 2341 Ref: ref("example.com/base/foo.NothingSpecial"), 2342 }, 2343 }, 2344 }, 2345 Required: []string{"Embedded", "PointerUnmarshal", "StructUnmarshal", "NoUnmarshal", "Regular"}, 2346 }, 2347 }, 2348 Dependencies: []string{ 2349 "example.com/base/foo.Duration", "example.com/base/foo.MicroTime", "example.com/base/foo.NothingSpecial", "example.com/base/foo.Time", "example.com/base/foo.TimeWithoutUnmarshal"}, 2350 } 2351 }`) 2352 } 2353 }) 2354 } 2355 2356 func TestMarkerComments(t *testing.T) { 2357 inputFile := ` 2358 package foo 2359 2360 // +k8s:openapi-gen=true 2361 // +k8s:validation:maxProperties=10 2362 // +k8s:validation:minProperties=1 2363 // +k8s:validation:exclusiveMinimum 2364 // +k8s:validation:exclusiveMaximum 2365 type Blah struct { 2366 2367 // Integer with min and max values 2368 // +k8s:validation:minimum=0 2369 // +k8s:validation:maximum=10 2370 // +k8s:validation:exclusiveMinimum 2371 // +k8s:validation:exclusiveMaximum 2372 IntValue int 2373 2374 // String with min and max lengths 2375 // +k8s:validation:minLength=1 2376 // +k8s:validation:maxLength=10 2377 // +k8s:validation:pattern="^foo$[0-9]+" 2378 StringValue string 2379 2380 // +k8s:validation:maxitems=10 2381 // +k8s:validation:minItems=1 2382 // +k8s:validation:uniqueItems 2383 ArrayValue []string 2384 2385 // +k8s:validation:maxProperties=10 2386 // +k8s:validation:minProperties=1 2387 ObjValue map[string]interface{} 2388 }` 2389 2390 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 2391 e := packagestest.Export(t, x, []packagestest.Module{{ 2392 Name: "example.com/base/foo", 2393 Files: map[string]interface{}{ 2394 "foo.go": inputFile, 2395 }, 2396 }}) 2397 defer e.Cleanup() 2398 2399 callErr, funcErr, _, funcBuffer, imports := testOpenAPITypeWriter(t, e.Config) 2400 if funcErr != nil { 2401 t.Fatalf("Unexpected funcErr: %v", funcErr) 2402 } 2403 if callErr != nil { 2404 t.Fatalf("Unexpected callErr: %v", callErr) 2405 } 2406 expImports := []string{ 2407 `foo "example.com/base/foo"`, 2408 `common "k8s.io/kube-openapi/pkg/common"`, 2409 `spec "k8s.io/kube-openapi/pkg/validation/spec"`, 2410 `ptr "k8s.io/utils/ptr"`, 2411 } 2412 if !cmp.Equal(imports, expImports) { 2413 t.Errorf("wrong imports:\n%s", cmp.Diff(expImports, imports)) 2414 } 2415 2416 if formatted, err := format.Source(funcBuffer.Bytes()); err != nil { 2417 t.Fatalf("%v\n%v", err, string(funcBuffer.Bytes())) 2418 } else { 2419 formatted_expected, ree := format.Source([]byte(`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 2420 return common.OpenAPIDefinition{ 2421 Schema: spec.Schema{ 2422 SchemaProps: spec.SchemaProps{ 2423 Type: []string{"object"}, 2424 ExclusiveMinimum: true, 2425 ExclusiveMaximum: true, 2426 MinProperties: ptr.To[int64](1), 2427 MaxProperties: ptr.To[int64](10), 2428 Properties: map[string]spec.Schema{ 2429 "IntValue": { 2430 SchemaProps: spec.SchemaProps{ 2431 Description: "Integer with min and max values", 2432 Default: 0, 2433 Minimum: ptr.To[float64](0), 2434 Maximum: ptr.To[float64](10), 2435 ExclusiveMinimum: true, 2436 ExclusiveMaximum: true, 2437 Type: []string{"integer"}, 2438 Format: "int32", 2439 }, 2440 }, 2441 "StringValue": { 2442 SchemaProps: spec.SchemaProps{ 2443 Description: "String with min and max lengths", 2444 Default: "", 2445 MinLength: ptr.To[int64](1), 2446 MaxLength: ptr.To[int64](10), 2447 Pattern: "^foo$[0-9]+", 2448 Type: []string{"string"}, 2449 Format: "", 2450 }, 2451 }, 2452 "ArrayValue": { 2453 SchemaProps: spec.SchemaProps{ 2454 MinItems: ptr.To[int64](1), 2455 MaxItems: ptr.To[int64](10), 2456 UniqueItems: true, 2457 Type: []string{"array"}, 2458 Items: &spec.SchemaOrArray{ 2459 Schema: &spec.Schema{ 2460 SchemaProps: spec.SchemaProps{ 2461 Default: "", 2462 Type: []string{"string"}, 2463 Format: "", 2464 }, 2465 }, 2466 }, 2467 }, 2468 }, 2469 "ObjValue": { 2470 SchemaProps: spec.SchemaProps{ 2471 MinProperties: ptr.To[int64](1), 2472 MaxProperties: ptr.To[int64](10), 2473 Type: []string{"object"}, 2474 AdditionalProperties: &spec.SchemaOrBool{ 2475 Allows: true, 2476 Schema: &spec.Schema{ 2477 SchemaProps: spec.SchemaProps{ 2478 Type: []string{"object"}, 2479 Format: "", 2480 }, 2481 }, 2482 }, 2483 }, 2484 }, 2485 }, 2486 Required: []string{"IntValue", "StringValue", "ArrayValue", "ObjValue"}, 2487 }, 2488 }, 2489 } 2490 }`)) 2491 if ree != nil { 2492 t.Fatal(ree) 2493 } 2494 assertEqual(t, string(formatted), string(formatted_expected)) 2495 } 2496 }) 2497 } 2498 2499 func TestCELMarkerComments(t *testing.T) { 2500 inputFile := ` 2501 package foo 2502 2503 // +k8s:openapi-gen=true 2504 // +k8s:validation:cel[0]:rule="self == oldSelf" 2505 // +k8s:validation:cel[0]:message="message1" 2506 type Blah struct { 2507 // +k8s:validation:cel[0]:rule="self.length() > 0" 2508 // +k8s:validation:cel[0]:message="string message" 2509 // +k8s:validation:cel[1]:rule="self.length() % 2 == 0" 2510 // +k8s:validation:cel[1]:messageExpression="self + ' hello'" 2511 // +k8s:validation:cel[1]:optionalOldSelf 2512 // +optional 2513 Field string 2514 }` 2515 2516 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 2517 e := packagestest.Export(t, x, []packagestest.Module{{ 2518 Name: "example.com/base/foo", 2519 Files: map[string]interface{}{ 2520 "foo.go": inputFile, 2521 }, 2522 }}) 2523 defer e.Cleanup() 2524 2525 callErr, funcErr, _, funcBuffer, imports := testOpenAPITypeWriter(t, e.Config) 2526 if funcErr != nil { 2527 t.Fatalf("Unexpected funcErr: %v", funcErr) 2528 } 2529 if callErr != nil { 2530 t.Fatalf("Unexpected callErr: %v", callErr) 2531 } 2532 expImports := []string{ 2533 `foo "example.com/base/foo"`, 2534 `common "k8s.io/kube-openapi/pkg/common"`, 2535 `spec "k8s.io/kube-openapi/pkg/validation/spec"`, 2536 } 2537 if !cmp.Equal(imports, expImports) { 2538 t.Errorf("wrong imports:\n%s", cmp.Diff(expImports, imports)) 2539 } 2540 2541 if formatted, err := format.Source(funcBuffer.Bytes()); err != nil { 2542 t.Fatalf("%v\n%v", err, string(funcBuffer.Bytes())) 2543 } else { 2544 formatted_expected, ree := format.Source([]byte(`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 2545 return common.OpenAPIDefinition{ 2546 Schema: spec.Schema{ 2547 SchemaProps: spec.SchemaProps{ 2548 Type: []string{"object"}, 2549 Properties: map[string]spec.Schema{ 2550 "Field": { 2551 VendorExtensible: spec.VendorExtensible{ 2552 Extensions: spec.Extensions{ 2553 "x-kubernetes-validations": []interface{}{map[string]interface{}{"message": "string message", "rule": "self.length() > 0"}, map[string]interface{}{"messageExpression": "self + ' hello'", "optionalOldSelf": true, "rule": "self.length() % 2 == 0"}}, 2554 }, 2555 }, 2556 SchemaProps: spec.SchemaProps{ 2557 Default: "", 2558 Type: []string{"string"}, 2559 Format: "", 2560 }, 2561 }, 2562 }, 2563 }, 2564 VendorExtensible: spec.VendorExtensible{ 2565 Extensions: spec.Extensions{ 2566 "x-kubernetes-validations": []interface{}{map[string]interface{}{"message": "message1", "rule": "self == oldSelf"}}, 2567 }, 2568 }, 2569 }, 2570 } 2571 }`)) 2572 if ree != nil { 2573 t.Fatal(ree) 2574 } 2575 assertEqual(t, string(formatted_expected), string(formatted)) 2576 } 2577 }) 2578 } 2579 2580 func TestMultilineCELMarkerComments(t *testing.T) { 2581 inputFile := ` 2582 package foo 2583 2584 // +k8s:openapi-gen=true 2585 // +k8s:validation:cel[0]:rule="self == oldSelf" 2586 // +k8s:validation:cel[0]:message="message1" 2587 // +k8s:validation:cel[0]:fieldPath="field" 2588 type Blah struct { 2589 // +k8s:validation:cel[0]:rule="self.length() > 0" 2590 // +k8s:validation:cel[0]:message="string message" 2591 // +k8s:validation:cel[0]:reason="Invalid" 2592 // +k8s:validation:cel[1]:rule> !oldSelf.hasValue() || self.length() % 2 == 0 2593 // +k8s:validation:cel[1]:rule> ? self.field == "even" 2594 // +k8s:validation:cel[1]:rule> : self.field == "odd" 2595 // +k8s:validation:cel[1]:messageExpression="field must be whether the length of the string is even or odd" 2596 // +k8s:validation:cel[1]:optionalOldSelf 2597 // +optional 2598 Field string 2599 }` 2600 2601 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 2602 e := packagestest.Export(t, x, []packagestest.Module{{ 2603 Name: "example.com/base/foo", 2604 Files: map[string]interface{}{ 2605 "foo.go": inputFile, 2606 }, 2607 }}) 2608 defer e.Cleanup() 2609 2610 callErr, funcErr, _, funcBuffer, imports := testOpenAPITypeWriter(t, e.Config) 2611 if funcErr != nil { 2612 t.Fatalf("Unexpected funcErr: %v", funcErr) 2613 } 2614 if callErr != nil { 2615 t.Fatalf("Unexpected callErr: %v", callErr) 2616 } 2617 expImports := []string{ 2618 `foo "example.com/base/foo"`, 2619 `common "k8s.io/kube-openapi/pkg/common"`, 2620 `spec "k8s.io/kube-openapi/pkg/validation/spec"`, 2621 } 2622 if !cmp.Equal(imports, expImports) { 2623 t.Errorf("wrong imports:\n%s", cmp.Diff(expImports, imports)) 2624 } 2625 2626 if formatted, err := format.Source(funcBuffer.Bytes()); err != nil { 2627 t.Fatalf("%v\n%v", err, string(funcBuffer.Bytes())) 2628 } else { 2629 formatted_expected, ree := format.Source([]byte(`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 2630 return common.OpenAPIDefinition{ 2631 Schema: spec.Schema{ 2632 SchemaProps: spec.SchemaProps{ 2633 Type: []string{"object"}, 2634 Properties: map[string]spec.Schema{ 2635 "Field": { 2636 VendorExtensible: spec.VendorExtensible{ 2637 Extensions: spec.Extensions{ 2638 "x-kubernetes-validations": []interface{}{map[string]interface{}{"message": "string message", "reason": "Invalid", "rule": "self.length() > 0"}, map[string]interface{}{"messageExpression": "field must be whether the length of the string is even or odd", "optionalOldSelf": true, "rule": "!oldSelf.hasValue() || self.length() % 2 == 0\n? self.field == \"even\"\n: self.field == \"odd\""}}, 2639 }, 2640 }, 2641 SchemaProps: spec.SchemaProps{ 2642 Default: "", 2643 Type: []string{"string"}, 2644 Format: "", 2645 }, 2646 }, 2647 }, 2648 }, 2649 VendorExtensible: spec.VendorExtensible{ 2650 Extensions: spec.Extensions{ 2651 "x-kubernetes-validations": []interface{}{map[string]interface{}{"fieldPath": "field", "message": "message1", "rule": "self == oldSelf"}}, 2652 }, 2653 }, 2654 }, 2655 } 2656 }`)) 2657 if ree != nil { 2658 t.Fatal(ree) 2659 } 2660 assertEqual(t, string(formatted_expected), string(formatted)) 2661 } 2662 }) 2663 } 2664 2665 func TestRequired(t *testing.T) { 2666 inputFile := ` 2667 package foo 2668 2669 // +k8s:openapi-gen=true 2670 type Blah struct { 2671 // +optional 2672 OptionalField string 2673 2674 // +required 2675 RequiredField string 2676 2677 // +required 2678 RequiredPointerField *string ` + "`json:\"requiredPointerField,omitempty\"`" + ` 2679 2680 // +optional 2681 OptionalPointerField *string ` + "`json:\"optionalPointerField,omitempty\"`" + ` 2682 2683 ImplicitlyRequiredField string 2684 ImplicitlyOptionalField string ` + "`json:\"implicitlyOptionalField,omitempty\"`" + ` 2685 }` 2686 2687 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 2688 e := packagestest.Export(t, x, []packagestest.Module{{ 2689 Name: "example.com/base/foo", 2690 Files: map[string]interface{}{ 2691 "foo.go": inputFile, 2692 }, 2693 }}) 2694 defer e.Cleanup() 2695 2696 callErr, funcErr, _, funcBuffer, imports := testOpenAPITypeWriter(t, e.Config) 2697 if funcErr != nil { 2698 t.Fatalf("Unexpected funcErr: %v", funcErr) 2699 } 2700 if callErr != nil { 2701 t.Fatalf("Unexpected callErr: %v", callErr) 2702 } 2703 expImports := []string{ 2704 `foo "example.com/base/foo"`, 2705 `common "k8s.io/kube-openapi/pkg/common"`, 2706 `spec "k8s.io/kube-openapi/pkg/validation/spec"`, 2707 } 2708 if !cmp.Equal(imports, expImports) { 2709 t.Errorf("wrong imports:\n%s", cmp.Diff(expImports, imports)) 2710 } 2711 2712 if formatted, err := format.Source(funcBuffer.Bytes()); err != nil { 2713 t.Fatalf("%v\n%v", err, string(funcBuffer.Bytes())) 2714 } else { 2715 formatted_expected, ree := format.Source([]byte(`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 2716 return common.OpenAPIDefinition{ 2717 Schema: spec.Schema{ 2718 SchemaProps: spec.SchemaProps{ 2719 Type: []string{"object"}, 2720 Properties: map[string]spec.Schema{ 2721 "OptionalField": { 2722 SchemaProps: spec.SchemaProps{ 2723 Default: "", 2724 Type: []string{"string"}, 2725 Format: "", 2726 }, 2727 }, 2728 "RequiredField": { 2729 SchemaProps: spec.SchemaProps{ 2730 Default: "", 2731 Type: []string{"string"}, 2732 Format: "", 2733 }, 2734 }, 2735 "requiredPointerField": { 2736 SchemaProps: spec.SchemaProps{ 2737 Type: []string{"string"}, 2738 Format: "", 2739 }, 2740 }, 2741 "optionalPointerField": { 2742 SchemaProps: spec.SchemaProps{ 2743 Type: []string{"string"}, 2744 Format: "", 2745 }, 2746 }, 2747 "ImplicitlyRequiredField": { 2748 SchemaProps: spec.SchemaProps{ 2749 Default: "", 2750 Type: []string{"string"}, 2751 Format: "", 2752 }, 2753 }, 2754 "implicitlyOptionalField": { 2755 SchemaProps: spec.SchemaProps{ 2756 Type: []string{"string"}, 2757 Format: "", 2758 }, 2759 }, 2760 }, 2761 Required: []string{"RequiredField", "requiredPointerField", "ImplicitlyRequiredField"}, 2762 }, 2763 }, 2764 } 2765 }`)) 2766 if ree != nil { 2767 t.Fatal(ree) 2768 } 2769 assertEqual(t, string(formatted_expected), string(formatted)) 2770 } 2771 }) 2772 2773 // Show specifying both is an error 2774 badFile := ` 2775 package foo 2776 2777 // +k8s:openapi-gen=true 2778 type Blah struct { 2779 // +optional 2780 // +required 2781 ConfusingField string 2782 }` 2783 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 2784 e := packagestest.Export(t, x, []packagestest.Module{{ 2785 Name: "example.com/base/foo", 2786 Files: map[string]interface{}{ 2787 "foo.go": badFile, 2788 }, 2789 }}) 2790 defer e.Cleanup() 2791 2792 callErr, funcErr, _, _, _ := testOpenAPITypeWriter(t, e.Config) 2793 if callErr != nil { 2794 t.Errorf("Unexpected callErr: %v", callErr) 2795 } 2796 if funcErr == nil { 2797 t.Fatalf("Expected funcErr") 2798 } 2799 if !strings.Contains(funcErr.Error(), "cannot be both optional and required") { 2800 t.Errorf("Unexpected error: %v", funcErr) 2801 } 2802 }) 2803 } 2804 2805 func TestMarkerCommentsCustomDefsV3(t *testing.T) { 2806 inputFile := ` 2807 package foo 2808 2809 import openapi "k8s.io/kube-openapi/pkg/common" 2810 2811 // +k8s:validation:maxProperties=10 2812 type Blah struct { 2813 } 2814 2815 func (_ Blah) OpenAPIV3Definition() openapi.OpenAPIDefinition { 2816 return openapi.OpenAPIDefinition{ 2817 Schema: spec.Schema{ 2818 SchemaProps: spec.SchemaProps{ 2819 Type: []string{"object"}, 2820 MaxProperties: ptr.To[int64](10), 2821 Format: "ipv4", 2822 }, 2823 }, 2824 } 2825 } 2826 2827 func (_ Blah) OpenAPISchemaType() []string { return []string{"object"} } 2828 func (_ Blah) OpenAPISchemaFormat() string { return "ipv4" }` 2829 commonFile := `package common 2830 2831 type OpenAPIDefinition struct {}` 2832 2833 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 2834 e := packagestest.Export(t, x, []packagestest.Module{{ 2835 Name: "example.com/base/foo", 2836 Files: map[string]interface{}{ 2837 "foo.go": inputFile, 2838 }, 2839 }, { 2840 Name: "k8s.io/kube-openapi/pkg/common", 2841 Files: map[string]interface{}{ 2842 "common.go": commonFile, 2843 }, 2844 }}) 2845 defer e.Cleanup() 2846 2847 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 2848 if callErr != nil { 2849 t.Fatal(callErr) 2850 } 2851 if funcErr != nil { 2852 t.Fatal(funcErr) 2853 } 2854 assertEqual(t, callBuffer.String(), 2855 `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) 2856 assertEqual(t, funcBuffer.String(), 2857 `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 2858 return common.EmbedOpenAPIDefinitionIntoV2Extension(foo.Blah{}.OpenAPIV3Definition(), common.OpenAPIDefinition{ 2859 Schema: spec.Schema{ 2860 SchemaProps: spec.SchemaProps{ 2861 Type:foo.Blah{}.OpenAPISchemaType(), 2862 Format:foo.Blah{}.OpenAPISchemaFormat(), 2863 MaxProperties: ptr.To[int64](10), 2864 }, 2865 }, 2866 }) 2867 }`) 2868 }) 2869 } 2870 2871 func TestMarkerCommentsV3OneOfTypes(t *testing.T) { 2872 inputFile := ` 2873 package foo 2874 2875 // +k8s:validation:maxLength=10 2876 type Blah struct { 2877 } 2878 2879 func (_ Blah) OpenAPISchemaType() []string { return []string{"string"} } 2880 func (_ Blah) OpenAPIV3OneOfTypes() []string { return []string{"string", "array"} } 2881 func (_ Blah) OpenAPISchemaFormat() string { return "ipv4" }` 2882 packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { 2883 e := packagestest.Export(t, x, []packagestest.Module{{ 2884 Name: "example.com/base/foo", 2885 Files: map[string]interface{}{ 2886 "foo.go": inputFile, 2887 }, 2888 }}) 2889 defer e.Cleanup() 2890 2891 callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) 2892 if callErr != nil { 2893 t.Fatal(callErr) 2894 } 2895 if funcErr != nil { 2896 t.Fatal(funcErr) 2897 } 2898 assertEqual(t, callBuffer.String(), 2899 `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) 2900 assertEqual(t, funcBuffer.String(), 2901 `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { 2902 return common.EmbedOpenAPIDefinitionIntoV2Extension(common.OpenAPIDefinition{ 2903 Schema: spec.Schema{ 2904 SchemaProps: spec.SchemaProps{ 2905 OneOf:common.GenerateOpenAPIV3OneOfSchema(foo.Blah{}.OpenAPIV3OneOfTypes()), 2906 Format:foo.Blah{}.OpenAPISchemaFormat(), 2907 MaxLength: ptr.To[int64](10), 2908 }, 2909 }, 2910 },common.OpenAPIDefinition{ 2911 Schema: spec.Schema{ 2912 SchemaProps: spec.SchemaProps{ 2913 Type:foo.Blah{}.OpenAPISchemaType(), 2914 Format:foo.Blah{}.OpenAPISchemaFormat(), 2915 MaxLength: ptr.To[int64](10), 2916 }, 2917 }, 2918 }) 2919 }`) 2920 }) 2921 }