/* Copyright 2016 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package generators import ( "bytes" "fmt" "go/format" "path" "strings" "testing" "github.com/google/go-cmp/cmp" "golang.org/x/tools/go/packages" "golang.org/x/tools/go/packages/packagestest" "k8s.io/gengo/v2/generator" "k8s.io/gengo/v2/namer" "k8s.io/gengo/v2/parser" "k8s.io/gengo/v2/types" ) func construct(t *testing.T, cfg *packages.Config, nameSystems namer.NameSystems, defaultSystem string, pkg string) *generator.Context { p := parser.New() if err := p.LoadPackagesWithConfigForTesting(cfg, pkg); err != nil { t.Fatalf("failed to load package: %v", err) } c, err := generator.NewContext(p, nameSystems, defaultSystem) if err != nil { t.Fatalf("failed to make a context: %v", err) } return c } func testOpenAPITypeWriter(t *testing.T, cfg *packages.Config) (error, error, *bytes.Buffer, *bytes.Buffer, []string) { pkgBase := "example.com/base" // `path` vs. `filepath` because packages use '/' inputPkg := path.Join(pkgBase, "foo") outputPkg := path.Join(pkgBase, "output") imports := generator.NewImportTrackerForPackage(outputPkg) rawNamer := namer.NewRawNamer(outputPkg, imports) namers := namer.NameSystems{ "raw": rawNamer, "private": &namer.NameStrategy{ Join: func(pre string, in []string, post string) string { return strings.Join(in, "_") }, PrependPackageNames: 4, // enough to fully qualify from k8s.io/api/... }, } context := construct(t, cfg, namers, "raw", inputPkg) universe := context.Universe blahT := universe.Type(types.Name{Package: inputPkg, Name: "Blah"}) callBuffer := &bytes.Buffer{} callSW := generator.NewSnippetWriter(callBuffer, context, "$", "$") callError := newOpenAPITypeWriter(callSW, context).generateCall(blahT) funcBuffer := &bytes.Buffer{} funcSW := generator.NewSnippetWriter(funcBuffer, context, "$", "$") funcError := newOpenAPITypeWriter(funcSW, context).generate(blahT) return callError, funcError, callBuffer, funcBuffer, imports.ImportLines() } // NOTE: the usual order of arguments for an assertion would be want, got, but // this helper function flips that in favor of callsite readability. func assertEqual(t *testing.T, got, want string) { t.Helper() want = strings.TrimSpace(want) got = strings.TrimSpace(got) if !cmp.Equal(want, got) { t.Errorf("Wrong result:\n%s", cmp.Diff(want, got)) } } func TestSimple(t *testing.T) { inputFile := ` package foo // Blah is a test. // +k8s:openapi-gen=true // +k8s:openapi-gen=x-kubernetes-type-tag:type_test type Blah struct { // A simple string String string // A simple int Int int ` + "`" + `json:",omitempty"` + "`" + ` // An int considered string simple int IntString int ` + "`" + `json:",string"` + "`" + ` // A simple int64 Int64 int64 // A simple int32 Int32 int32 // A simple int16 Int16 int16 // A simple int8 Int8 int8 // A simple int Uint uint // A simple int64 Uint64 uint64 // A simple int32 Uint32 uint32 // A simple int16 Uint16 uint16 // A simple int8 Uint8 uint8 // A simple byte Byte byte // A simple boolean Bool bool // A simple float64 Float64 float64 // A simple float32 Float32 float32 // a base64 encoded characters ByteArray []byte // a member with an extension // +k8s:openapi-gen=x-kubernetes-member-tag:member_test WithExtension string // a member with struct tag as extension // +patchStrategy=merge // +patchMergeKey=pmk WithStructTagExtension string ` + "`" + `patchStrategy:"merge" patchMergeKey:"pmk"` + "`" + ` // a member with a list type // +listType=atomic // +default=["foo", "bar"] WithListType []string // a member with a map type // +listType=atomic // +default={"foo": "bar", "fizz": "buzz"} Map map[string]string // a member with a string pointer // +default="foo" StringPointer *string // an int member with a default // +default=1 OmittedInt int ` + "`" + `json:"omitted,omitempty"` + "`" + ` // a field with an invalid escape sequence in comment // ex) regexp:^.*\.yaml$ InvalidEscapeSequenceInComment string }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) assertEqual(t, funcBuffer.String(), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "Blah is a test.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "String": { SchemaProps: spec.SchemaProps{ Description: "A simple string", Default: "", Type: []string{"string"}, Format: "", }, }, "Int64": { SchemaProps: spec.SchemaProps{ Description: "A simple int64", Default: 0, Type: []string{"integer"}, Format: "int64", }, }, "Int32": { SchemaProps: spec.SchemaProps{ Description: "A simple int32", Default: 0, Type: []string{"integer"}, Format: "int32", }, }, "Int16": { SchemaProps: spec.SchemaProps{ Description: "A simple int16", Default: 0, Type: []string{"integer"}, Format: "int32", }, }, "Int8": { SchemaProps: spec.SchemaProps{ Description: "A simple int8", Default: 0, Type: []string{"integer"}, Format: "byte", }, }, "Uint": { SchemaProps: spec.SchemaProps{ Description: "A simple int", Default: 0, Type: []string{"integer"}, Format: "int32", }, }, "Uint64": { SchemaProps: spec.SchemaProps{ Description: "A simple int64", Default: 0, Type: []string{"integer"}, Format: "int64", }, }, "Uint32": { SchemaProps: spec.SchemaProps{ Description: "A simple int32", Default: 0, Type: []string{"integer"}, Format: "int64", }, }, "Uint16": { SchemaProps: spec.SchemaProps{ Description: "A simple int16", Default: 0, Type: []string{"integer"}, Format: "int32", }, }, "Uint8": { SchemaProps: spec.SchemaProps{ Description: "A simple int8", Default: 0, Type: []string{"integer"}, Format: "byte", }, }, "Byte": { SchemaProps: spec.SchemaProps{ Description: "A simple byte", Default: 0, Type: []string{"integer"}, Format: "byte", }, }, "Bool": { SchemaProps: spec.SchemaProps{ Description: "A simple boolean", Default: false, Type: []string{"boolean"}, Format: "", }, }, "Float64": { SchemaProps: spec.SchemaProps{ Description: "A simple float64", Default: 0, Type: []string{"number"}, Format: "double", }, }, "Float32": { SchemaProps: spec.SchemaProps{ Description: "A simple float32", Default: 0, Type: []string{"number"}, Format: "float", }, }, "ByteArray": { SchemaProps: spec.SchemaProps{ Description: "a base64 encoded characters", Type: []string{"string"}, Format: "byte", }, }, "WithExtension": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-member-tag": "member_test", }, }, SchemaProps: spec.SchemaProps{ Description: "a member with an extension", Default: "", Type: []string{"string"}, Format: "", }, }, "WithStructTagExtension": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-patch-merge-key": "pmk", "x-kubernetes-patch-strategy": "merge", }, }, SchemaProps: spec.SchemaProps{ Description: "a member with struct tag as extension", Default: "", Type: []string{"string"}, Format: "", }, }, "WithListType": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-list-type": "atomic", }, }, SchemaProps: spec.SchemaProps{ Description: "a member with a list type", Default: []interface {}{"foo", "bar"}, Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: "", Type: []string{"string"}, Format: "", }, }, }, }, }, "Map": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-list-type": "atomic", }, }, SchemaProps: spec.SchemaProps{ Description: "a member with a map type", Default: map[string]interface {}{"fizz":"buzz", "foo":"bar"}, Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{ Allows: true, Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: "", Type: []string{"string"}, Format: "", }, }, }, }, }, "StringPointer": { SchemaProps: spec.SchemaProps{ Description: "a member with a string pointer", Default: "foo", Type: []string{"string"}, Format: "", }, }, "omitted": { SchemaProps: spec.SchemaProps{ Description: "an int member with a default", Default: 1, Type: []string{"integer"}, Format: "int32", }, }, "InvalidEscapeSequenceInComment": { SchemaProps: spec.SchemaProps{ Description: "a field with an invalid escape sequence in comment ex) regexp:^.*\\.yaml$", Default: "", Type: []string{"string"}, Format: "", }, }, }, Required: []string{"String","Int64","Int32","Int16","Int8","Uint","Uint64","Uint32","Uint16","Uint8","Byte","Bool","Float64","Float32","ByteArray","WithExtension","WithStructTagExtension","WithListType","Map","StringPointer","InvalidEscapeSequenceInComment"}, }, VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-type-tag": "type_test", }, }, }, } }`) }) } func TestEmptyProperties(t *testing.T) { inputFile := ` package foo // Blah demonstrate a struct without fields. type Blah struct { }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) assertEqual(t, funcBuffer.String(), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "Blah demonstrate a struct without fields.", Type: []string{"object"}, }, }, } }`) }) } func TestNestedStruct(t *testing.T) { inputFile := ` package foo // Nested is used as struct field type Nested struct { // A simple string String string } // Blah demonstrate a struct with struct field. type Blah struct { // A struct field Field Nested }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) assertEqual(t, funcBuffer.String(), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "Blah demonstrate a struct with struct field.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "Field": { SchemaProps: spec.SchemaProps{ Description: "A struct field", Default: map[string]interface {}{}, Ref: ref("example.com/base/foo.Nested"), }, }, }, Required: []string{"Field"}, }, }, Dependencies: []string{ "example.com/base/foo.Nested",}, } }`) }) } func TestNestedStructPointer(t *testing.T) { inputFile := ` package foo // Nested is used as struct pointer field type Nested struct { // A simple string String string } // Blah demonstrate a struct with struct pointer field. type Blah struct { // A struct pointer field Field *Nested }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) assertEqual(t, funcBuffer.String(), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "Blah demonstrate a struct with struct pointer field.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "Field": { SchemaProps: spec.SchemaProps{ Description: "A struct pointer field", Ref: ref("example.com/base/foo.Nested"), }, }, }, Required: []string{"Field"}, }, }, Dependencies: []string{ "example.com/base/foo.Nested",}, } }`) }) } func TestEmbeddedStruct(t *testing.T) { inputFile := ` package foo // Nested is used as embedded struct field type Nested struct { // A simple string String string } // Blah demonstrate a struct with embedded struct field. type Blah struct { // An embedded struct field Nested }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) assertEqual(t, funcBuffer.String(), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "Blah demonstrate a struct with embedded struct field.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "Nested": { SchemaProps: spec.SchemaProps{ Description: "An embedded struct field", Default: map[string]interface {}{}, Ref: ref("example.com/base/foo.Nested"), }, }, }, Required: []string{"Nested"}, }, }, Dependencies: []string{ "example.com/base/foo.Nested",}, } }`) }) } func TestSingleEmbeddedStruct(t *testing.T) { inputFile := ` package foo import "time" // Nested is used as embedded struct field type Nested struct { // A simple string time.Duration } // Blah demonstrate a struct with embedded struct field. type Blah struct { // An embedded struct field // +default="10ms" Nested ` + "`" + `json:"nested,omitempty" protobuf:"bytes,5,opt,name=nested"` + "`" + ` }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) assertEqual(t, funcBuffer.String(), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "Blah demonstrate a struct with embedded struct field.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "nested": { SchemaProps: spec.SchemaProps{ Description: "An embedded struct field", Default: "10ms", Ref: ref("example.com/base/foo.Nested"), }, }, }, }, }, Dependencies: []string{ "example.com/base/foo.Nested",}, } }`) }) } func TestEmbeddedInlineStruct(t *testing.T) { inputFile := ` package foo // Nested is used as embedded inline struct field type Nested struct { // A simple string String string } // Blah demonstrate a struct with embedded inline struct field. type Blah struct { // An embedded inline struct field Nested ` + "`" + `json:",inline,omitempty"` + "`" + ` }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) assertEqual(t, funcBuffer.String(), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "Blah demonstrate a struct with embedded inline struct field.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "String": { SchemaProps: spec.SchemaProps{ Description: "A simple string", Default: "", Type: []string{"string"}, Format: "", }, }, }, Required: []string{"String"}, }, }, } }`) }) } func TestEmbeddedInlineStructPointer(t *testing.T) { inputFile := ` package foo // Nested is used as embedded inline struct pointer field. type Nested struct { // A simple string String string } // Blah demonstrate a struct with embedded inline struct pointer field. type Blah struct { // An embedded inline struct pointer field *Nested ` + "`" + `json:",inline,omitempty"` + "`" + ` }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) assertEqual(t, funcBuffer.String(), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "Blah demonstrate a struct with embedded inline struct pointer field.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "String": { SchemaProps: spec.SchemaProps{ Description: "A simple string", Default: "", Type: []string{"string"}, Format: "", }, }, }, Required: []string{"String"}, }, }, } }`) }) } func TestNestedMapString(t *testing.T) { inputFile := ` package foo // Map sample tests openAPIGen.generateMapProperty method. type Blah struct { // A sample String to String map StringToArray map[string]map[string]string }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) assertEqual(t, funcBuffer.String(), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "Map sample tests openAPIGen.generateMapProperty method.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "StringToArray": { SchemaProps: spec.SchemaProps{ Description: "A sample String to String map", Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{ Allows: true, Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{ Allows: true, Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: "", Type: []string{"string"}, Format: "", }, }, }, }, }, }, }, }, }, Required: []string{"StringToArray"}, }, }, } }`) }) } func TestNestedMapInt(t *testing.T) { inputFile := ` package foo // Map sample tests openAPIGen.generateMapProperty method. type Blah struct { // A sample String to String map StringToArray map[string]map[string]int }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) assertEqual(t, funcBuffer.String(), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "Map sample tests openAPIGen.generateMapProperty method.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "StringToArray": { SchemaProps: spec.SchemaProps{ Description: "A sample String to String map", Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{ Allows: true, Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{ Allows: true, Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: 0, Type: []string{"integer"}, Format: "int32", }, }, }, }, }, }, }, }, }, Required: []string{"StringToArray"}, }, }, } }`) }) } func TestNestedMapBoolean(t *testing.T) { inputFile := ` package foo // Map sample tests openAPIGen.generateMapProperty method. type Blah struct { // A sample String to String map StringToArray map[string]map[string]bool }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) assertEqual(t, funcBuffer.String(), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "Map sample tests openAPIGen.generateMapProperty method.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "StringToArray": { SchemaProps: spec.SchemaProps{ Description: "A sample String to String map", Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{ Allows: true, Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{ Allows: true, Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: false, Type: []string{"boolean"}, Format: "", }, }, }, }, }, }, }, }, }, Required: []string{"StringToArray"}, }, }, } }`) }) } func TestFailingSample1(t *testing.T) { inputFile := ` package foo // Map sample tests openAPIGen.generateMapProperty method. type Blah struct { // A sample String to String map StringToArray map[string]map[string]map[int]string }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() _, funcErr, _, _, _ := testOpenAPITypeWriter(t, e.Config) if funcErr == nil { t.Fatalf("An error was expected") } assertEqual(t, "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", funcErr.Error()) }) } func TestFailingSample2(t *testing.T) { inputFile := ` package foo // Map sample tests openAPIGen.generateMapProperty method. type Blah struct { // A sample String to String map StringToArray map[int]string }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() _, funcErr, _, _, _ := testOpenAPITypeWriter(t, e.Config) if funcErr == nil { t.Fatalf("An error was expected") } assertEqual(t, "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", funcErr.Error()) }) } func TestFailingDefaultEnforced(t *testing.T) { tests := []struct { definition string expectedError string }{{ definition: ` package foo type Blah struct { // +default=5 Int int }`, 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", }, { definition: ` package foo type Blah struct { // +default={"foo": 5} Struct struct{ foo int } }`, 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: {}`, }, { definition: ` package foo type Blah struct { List []Item } // +default="foo" type Item string`, 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: ""`, }, { definition: ` package foo type Blah struct { Map map[string]Item } // +default="foo" type Item string`, 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: ""`, }} for i, test := range tests { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": test.definition, }, }}) defer e.Cleanup() _, funcErr, _, _, _ := testOpenAPITypeWriter(t, e.Config) if funcErr == nil { t.Fatalf("An error was expected") } assertEqual(t, test.expectedError, funcErr.Error()) }) }) } } func TestCustomDef(t *testing.T) { inputFile := ` package foo import openapi "k8s.io/kube-openapi/pkg/common" type Blah struct { } func (_ Blah) OpenAPIDefinition() openapi.OpenAPIDefinition { return openapi.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"string"}, Format: "date-time", }, }, } }` commonFile := `package common type OpenAPIDefinition struct {}` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }, { Name: "k8s.io/kube-openapi/pkg/common", Files: map[string]interface{}{ "common.go": commonFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": foo.Blah{}.OpenAPIDefinition(),`) assertEqual(t, "", funcBuffer.String()) }) } func TestCustomDefV3(t *testing.T) { inputFile := ` package foo import openapi "k8s.io/kube-openapi/pkg/common" type Blah struct { } func (_ Blah) OpenAPIV3Definition() openapi.OpenAPIDefinition { return openapi.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"string"}, Format: "date-time", }, }, } }` commonFile := `package common type OpenAPIDefinition struct {}` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }, { Name: "k8s.io/kube-openapi/pkg/common", Files: map[string]interface{}{ "common.go": commonFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": foo.Blah{}.OpenAPIV3Definition(),`) assertEqual(t, "", funcBuffer.String()) }) } func TestCustomDefV2AndV3(t *testing.T) { inputFile := ` package foo import openapi "k8s.io/kube-openapi/pkg/common" type Blah struct { } func (_ Blah) OpenAPIV3Definition() openapi.OpenAPIDefinition { return openapi.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"string"}, Format: "date-time", }, }, } } func (_ Blah) OpenAPIDefinition() openapi.OpenAPIDefinition { return openapi.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"string"}, Format: "date-time", }, }, } }` commonFile := `package common type OpenAPIDefinition struct {}` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }, { Name: "k8s.io/kube-openapi/pkg/common", Files: map[string]interface{}{ "common.go": commonFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": common.EmbedOpenAPIDefinitionIntoV2Extension(foo.Blah{}.OpenAPIV3Definition(), foo.Blah{}.OpenAPIDefinition()),`) assertEqual(t, "", funcBuffer.String()) }) } func TestCustomDefs(t *testing.T) { inputFile := ` package foo // Blah is a custom type type Blah struct { } func (_ Blah) OpenAPISchemaType() []string { return []string{"string"} } func (_ Blah) OpenAPISchemaFormat() string { return "date-time" }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) assertEqual(t, funcBuffer.String(), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "Blah is a custom type", Type:foo.Blah{}.OpenAPISchemaType(), Format:foo.Blah{}.OpenAPISchemaFormat(), }, }, } }`) }) } func TestCustomDefsV3(t *testing.T) { inputFile := ` package foo import openapi "k8s.io/kube-openapi/pkg/common" // Blah is a custom type type Blah struct { } func (_ Blah) OpenAPIV3Definition() openapi.OpenAPIDefinition { return openapi.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"string"}, Format: "date-time", }, }, } } func (_ Blah) OpenAPISchemaType() []string { return []string{"string"} } func (_ Blah) OpenAPISchemaFormat() string { return "date-time" }` commonFile := `package common type OpenAPIDefinition struct {}` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }, { Name: "k8s.io/kube-openapi/pkg/common", Files: map[string]interface{}{ "common.go": commonFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) assertEqual(t, funcBuffer.String(), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.EmbedOpenAPIDefinitionIntoV2Extension(foo.Blah{}.OpenAPIV3Definition(), common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "Blah is a custom type", Type:foo.Blah{}.OpenAPISchemaType(), Format:foo.Blah{}.OpenAPISchemaFormat(), }, }, }) }`) }) } func TestV3OneOfTypes(t *testing.T) { inputFile := ` package foo // Blah is a custom type type Blah struct { } func (_ Blah) OpenAPISchemaType() []string { return []string{"string"} } func (_ Blah) OpenAPISchemaFormat() string { return "date-time" } func (_ Blah) OpenAPIV3OneOfTypes() []string { return []string{"string", "number"} }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) assertEqual(t, funcBuffer.String(), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.EmbedOpenAPIDefinitionIntoV2Extension(common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "Blah is a custom type", OneOf:common.GenerateOpenAPIV3OneOfSchema(foo.Blah{}.OpenAPIV3OneOfTypes()), Format:foo.Blah{}.OpenAPISchemaFormat(), }, }, },common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "Blah is a custom type", Type:foo.Blah{}.OpenAPISchemaType(), Format:foo.Blah{}.OpenAPISchemaFormat(), }, }, }) }`) }) } func TestPointer(t *testing.T) { inputFile := ` package foo // PointerSample demonstrate pointer's properties type Blah struct { // A string pointer StringPointer *string // A struct pointer StructPointer *Blah // A slice pointer SlicePointer *[]string // A map pointer MapPointer *map[string]string }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) assertEqual(t, funcBuffer.String(), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "PointerSample demonstrate pointer's properties", Type: []string{"object"}, Properties: map[string]spec.Schema{ "StringPointer": { SchemaProps: spec.SchemaProps{ Description: "A string pointer", Type: []string{"string"}, Format: "", }, }, "StructPointer": { SchemaProps: spec.SchemaProps{ Description: "A struct pointer", Ref: ref("example.com/base/foo.Blah"), }, }, "SlicePointer": { SchemaProps: spec.SchemaProps{ Description: "A slice pointer", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: "", Type: []string{"string"}, Format: "", }, }, }, }, }, "MapPointer": { SchemaProps: spec.SchemaProps{ Description: "A map pointer", Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{ Allows: true, Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: "", Type: []string{"string"}, Format: "", }, }, }, }, }, }, Required: []string{"StringPointer","StructPointer","SlicePointer","MapPointer"}, }, }, Dependencies: []string{ "example.com/base/foo.Blah",}, } }`) }) } func TestNestedLists(t *testing.T) { inputFile := ` package foo // Blah is a test. // +k8s:openapi-gen=true // +k8s:openapi-gen=x-kubernetes-type-tag:type_test type Blah struct { // Nested list NestedList [][]int64 }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) assertEqual(t, funcBuffer.String(), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "Blah is a test.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "NestedList": { SchemaProps: spec.SchemaProps{ Description: "Nested list", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: 0, Type: []string{"integer"}, Format: "int64", }, }, }, }, }, }, }, }, }, Required: []string{"NestedList"}, }, VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-type-tag": "type_test", }, }, }, } }`) }) } func TestNestListOfMaps(t *testing.T) { inputFile := ` package foo // Blah is a test. // +k8s:openapi-gen=true // +k8s:openapi-gen=x-kubernetes-type-tag:type_test type Blah struct { // Nested list of maps NestedListOfMaps [][]map[string]string }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) assertEqual(t, funcBuffer.String(), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "Blah is a test.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "NestedListOfMaps": { SchemaProps: spec.SchemaProps{ Description: "Nested list of maps", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{ Allows: true, Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: "", Type: []string{"string"}, Format: "", }, }, }, }, }, }, }, }, }, }, }, }, Required: []string{"NestedListOfMaps"}, }, VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-type-tag": "type_test", }, }, }, } }`) }) } func TestExtensions(t *testing.T) { inputFile := ` package foo // Blah is a test. // +k8s:openapi-gen=true // +k8s:openapi-gen=x-kubernetes-type-tag:type_test type Blah struct { // a member with a list type with two map keys // +listType=map // +listMapKey=port // +listMapKey=protocol WithListField []string // another member with a list type with one map key // +listType=map // +listMapKey=port WithListField2 []string }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) assertEqual(t, funcBuffer.String(), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "Blah is a test.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "WithListField": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-list-map-keys": []interface{}{ "port", "protocol", }, "x-kubernetes-list-type": "map", }, }, SchemaProps: spec.SchemaProps{ Description: "a member with a list type with two map keys", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: "", Type: []string{"string"}, Format: "", }, }, }, }, }, "WithListField2": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-list-map-keys": []interface{}{ "port", }, "x-kubernetes-list-type": "map", }, }, SchemaProps: spec.SchemaProps{ Description: "another member with a list type with one map key", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: "", Type: []string{"string"}, Format: "", }, }, }, }, }, }, Required: []string{"WithListField","WithListField2"}, }, VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-type-tag": "type_test", }, }, }, } }`) }) } func TestUnion(t *testing.T) { inputFile := ` package foo // Blah is a test. // +k8s:openapi-gen=true // +k8s:openapi-gen=x-kubernetes-type-tag:type_test // +union type Blah struct { // +unionDiscriminator Discriminator *string ` + "`" + `json:"discriminator"` + "`" + ` // +optional Numeric int ` + "`" + `json:"numeric"` + "`" + ` // +optional String string ` + "`" + `json:"string"` + "`" + ` // +optional Float float64 ` + "`" + `json:"float"` + "`" + ` }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) assertEqual(t, funcBuffer.String(), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "Blah is a test.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "discriminator": { SchemaProps: spec.SchemaProps{ Type: []string{"string"}, Format: "", }, }, "numeric": { SchemaProps: spec.SchemaProps{ Default: 0, Type: []string{"integer"}, Format: "int32", }, }, "string": { SchemaProps: spec.SchemaProps{ Default: "", Type: []string{"string"}, Format: "", }, }, "float": { SchemaProps: spec.SchemaProps{ Default: 0, Type: []string{"number"}, Format: "double", }, }, }, Required: []string{"discriminator"}, }, VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-type-tag": "type_test", "x-kubernetes-unions": []interface{}{ map[string]interface{}{ "discriminator": "discriminator", "fields-to-discriminateBy": map[string]interface{}{ "float": "Float", "numeric": "Numeric", "string": "String", }, }, }, }, }, }, } }`) }) } func TestEnumAlias(t *testing.T) { inputFile := ` package foo import "example.com/base/bar" // EnumType is the enumType. // +enum type EnumType = bar.EnumType // EnumA is a. const EnumA EnumType = bar.EnumA // EnumB is b. const EnumB EnumType = bar.EnumB // Blah is a test. // +k8s:openapi-gen=true type Blah struct { // Value is the value. Value EnumType }` otherFile := ` package bar // EnumType is the enumType. // +enum type EnumType string // EnumA is a. const EnumA EnumType = "a" // EnumB is b. const EnumB EnumType = "b"` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }, { Name: "example.com/base/bar", Files: map[string]interface{}{ "bar.go": otherFile, }, }}) defer e.Cleanup() callErr, funcErr, _, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, funcBuffer.String(), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "Blah is a test.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "Value": { SchemaProps: spec.SchemaProps{`+"\n"+ "Description: \"Value is the value.\\n\\nPossible enum values:\\n - `\\\"a\\\"` is a.\\n - `\\\"b\\\"` is b.\","+` Default: "", Type: []string{"string"}, Format: "", Enum: []interface{}{"a", "b"}, }, }, }, Required: []string{"Value"}, }, }, } }`) }) } func TestEnum(t *testing.T) { inputFile := ` package foo // EnumType is the enumType. // +enum type EnumType string // EnumA is a. const EnumA EnumType = "a" // EnumB is b. const EnumB EnumType = "b" // Blah is a test. // +k8s:openapi-gen=true // +k8s:openapi-gen=x-kubernetes-type-tag:type_test type Blah struct { // Value is the value. Value EnumType NoCommentEnum EnumType // +optional OptionalEnum *EnumType List []EnumType Map map[string]EnumType }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, _, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, funcBuffer.String(), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "Blah is a test.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "Value": { SchemaProps: spec.SchemaProps{`+"\n"+ "Description: \"Value is the value.\\n\\nPossible enum values:\\n - `\\\"a\\\"` is a.\\n - `\\\"b\\\"` is b.\","+` Default: "", Type: []string{"string"}, Format: "", Enum: []interface{}{"a", "b"}, }, }, "NoCommentEnum": { SchemaProps: spec.SchemaProps{`+"\n"+ "Description: \"Possible enum values:\\n - `\\\"a\\\"` is a.\\n - `\\\"b\\\"` is b.\","+` Default: "", Type: []string{"string"}, Format: "", Enum: []interface{}{"a", "b"}, }, }, "OptionalEnum": { SchemaProps: spec.SchemaProps{`+"\n"+ "Description: \"Possible enum values:\\n - `\\\"a\\\"` is a.\\n - `\\\"b\\\"` is b.\","+` Type: []string{"string"}, Format: "", Enum: []interface{}{"a", "b"}, }, }, "List": { SchemaProps: spec.SchemaProps{ Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: "", Type: []string{"string"}, Format: "", Enum: []interface{}{"a", "b"}, }, }, }, }, }, "Map": { SchemaProps: spec.SchemaProps{ Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{ Allows: true, Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: "", Type: []string{"string"}, Format: "", Enum: []interface{}{"a", "b"}, }, }, }, }, }, }, Required: []string{"Value","NoCommentEnum","List","Map"}, }, VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-type-tag": "type_test", }, }, }, } }`) }) } func TestSymbolReference(t *testing.T) { inputFile := ` package foo // +k8s:openapi-gen=true type Blah struct { // +default="A Default Value" // +optional Value *string // User constant local to the output package fully qualified // +default=ref(example.com/base/output.MyConst) // +optional FullyQualifiedOutputValue *string // Local to types but not to output // +default=ref(MyConst) // +optional LocalValue *string // +default=ref(example.com/base/foo.MyConst) // +optional FullyQualifiedLocalValue *string // +default=ref(k8s.io/api/v1.TerminationPathDefault) // +optional FullyQualifiedExternalValue *string }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, _, funcBuffer, imports := testOpenAPITypeWriter(t, e.Config) if funcErr != nil { t.Fatalf("Unexpected funcErr: %v", funcErr) } if callErr != nil { t.Fatalf("Unexpected callErr: %v", callErr) } expImports := []string{ `foo "example.com/base/foo"`, `v1 "k8s.io/api/v1"`, `common "k8s.io/kube-openapi/pkg/common"`, `spec "k8s.io/kube-openapi/pkg/validation/spec"`, } if !cmp.Equal(imports, expImports) { t.Errorf("wrong imports:\n%s", cmp.Diff(expImports, imports)) } if formatted, err := format.Source(funcBuffer.Bytes()); err != nil { t.Fatal(err) } else { assertEqual(t, string(formatted), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ "Value": { SchemaProps: spec.SchemaProps{ Default: "A Default Value", Type: []string{"string"}, Format: "", }, }, "FullyQualifiedOutputValue": { SchemaProps: spec.SchemaProps{ Description: "User constant local to the output package fully qualified", Default: MyConst, Type: []string{"string"}, Format: "", }, }, "LocalValue": { SchemaProps: spec.SchemaProps{ Description: "Local to types but not to output", Default: foo.MyConst, Type: []string{"string"}, Format: "", }, }, "FullyQualifiedLocalValue": { SchemaProps: spec.SchemaProps{ Default: foo.MyConst, Type: []string{"string"}, Format: "", }, }, "FullyQualifiedExternalValue": { SchemaProps: spec.SchemaProps{ Default: v1.TerminationPathDefault, Type: []string{"string"}, Format: "", }, }, }, }, }, } }`) } }) } // Show that types with unmarshalJSON in their hierarchy do not have struct // defaults enforced, and that aliases and embededd types are respected func TestMustEnforceDefaultStruct(t *testing.T) { inputFile := ` package foo type Time struct { value interface{} } type TimeWithoutUnmarshal struct { value interface{} } func (_ TimeWithoutUnmarshal) OpenAPISchemaType() []string { return []string{"string"} } func (_ TimeWithoutUnmarshal) OpenAPISchemaFormat() string { return "date-time" } func (_ Time) UnmarshalJSON([]byte) error { return nil } func (_ Time) OpenAPISchemaType() []string { return []string{"string"} } func (_ Time) OpenAPISchemaFormat() string { return "date-time" } // Time with UnmarshalJSON defined on pointer instead of struct type MicroTime struct { value interface{} } func (t *MicroTime) UnmarshalJSON([]byte) error { return nil } func (_ MicroTime) OpenAPISchemaType() []string { return []string{"string"} } func (_ MicroTime) OpenAPISchemaFormat() string { return "date-time" } type Int64 int64 type Duration struct { Int64 } func (_ Duration) OpenAPISchemaType() []string { return []string{"string"} } func (_ Duration) OpenAPISchemaFormat() string { return "" } type NothingSpecial struct { Field string } // +k8s:openapi-gen=true type Blah struct { Embedded Duration PointerUnmarshal MicroTime StructUnmarshal Time NoUnmarshal TimeWithoutUnmarshal Regular NothingSpecial }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, _, funcBuffer, imports := testOpenAPITypeWriter(t, e.Config) if funcErr != nil { t.Fatalf("Unexpected funcErr: %v", funcErr) } if callErr != nil { t.Fatalf("Unexpected callErr: %v", callErr) } expImports := []string{ `foo "example.com/base/foo"`, `common "k8s.io/kube-openapi/pkg/common"`, `spec "k8s.io/kube-openapi/pkg/validation/spec"`, } if !cmp.Equal(imports, expImports) { t.Errorf("wrong imports:\n%s", cmp.Diff(expImports, imports)) } if formatted, err := format.Source(funcBuffer.Bytes()); err != nil { t.Fatal(err) } else { assertEqual(t, string(formatted), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ "Embedded": { SchemaProps: spec.SchemaProps{ Default: 0, Ref: ref("example.com/base/foo.Duration"), }, }, "PointerUnmarshal": { SchemaProps: spec.SchemaProps{ Ref: ref("example.com/base/foo.MicroTime"), }, }, "StructUnmarshal": { SchemaProps: spec.SchemaProps{ Ref: ref("example.com/base/foo.Time"), }, }, "NoUnmarshal": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, Ref: ref("example.com/base/foo.TimeWithoutUnmarshal"), }, }, "Regular": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, Ref: ref("example.com/base/foo.NothingSpecial"), }, }, }, Required: []string{"Embedded", "PointerUnmarshal", "StructUnmarshal", "NoUnmarshal", "Regular"}, }, }, Dependencies: []string{ "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"}, } }`) } }) } func TestMarkerComments(t *testing.T) { inputFile := ` package foo // +k8s:openapi-gen=true // +k8s:validation:maxProperties=10 // +k8s:validation:minProperties=1 // +k8s:validation:exclusiveMinimum // +k8s:validation:exclusiveMaximum type Blah struct { // Integer with min and max values // +k8s:validation:minimum=0 // +k8s:validation:maximum=10 // +k8s:validation:exclusiveMinimum // +k8s:validation:exclusiveMaximum IntValue int // String with min and max lengths // +k8s:validation:minLength=1 // +k8s:validation:maxLength=10 // +k8s:validation:pattern="^foo$[0-9]+" StringValue string // +k8s:validation:maxitems=10 // +k8s:validation:minItems=1 // +k8s:validation:uniqueItems ArrayValue []string // +k8s:validation:maxProperties=10 // +k8s:validation:minProperties=1 ObjValue map[string]interface{} }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, _, funcBuffer, imports := testOpenAPITypeWriter(t, e.Config) if funcErr != nil { t.Fatalf("Unexpected funcErr: %v", funcErr) } if callErr != nil { t.Fatalf("Unexpected callErr: %v", callErr) } expImports := []string{ `foo "example.com/base/foo"`, `common "k8s.io/kube-openapi/pkg/common"`, `spec "k8s.io/kube-openapi/pkg/validation/spec"`, `ptr "k8s.io/utils/ptr"`, } if !cmp.Equal(imports, expImports) { t.Errorf("wrong imports:\n%s", cmp.Diff(expImports, imports)) } if formatted, err := format.Source(funcBuffer.Bytes()); err != nil { t.Fatalf("%v\n%v", err, string(funcBuffer.Bytes())) } else { formatted_expected, ree := format.Source([]byte(`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"object"}, ExclusiveMinimum: true, ExclusiveMaximum: true, MinProperties: ptr.To[int64](1), MaxProperties: ptr.To[int64](10), Properties: map[string]spec.Schema{ "IntValue": { SchemaProps: spec.SchemaProps{ Description: "Integer with min and max values", Default: 0, Minimum: ptr.To[float64](0), Maximum: ptr.To[float64](10), ExclusiveMinimum: true, ExclusiveMaximum: true, Type: []string{"integer"}, Format: "int32", }, }, "StringValue": { SchemaProps: spec.SchemaProps{ Description: "String with min and max lengths", Default: "", MinLength: ptr.To[int64](1), MaxLength: ptr.To[int64](10), Pattern: "^foo$[0-9]+", Type: []string{"string"}, Format: "", }, }, "ArrayValue": { SchemaProps: spec.SchemaProps{ MinItems: ptr.To[int64](1), MaxItems: ptr.To[int64](10), UniqueItems: true, Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: "", Type: []string{"string"}, Format: "", }, }, }, }, }, "ObjValue": { SchemaProps: spec.SchemaProps{ MinProperties: ptr.To[int64](1), MaxProperties: ptr.To[int64](10), Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{ Allows: true, Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Format: "", }, }, }, }, }, }, Required: []string{"IntValue", "StringValue", "ArrayValue", "ObjValue"}, }, }, } }`)) if ree != nil { t.Fatal(ree) } assertEqual(t, string(formatted), string(formatted_expected)) } }) } func TestCELMarkerComments(t *testing.T) { inputFile := ` package foo // +k8s:openapi-gen=true // +k8s:validation:cel[0]:rule="self == oldSelf" // +k8s:validation:cel[0]:message="message1" type Blah struct { // +k8s:validation:cel[0]:rule="self.length() > 0" // +k8s:validation:cel[0]:message="string message" // +k8s:validation:cel[1]:rule="self.length() % 2 == 0" // +k8s:validation:cel[1]:messageExpression="self + ' hello'" // +k8s:validation:cel[1]:optionalOldSelf // +optional Field string }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, _, funcBuffer, imports := testOpenAPITypeWriter(t, e.Config) if funcErr != nil { t.Fatalf("Unexpected funcErr: %v", funcErr) } if callErr != nil { t.Fatalf("Unexpected callErr: %v", callErr) } expImports := []string{ `foo "example.com/base/foo"`, `common "k8s.io/kube-openapi/pkg/common"`, `spec "k8s.io/kube-openapi/pkg/validation/spec"`, } if !cmp.Equal(imports, expImports) { t.Errorf("wrong imports:\n%s", cmp.Diff(expImports, imports)) } if formatted, err := format.Source(funcBuffer.Bytes()); err != nil { t.Fatalf("%v\n%v", err, string(funcBuffer.Bytes())) } else { formatted_expected, ree := format.Source([]byte(`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ "Field": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "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"}}, }, }, SchemaProps: spec.SchemaProps{ Default: "", Type: []string{"string"}, Format: "", }, }, }, }, VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-validations": []interface{}{map[string]interface{}{"message": "message1", "rule": "self == oldSelf"}}, }, }, }, } }`)) if ree != nil { t.Fatal(ree) } assertEqual(t, string(formatted_expected), string(formatted)) } }) } func TestMultilineCELMarkerComments(t *testing.T) { inputFile := ` package foo // +k8s:openapi-gen=true // +k8s:validation:cel[0]:rule="self == oldSelf" // +k8s:validation:cel[0]:message="message1" // +k8s:validation:cel[0]:fieldPath="field" type Blah struct { // +k8s:validation:cel[0]:rule="self.length() > 0" // +k8s:validation:cel[0]:message="string message" // +k8s:validation:cel[0]:reason="Invalid" // +k8s:validation:cel[1]:rule> !oldSelf.hasValue() || self.length() % 2 == 0 // +k8s:validation:cel[1]:rule> ? self.field == "even" // +k8s:validation:cel[1]:rule> : self.field == "odd" // +k8s:validation:cel[1]:messageExpression="field must be whether the length of the string is even or odd" // +k8s:validation:cel[1]:optionalOldSelf // +optional Field string }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, _, funcBuffer, imports := testOpenAPITypeWriter(t, e.Config) if funcErr != nil { t.Fatalf("Unexpected funcErr: %v", funcErr) } if callErr != nil { t.Fatalf("Unexpected callErr: %v", callErr) } expImports := []string{ `foo "example.com/base/foo"`, `common "k8s.io/kube-openapi/pkg/common"`, `spec "k8s.io/kube-openapi/pkg/validation/spec"`, } if !cmp.Equal(imports, expImports) { t.Errorf("wrong imports:\n%s", cmp.Diff(expImports, imports)) } if formatted, err := format.Source(funcBuffer.Bytes()); err != nil { t.Fatalf("%v\n%v", err, string(funcBuffer.Bytes())) } else { formatted_expected, ree := format.Source([]byte(`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ "Field": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "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\""}}, }, }, SchemaProps: spec.SchemaProps{ Default: "", Type: []string{"string"}, Format: "", }, }, }, }, VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-validations": []interface{}{map[string]interface{}{"fieldPath": "field", "message": "message1", "rule": "self == oldSelf"}}, }, }, }, } }`)) if ree != nil { t.Fatal(ree) } assertEqual(t, string(formatted_expected), string(formatted)) } }) } func TestRequired(t *testing.T) { inputFile := ` package foo // +k8s:openapi-gen=true type Blah struct { // +optional OptionalField string // +required RequiredField string // +required RequiredPointerField *string ` + "`json:\"requiredPointerField,omitempty\"`" + ` // +optional OptionalPointerField *string ` + "`json:\"optionalPointerField,omitempty\"`" + ` ImplicitlyRequiredField string ImplicitlyOptionalField string ` + "`json:\"implicitlyOptionalField,omitempty\"`" + ` }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, _, funcBuffer, imports := testOpenAPITypeWriter(t, e.Config) if funcErr != nil { t.Fatalf("Unexpected funcErr: %v", funcErr) } if callErr != nil { t.Fatalf("Unexpected callErr: %v", callErr) } expImports := []string{ `foo "example.com/base/foo"`, `common "k8s.io/kube-openapi/pkg/common"`, `spec "k8s.io/kube-openapi/pkg/validation/spec"`, } if !cmp.Equal(imports, expImports) { t.Errorf("wrong imports:\n%s", cmp.Diff(expImports, imports)) } if formatted, err := format.Source(funcBuffer.Bytes()); err != nil { t.Fatalf("%v\n%v", err, string(funcBuffer.Bytes())) } else { formatted_expected, ree := format.Source([]byte(`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ "OptionalField": { SchemaProps: spec.SchemaProps{ Default: "", Type: []string{"string"}, Format: "", }, }, "RequiredField": { SchemaProps: spec.SchemaProps{ Default: "", Type: []string{"string"}, Format: "", }, }, "requiredPointerField": { SchemaProps: spec.SchemaProps{ Type: []string{"string"}, Format: "", }, }, "optionalPointerField": { SchemaProps: spec.SchemaProps{ Type: []string{"string"}, Format: "", }, }, "ImplicitlyRequiredField": { SchemaProps: spec.SchemaProps{ Default: "", Type: []string{"string"}, Format: "", }, }, "implicitlyOptionalField": { SchemaProps: spec.SchemaProps{ Type: []string{"string"}, Format: "", }, }, }, Required: []string{"RequiredField", "requiredPointerField", "ImplicitlyRequiredField"}, }, }, } }`)) if ree != nil { t.Fatal(ree) } assertEqual(t, string(formatted_expected), string(formatted)) } }) // Show specifying both is an error badFile := ` package foo // +k8s:openapi-gen=true type Blah struct { // +optional // +required ConfusingField string }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": badFile, }, }}) defer e.Cleanup() callErr, funcErr, _, _, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Errorf("Unexpected callErr: %v", callErr) } if funcErr == nil { t.Fatalf("Expected funcErr") } if !strings.Contains(funcErr.Error(), "cannot be both optional and required") { t.Errorf("Unexpected error: %v", funcErr) } }) } func TestMarkerCommentsCustomDefsV3(t *testing.T) { inputFile := ` package foo import openapi "k8s.io/kube-openapi/pkg/common" // +k8s:validation:maxProperties=10 type Blah struct { } func (_ Blah) OpenAPIV3Definition() openapi.OpenAPIDefinition { return openapi.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"object"}, MaxProperties: ptr.To[int64](10), Format: "ipv4", }, }, } } func (_ Blah) OpenAPISchemaType() []string { return []string{"object"} } func (_ Blah) OpenAPISchemaFormat() string { return "ipv4" }` commonFile := `package common type OpenAPIDefinition struct {}` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }, { Name: "k8s.io/kube-openapi/pkg/common", Files: map[string]interface{}{ "common.go": commonFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) assertEqual(t, funcBuffer.String(), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.EmbedOpenAPIDefinitionIntoV2Extension(foo.Blah{}.OpenAPIV3Definition(), common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Type:foo.Blah{}.OpenAPISchemaType(), Format:foo.Blah{}.OpenAPISchemaFormat(), MaxProperties: ptr.To[int64](10), }, }, }) }`) }) } func TestMarkerCommentsV3OneOfTypes(t *testing.T) { inputFile := ` package foo // +k8s:validation:maxLength=10 type Blah struct { } func (_ Blah) OpenAPISchemaType() []string { return []string{"string"} } func (_ Blah) OpenAPIV3OneOfTypes() []string { return []string{"string", "array"} } func (_ Blah) OpenAPISchemaFormat() string { return "ipv4" }` packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) { e := packagestest.Export(t, x, []packagestest.Module{{ Name: "example.com/base/foo", Files: map[string]interface{}{ "foo.go": inputFile, }, }}) defer e.Cleanup() callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assertEqual(t, callBuffer.String(), `"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`) assertEqual(t, funcBuffer.String(), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.EmbedOpenAPIDefinitionIntoV2Extension(common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ OneOf:common.GenerateOpenAPIV3OneOfSchema(foo.Blah{}.OpenAPIV3OneOfTypes()), Format:foo.Blah{}.OpenAPISchemaFormat(), MaxLength: ptr.To[int64](10), }, }, },common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Type:foo.Blah{}.OpenAPISchemaType(), Format:foo.Blah{}.OpenAPISchemaFormat(), MaxLength: ptr.To[int64](10), }, }, }) }`) }) }