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