/* 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" "path/filepath" "strings" "testing" "github.com/stretchr/testify/assert" "k8s.io/gengo/generator" "k8s.io/gengo/namer" "k8s.io/gengo/parser" "k8s.io/gengo/types" ) func construct(t *testing.T, files map[string]string, testNamer namer.Namer) (*parser.Builder, types.Universe, []*types.Type) { b := parser.New() for name, src := range files { if err := b.AddFileForTest(filepath.Dir(name), name, []byte(src)); err != nil { t.Fatal(err) } } u, err := b.FindTypes() if err != nil { t.Fatal(err) } orderer := namer.Orderer{Namer: testNamer} o := orderer.OrderUniverse(u) return b, u, o } func testOpenAPITypeWriter(t *testing.T, code string) (error, error, *assert.Assertions, *bytes.Buffer, *bytes.Buffer) { assert := assert.New(t) var testFiles = map[string]string{ "base/foo/bar.go": code, } rawNamer := namer.NewRawNamer("o", nil) namers := namer.NameSystems{ "raw": namer.NewRawNamer("", nil), "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/... }, } builder, universe, _ := construct(t, testFiles, rawNamer) context, err := generator.NewContext(builder, namers, "raw") if err != nil { t.Fatal(err) } blahT := universe.Type(types.Name{Package: "base/foo", 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, assert, callBuffer, funcBuffer } func TestSimple(t *testing.T) { callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 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"`+"`"+` } `) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), `, callBuffer.String()) assert.Equal(`func schema_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", }, }, }, Required: []string{"String","Int64","Int32","Int16","Int8","Uint","Uint64","Uint32","Uint16","Uint8","Byte","Bool","Float64","Float32","ByteArray","WithExtension","WithStructTagExtension","WithListType","Map","StringPointer"}, }, VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-type-tag": "type_test", }, }, }, } } `, funcBuffer.String()) } func TestEmptyProperties(t *testing.T) { callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` package foo // Blah demonstrate a struct without fields. type Blah struct { } `) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), `, callBuffer.String()) assert.Equal(`func schema_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"}, }, }, } } `, funcBuffer.String()) } func TestNestedStruct(t *testing.T) { callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 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 } `) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), `, callBuffer.String()) assert.Equal(`func schema_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("base/foo.Nested"), }, }, }, Required: []string{"Field"}, }, }, Dependencies: []string{ "base/foo.Nested",}, } } `, funcBuffer.String()) } func TestNestedStructPointer(t *testing.T) { callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 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 } `) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), `, callBuffer.String()) assert.Equal(`func schema_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("base/foo.Nested"), }, }, }, Required: []string{"Field"}, }, }, Dependencies: []string{ "base/foo.Nested",}, } } `, funcBuffer.String()) } func TestEmbeddedStruct(t *testing.T) { callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 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 } `) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), `, callBuffer.String()) assert.Equal(`func schema_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("base/foo.Nested"), }, }, }, Required: []string{"Nested"}, }, }, Dependencies: []string{ "base/foo.Nested",}, } } `, funcBuffer.String()) } func TestSingleEmbeddedStruct(t *testing.T) { callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 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"`+"`"+` } `) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), `, callBuffer.String()) assert.Equal(`func schema_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("base/foo.Nested"), }, }, }, }, }, Dependencies: []string{ "base/foo.Nested",}, } } `, funcBuffer.String()) } func TestEmbeddedInlineStruct(t *testing.T) { callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 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"`+"`"+` } `) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), `, callBuffer.String()) assert.Equal(`func schema_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"}, }, }, } } `, funcBuffer.String()) } func TestEmbeddedInlineStructPointer(t *testing.T) { callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 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"`+"`"+` } `) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), `, callBuffer.String()) assert.Equal(`func schema_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"}, }, }, } } `, funcBuffer.String()) } func TestNestedMapString(t *testing.T) { callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` package foo // Map sample tests openAPIGen.generateMapProperty method. type Blah struct { // A sample String to String map StringToArray map[string]map[string]string } `) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), `, callBuffer.String()) assert.Equal(`func schema_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"}, }, }, } } `, funcBuffer.String()) } func TestNestedMapInt(t *testing.T) { callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` package foo // Map sample tests openAPIGen.generateMapProperty method. type Blah struct { // A sample String to String map StringToArray map[string]map[string]int } `) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), `, callBuffer.String()) assert.Equal(`func schema_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"}, }, }, } } `, funcBuffer.String()) } func TestNestedMapBoolean(t *testing.T) { callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` package foo // Map sample tests openAPIGen.generateMapProperty method. type Blah struct { // A sample String to String map StringToArray map[string]map[string]bool } `) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), `, callBuffer.String()) assert.Equal(`func schema_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"}, }, }, } } `, funcBuffer.String()) } func TestFailingSample1(t *testing.T) { _, funcErr, assert, _, _ := testOpenAPITypeWriter(t, ` 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 } `) if assert.Error(funcErr, "An error was expected") { assert.Equal(funcErr, fmt.Errorf("failed to generate map property in base/foo.Blah: StringToArray: map with non-string keys are not supported by OpenAPI in map[int]string")) } } func TestFailingSample2(t *testing.T) { _, funcErr, assert, _, _ := testOpenAPITypeWriter(t, ` package foo // Map sample tests openAPIGen.generateMapProperty method. type Blah struct { // A sample String to String map StringToArray map[int]string } `) if assert.Error(funcErr, "An error was expected") { assert.Equal(funcErr, fmt.Errorf("failed to generate map property in base/foo.Blah: StringToArray: map with non-string keys are not supported by OpenAPI in map[int]string")) } } func TestFailingDefaultEnforced(t *testing.T) { tests := []struct { definition string expectedError error }{ { definition: ` package foo type Blah struct { // +default=5 Int int } `, expectedError: fmt.Errorf("failed to generate default in base/foo.Blah: Int: invalid default value (5) for non-pointer/non-omitempty. If specified, must be: 0"), }, { definition: ` package foo type Blah struct { // +default={"foo": 5} Struct struct{ foo int } } `, expectedError: fmt.Errorf(`failed to generate default in base/foo.Blah: Struct: invalid default value (map[string]interface {}{"foo":5}) for non-pointer/non-omitempty. If specified, must be: {}`), }, { definition: ` package foo type Blah struct { List []Item } // +default="foo" type Item string `, expectedError: fmt.Errorf(`failed to generate slice property in base/foo.Blah: List: invalid default value ("foo") for non-pointer/non-omitempty. If specified, must be: ""`), }, { definition: ` package foo type Blah struct { Map map[string]Item } // +default="foo" type Item string `, expectedError: fmt.Errorf(`failed to generate map property in base/foo.Blah: Map: invalid default value ("foo") for non-pointer/non-omitempty. If specified, must be: ""`), }, } for i, test := range tests { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { _, funcErr, assert, _, _ := testOpenAPITypeWriter(t, test.definition) if assert.Error(funcErr, "An error was expected") { assert.Equal(funcErr, test.expectedError) } }) } } func TestCustomDef(t *testing.T) { callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 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", }, }, } } `) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assert.Equal(`"base/foo.Blah": foo.Blah{}.OpenAPIDefinition(), `, callBuffer.String()) assert.Equal(``, funcBuffer.String()) } func TestCustomDefV3(t *testing.T) { callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 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", }, }, } } `) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assert.Equal(`"base/foo.Blah": foo.Blah{}.OpenAPIV3Definition(), `, callBuffer.String()) assert.Equal(``, funcBuffer.String()) } func TestCustomDefV2AndV3(t *testing.T) { callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 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", }, }, } } `) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assert.Equal(`"base/foo.Blah": common.EmbedOpenAPIDefinitionIntoV2Extension(foo.Blah{}.OpenAPIV3Definition(), foo.Blah{}.OpenAPIDefinition()), `, callBuffer.String()) assert.Equal(``, funcBuffer.String()) } func TestCustomDefs(t *testing.T) { callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` package foo // Blah is a custom type type Blah struct { } func (_ Blah) OpenAPISchemaType() []string { return []string{"string"} } func (_ Blah) OpenAPISchemaFormat() string { return "date-time" } `) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), `, callBuffer.String()) assert.Equal(`func schema_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(), }, }, } } `, funcBuffer.String()) } func TestCustomDefsV3(t *testing.T) { callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 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" } `) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), `, callBuffer.String()) assert.Equal(`func schema_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(), }, }, }) } `, funcBuffer.String()) } func TestV3OneOfTypes(t *testing.T) { callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 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"} } `) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), `, callBuffer.String()) assert.Equal(`func schema_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(), }, }, }) } `, funcBuffer.String()) } func TestPointer(t *testing.T) { callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 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 } `) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), `, callBuffer.String()) assert.Equal(`func schema_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("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{ "base/foo.Blah",}, } } `, funcBuffer.String()) } func TestNestedLists(t *testing.T) { callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 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 } `) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), `, callBuffer.String()) assert.Equal(`func schema_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", }, }, }, } } `, funcBuffer.String()) } func TestNestListOfMaps(t *testing.T) { callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 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 } `) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), `, callBuffer.String()) assert.Equal(`func schema_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", }, }, }, } } `, funcBuffer.String()) } func TestExtensions(t *testing.T) { callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 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 } `) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), `, callBuffer.String()) assert.Equal(`func schema_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", }, }, }, } } `, funcBuffer.String()) } func TestUnion(t *testing.T) { callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, ` 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"`+"`"+` } `) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref), `, callBuffer.String()) assert.Equal(`func schema_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", }, }, }, }, }, }, } } `, funcBuffer.String()) } func TestEnum(t *testing.T) { callErr, funcErr, assert, _, funcBuffer := testOpenAPITypeWriter(t, ` 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 }`) if callErr != nil { t.Fatal(callErr) } if funcErr != nil { t.Fatal(funcErr) } _ = assert assert.Equal(`func schema_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"}}, }, }, Required: []string{"Value","NoCommentEnum"}, }, VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-type-tag": "type_test", }, }, }, } } `, funcBuffer.String()) }