diff --git a/cmd/openapi-gen/args/args.go b/cmd/openapi-gen/args/args.go index 153784ed9..6472971d9 100644 --- a/cmd/openapi-gen/args/args.go +++ b/cmd/openapi-gen/args/args.go @@ -33,6 +33,10 @@ type Args struct { // by API linter. If specified, API rule violations will be printed to report file. // Otherwise default value "-" will be used which indicates stdout. ReportFilename string + + // UseOpenAPIModelNames specifies the use of OpenAPI model names instead of + // Go '.' names for types in the OpenAPI spec. + UseOpenAPIModelNames bool } // New returns default arguments for the generator. Returning the arguments instead @@ -58,6 +62,7 @@ func (args *Args) AddFlags(fs *pflag.FlagSet) { "the path to a file containing boilerplate header text; the string \"YEAR\" will be replaced with the current 4-digit year") fs.StringVarP(&args.ReportFilename, "report-filename", "r", args.ReportFilename, "Name of report file used by API linter to print API violations. Default \"-\" stands for standard output. NOTE that if valid filename other than \"-\" is specified, API linter won't return error on detected API violations. This allows further check of existing API violations without stopping the OpenAPI generation toolchain.") + fs.BoolVar(&args.UseOpenAPIModelNames, "use-openapi-model-names", false, "Use OpenAPI model names instead of Go '.' names for types in the OpenAPI spec.") } // Validate checks the given arguments. diff --git a/pkg/generators/config.go b/pkg/generators/config.go index 1fbd77598..9f60b0357 100644 --- a/pkg/generators/config.go +++ b/pkg/generators/config.go @@ -74,6 +74,7 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target newOpenAPIGen( args.OutputFile, args.OutputPkg, + args.UseOpenAPIModelNames, ), newAPIViolationGen(), } diff --git a/pkg/generators/openapi.go b/pkg/generators/openapi.go index c5c009381..75a6706f2 100644 --- a/pkg/generators/openapi.go +++ b/pkg/generators/openapi.go @@ -127,17 +127,19 @@ const ( type openAPIGen struct { generator.GoGenerator // TargetPackage is the package that will get GetOpenAPIDefinitions function returns all open API definitions. - targetPackage string - imports namer.ImportTracker + targetPackage string + imports namer.ImportTracker + useOpenAPIModelNames bool } -func newOpenAPIGen(outputFilename string, targetPackage string) generator.Generator { +func newOpenAPIGen(outputFilename string, targetPackage string, useOpenAPIModelNames bool) generator.Generator { return &openAPIGen{ GoGenerator: generator.GoGenerator{ OutputFilename: outputFilename, }, - imports: generator.NewImportTrackerForPackage(targetPackage), - targetPackage: targetPackage, + imports: generator.NewImportTrackerForPackage(targetPackage), + targetPackage: targetPackage, + useOpenAPIModelNames: useOpenAPIModelNames, } } @@ -179,7 +181,7 @@ func (g *openAPIGen) Init(c *generator.Context, w io.Writer) error { sw.Do("return map[string]$.OpenAPIDefinition|raw${\n", argsFromType(nil)) for _, t := range c.Order { - err := newOpenAPITypeWriter(sw, c).generateCall(t) + err := newOpenAPITypeWriter(sw, c, g.useOpenAPIModelNames).generateCall(t) if err != nil { return err } @@ -194,7 +196,7 @@ func (g *openAPIGen) Init(c *generator.Context, w io.Writer) error { func (g *openAPIGen) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { klog.V(5).Infof("generating for type %v", t) sw := generator.NewSnippetWriter(w, c, "$", "$") - err := newOpenAPITypeWriter(sw, c).generate(t) + err := newOpenAPITypeWriter(sw, c, g.useOpenAPIModelNames).generate(t) if err != nil { return err } @@ -233,14 +235,16 @@ type openAPITypeWriter struct { refTypes map[string]*types.Type enumContext *enumContext GetDefinitionInterface *types.Type + useOpenAPIModelNames bool } -func newOpenAPITypeWriter(sw *generator.SnippetWriter, c *generator.Context) openAPITypeWriter { +func newOpenAPITypeWriter(sw *generator.SnippetWriter, c *generator.Context, useOpenAPIModelNames bool) openAPITypeWriter { return openAPITypeWriter{ - SnippetWriter: sw, - context: c, - refTypes: map[string]*types.Type{}, - enumContext: newEnumContext(c), + SnippetWriter: sw, + context: c, + refTypes: map[string]*types.Type{}, + enumContext: newEnumContext(c), + useOpenAPIModelNames: useOpenAPIModelNames, } } @@ -339,8 +343,18 @@ func (g openAPITypeWriter) generateCall(t *types.Type) error { // Only generate for struct type and ignore the rest switch t.Kind { case types.Struct: + if namer.IsPrivateGoName(t.Name.Name) { // skip private types + return nil + } + args := argsFromType(t) - g.Do("\"$.$\": ", t.Name) + + if g.useOpenAPIModelNames { + g.Do("$.|raw${}.OpenAPIModelName(): ", t) + } else { + // Legacy case: use the "canonical type name" + g.Do("\"$.$\": ", t.Name) + } hasV2Definition := hasOpenAPIDefinitionMethod(t) hasV2DefinitionTypeAndFormat := hasOpenAPIDefinitionMethods(t) @@ -667,7 +681,12 @@ func (g openAPITypeWriter) generate(t *types.Type) error { if len(deps) > 0 { g.Do("Dependencies: []string{\n", args) for _, k := range deps { - g.Do("\"$.$\",", k) + t := g.refTypes[k] + if g.useOpenAPIModelNames { + g.Do("$.|raw${}.OpenAPIModelName(),", t) + } else { + g.Do("\"$.$\",", k) + } } g.Do("},\n", nil) } @@ -1027,7 +1046,11 @@ func (g openAPITypeWriter) generateSimpleProperty(typeString, format string) { func (g openAPITypeWriter) generateReferenceProperty(t *types.Type) { g.refTypes[t.Name.String()] = t - g.Do("Ref: ref(\"$.$\"),\n", t.Name.String()) + if g.useOpenAPIModelNames { + g.Do("Ref: ref($.|raw${}.OpenAPIModelName()),\n", t) + } else { + g.Do("Ref: ref(\"$.$\"),\n", t.Name.String()) + } } func resolvePtrType(t *types.Type) *types.Type { diff --git a/pkg/generators/openapi_test.go b/pkg/generators/openapi_test.go index 55be4c557..92da8ad1f 100644 --- a/pkg/generators/openapi_test.go +++ b/pkg/generators/openapi_test.go @@ -68,11 +68,11 @@ func testOpenAPITypeWriter(t *testing.T, cfg *packages.Config) (error, error, *b callBuffer := &bytes.Buffer{} callSW := generator.NewSnippetWriter(callBuffer, context, "$", "$") - callError := newOpenAPITypeWriter(callSW, context).generateCall(blahT) + callError := newOpenAPITypeWriter(callSW, context, false).generateCall(blahT) funcBuffer := &bytes.Buffer{} funcSW := generator.NewSnippetWriter(funcBuffer, context, "$", "$") - funcError := newOpenAPITypeWriter(funcSW, context).generate(blahT) + funcError := newOpenAPITypeWriter(funcSW, context, false).generate(blahT) return callError, funcError, callBuffer, funcBuffer, imports.ImportLines() } diff --git a/pkg/util/util.go b/pkg/util/util.go index 6eee935b2..830ec3ca0 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -92,10 +92,21 @@ type OpenAPICanonicalTypeNamer interface { OpenAPICanonicalTypeName() string } +// OpenAPIModelNamer is an interface Go types may implement to provide an OpenAPI model name. +// +// This takes precedence over OpenAPICanonicalTypeNamer, and should be used when a Go type has a model +// name that differs from its canonical type name as determined by Go package name reflection. +type OpenAPIModelNamer interface { + OpenAPIModelName() string +} + // GetCanonicalTypeName will find the canonical type name of a sample object, removing // the "vendor" part of the path func GetCanonicalTypeName(model interface{}) string { - if namer, ok := model.(OpenAPICanonicalTypeNamer); ok { + switch namer := model.(type) { + case OpenAPIModelNamer: + return namer.OpenAPIModelName() + case OpenAPICanonicalTypeNamer: return namer.OpenAPICanonicalTypeName() } t := reflect.TypeOf(model) diff --git a/test/integration/integration_suite_test.go b/test/integration/integration_suite_test.go index d6894f772..50e195da4 100644 --- a/test/integration/integration_suite_test.go +++ b/test/integration/integration_suite_test.go @@ -104,6 +104,23 @@ var _ = BeforeSuite(func() { Expect(err).ShouldNot(HaveOccurred()) Eventually(session, timeoutSeconds).Should(gexec.Exit(0)) + // Run the OpenAPI code generator with --use-openapi-model-names + Expect(terr).ShouldNot(HaveOccurred()) + + By("'namedmodels' running openapi-gen") + args = append([]string{ + "--output-dir", tempDir + "/namedmodels", + "--output-pkg", outputPkg + "/namedmodels", + "--output-file", generatedCodeFileName, + "--use-openapi-model-names", + "--go-header-file", headerFilePath, + }, path.Join(testPkgRoot, "namedmodels")) + command = exec.Command(openAPIGenPath, args...) + command.Dir = workingDirectory + session, err = gexec.Start(command, GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) + Eventually(session, timeoutSeconds).Should(gexec.Exit(0)) + By("writing swagger v2.0") // Create the OpenAPI swagger builder. binaryPath, berr = gexec.Build("./builder/main.go") @@ -131,6 +148,20 @@ var _ = BeforeSuite(func() { session, err = gexec.Start(command, GinkgoWriter, GinkgoWriter) Expect(err).ShouldNot(HaveOccurred()) Eventually(session, timeoutSeconds).Should(gexec.Exit(0)) + + By("'namedmodels' writing OpenAPI v3.0") + // Create the OpenAPI swagger builder. + binaryPath, berr = gexec.Build("./builder3/main.go") + Expect(berr).ShouldNot(HaveOccurred()) + + // Execute the builder, generating an OpenAPI swagger file with definitions. + gov3 = generatedFile("namedmodels/" + generatedOpenAPIv3FileName) + By("'namedmodels' writing swagger to " + gov3) + command = exec.Command(binaryPath, gov3) + command.Dir = workingDirectory + session, err = gexec.Start(command, GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) + Eventually(session, timeoutSeconds).Should(gexec.Exit(0)) }) var _ = AfterSuite(func() { @@ -152,6 +183,18 @@ var _ = Describe("Open API Definitions Generation", func() { Expect(err).ShouldNot(HaveOccurred()) Eventually(session, timeoutSeconds).Should(gexec.Exit(0)) }) + It("'namedmodels' Generated code should match golden files", func() { + // Diff the generated code against the golden code. Exit code should be zero. + command := exec.Command( + "diff", "-u", + "pkg/generated/namedmodels/"+generatedCodeFileName, + generatedFile("namedmodels/"+generatedCodeFileName), + ) + command.Dir = workingDirectory + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) + Eventually(session, timeoutSeconds).Should(gexec.Exit(0)) + }) }) Describe("Validating OpenAPI V2 Definition Generation", func() { diff --git a/test/integration/pkg/generated/namedmodels/openapi_generated.go b/test/integration/pkg/generated/namedmodels/openapi_generated.go new file mode 100644 index 000000000..411630ffa --- /dev/null +++ b/test/integration/pkg/generated/namedmodels/openapi_generated.go @@ -0,0 +1,94 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by openapi-gen. DO NOT EDIT. + +package namedmodels + +import ( + common "k8s.io/kube-openapi/pkg/common" + spec "k8s.io/kube-openapi/pkg/validation/spec" + namedmodels "k8s.io/kube-openapi/test/integration/testdata/namedmodels" +) + +func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { + return map[string]common.OpenAPIDefinition{ + namedmodels.AtomicStruct{}.OpenAPIModelName(): schema_test_integration_testdata_namedmodels_AtomicStruct(ref), + namedmodels.ContainedStruct{}.OpenAPIModelName(): schema_test_integration_testdata_namedmodels_ContainedStruct(ref), + namedmodels.Struct{}.OpenAPIModelName(): schema_test_integration_testdata_namedmodels_Struct(ref), + } +} + +func schema_test_integration_testdata_namedmodels_AtomicStruct(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "Field": { + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"Field"}, + }, + }, + } +} + +func schema_test_integration_testdata_namedmodels_ContainedStruct(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + }, + }, + } +} + +func schema_test_integration_testdata_namedmodels_Struct(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "Field": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(namedmodels.ContainedStruct{}.OpenAPIModelName()), + }, + }, + "OtherField": { + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"Field", "OtherField"}, + }, + }, + Dependencies: []string{ + namedmodels.ContainedStruct{}.OpenAPIModelName()}, + } +} diff --git a/test/integration/testdata/namedmodels/golden.v3.json b/test/integration/testdata/namedmodels/golden.v3.json new file mode 100644 index 000000000..7a3b832a8 --- /dev/null +++ b/test/integration/testdata/namedmodels/golden.v3.json @@ -0,0 +1,1288 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Integration Test", + "version": "1.0" + }, + "paths": { + "/test/custom": { + "post": { + "operationId": "create-custom.Bah", + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom.Bah" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/custom/bac": { + "get": { + "operationId": "get-custom.Bac", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom.Bac" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/custom/bah": { + "get": { + "operationId": "get-custom.Bah", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom.Bah" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/custom/bak": { + "get": { + "operationId": "get-custom.Bak", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom.Bak" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/custom/bal": { + "get": { + "operationId": "get-custom.Bal", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom.Bal" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/defaults": { + "post": { + "operationId": "create-defaults.Defaulted", + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/defaults.Defaulted" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/defaults/defaulted": { + "get": { + "operationId": "get-defaults.Defaulted", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/defaults.Defaulted" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/dummytype": { + "post": { + "operationId": "create-dummytype.Waldo", + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dummytype.Waldo" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "description": "Internal Service Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dummytype.StatusError" + } + } + } + } + } + } + }, + "/test/dummytype/bar": { + "get": { + "operationId": "get-dummytype.Bar", + "responses": { + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "description": "Internal Service Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dummytype.StatusError" + } + } + } + } + } + } + }, + "/test/dummytype/baz": { + "get": { + "operationId": "get-dummytype.Baz", + "responses": { + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "description": "Internal Service Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dummytype.StatusError" + } + } + } + } + } + } + }, + "/test/dummytype/foo": { + "get": { + "operationId": "get-dummytype.Foo", + "responses": { + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "description": "Internal Service Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dummytype.StatusError" + } + } + } + } + } + } + }, + "/test/dummytype/waldo": { + "get": { + "operationId": "get-dummytype.Waldo", + "responses": { + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "description": "Internal Service Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dummytype.StatusError" + } + } + } + } + } + } + }, + "/test/listtype": { + "post": { + "operationId": "create-listtype.SetList", + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/listtype.SetList" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/listtype/atomiclist": { + "get": { + "operationId": "get-listtype.AtomicList", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/listtype.AtomicList" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/listtype/maplist": { + "get": { + "operationId": "get-listtype.MapList", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/listtype.MapList" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/listtype/setlist": { + "get": { + "operationId": "get-listtype.SetList", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/listtype.SetList" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/maptype": { + "post": { + "operationId": "create-maptype.AtomicMap", + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/maptype.AtomicMap" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/maptype/atomicmap": { + "get": { + "operationId": "get-maptype.AtomicMap", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/maptype.AtomicMap" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/maptype/granularmap": { + "get": { + "operationId": "get-maptype.GranularMap", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/maptype.GranularMap" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/structtype": { + "post": { + "operationId": "create-structtype.DeclaredAtomicStruct", + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/structtype.DeclaredAtomicStruct" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/structtype/atomicstruct": { + "get": { + "operationId": "get-structtype.AtomicStruct", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/structtype.AtomicStruct" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/structtype/declaredatomicstruct": { + "get": { + "operationId": "get-structtype.DeclaredAtomicStruct", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/structtype.DeclaredAtomicStruct" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/structtype/fieldleveloverridestruct": { + "get": { + "operationId": "get-structtype.FieldLevelOverrideStruct", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/structtype.FieldLevelOverrideStruct" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/structtype/granularstruct": { + "get": { + "operationId": "get-structtype.GranularStruct", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/structtype.GranularStruct" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/uniontype": { + "post": { + "operationId": "create-uniontype.InlinedUnion", + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/uniontype.InlinedUnion" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/uniontype/inlinedunion": { + "get": { + "operationId": "get-uniontype.InlinedUnion", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/uniontype.InlinedUnion" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/uniontype/toplevelunion": { + "get": { + "operationId": "get-uniontype.TopLevelUnion", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/uniontype.TopLevelUnion" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/valuevalidation": { + "post": { + "operationId": "create-valuevalidation.Foo", + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/valuevalidation.Foo" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/valuevalidation/foo": { + "get": { + "operationId": "get-valuevalidation.Foo", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/valuevalidation.Foo" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/valuevalidation/foo2": { + "get": { + "operationId": "get-valuevalidation.Foo2", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/valuevalidation.Foo2" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/valuevalidation/foo3": { + "get": { + "operationId": "get-valuevalidation.Foo3", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/valuevalidation.Foo3" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/valuevalidation/foo4": { + "get": { + "operationId": "get-valuevalidation.Foo4", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/valuevalidation.Foo4" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/test/valuevalidation/foo5": { + "get": { + "operationId": "get-valuevalidation.Foo5", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/valuevalidation.Foo5" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + } + }, + "components": { + "schemas": { + "custom.Bac": { + "type": "object" + }, + "custom.Bah": { + "type": "object" + }, + "custom.Bak": { + "type": "integer" + }, + "custom.Bal": { + "type": "string" + }, + "defaults.Defaulted": { + "type": "object", + "required": [ + "OtherField", + "List", + "Sub", + "OtherSub", + "Map" + ], + "properties": { + "Field": { + "type": "string", + "default": "bar" + }, + "List": { + "type": "array", + "default": [ + "foo", + "bar" + ], + "items": { + "type": "string", + "default": "" + } + }, + "Map": { + "type": "object", + "default": { + "foo": "bar" + }, + "additionalProperties": { + "type": "string", + "default": "" + } + }, + "OtherField": { + "type": "integer", + "format": "int32", + "default": 0 + }, + "OtherSub": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/defaults.SubStruct" + } + ] + }, + "Sub": { + "default": { + "i": 5, + "s": "foo" + }, + "allOf": [ + { + "$ref": "#/components/schemas/defaults.SubStruct" + } + ] + }, + "defaultedAliasSymbolReference": { + "type": "string", + "default": "SymbolConstant" + }, + "externalSymbolReference": { + "type": "string", + "default": "apple" + }, + "fullyQualifiedSymbolReference": { + "type": "string", + "default": "SymbolConstant" + }, + "localSymbolReference": { + "type": "string", + "default": "SymbolConstant" + }, + "pointerConversionSymbolReference": { + "type": "string", + "default": "apple" + } + } + }, + "defaults.SubStruct": { + "type": "object", + "required": [ + "S" + ], + "properties": { + "I": { + "type": "integer", + "format": "int32", + "default": 1 + }, + "S": { + "type": "string", + "default": "" + } + } + }, + "dummytype.Bar": { + "type": "object", + "required": [ + "ViolationBehind", + "Violation" + ], + "properties": { + "Violation": { + "type": "boolean", + "default": false + }, + "ViolationBehind": { + "type": "boolean", + "default": false + } + } + }, + "dummytype.Baz": { + "type": "object", + "required": [ + "Violation", + "ViolationBehind" + ], + "properties": { + "Violation": { + "type": "boolean", + "default": false + }, + "ViolationBehind": { + "type": "boolean", + "default": false + } + } + }, + "dummytype.Foo": { + "type": "object", + "required": [ + "Second", + "First" + ], + "properties": { + "First": { + "type": "integer", + "format": "int32", + "default": 0 + }, + "Second": { + "type": "string", + "default": "" + } + } + }, + "dummytype.StatusError": { + "type": "object", + "required": [ + "Code", + "Message" + ], + "properties": { + "Code": { + "type": "integer", + "format": "int32", + "default": 0 + }, + "Message": { + "type": "string", + "default": "" + } + } + }, + "dummytype.Waldo": { + "type": "object", + "required": [ + "First", + "Second" + ], + "properties": { + "First": { + "type": "integer", + "format": "int32", + "default": 0 + }, + "Second": { + "type": "string", + "default": "" + } + } + }, + "listtype.AtomicList": { + "type": "object", + "required": [ + "Field" + ], + "properties": { + "Field": { + "type": "array", + "items": { + "type": "string", + "default": "" + }, + "x-kubernetes-list-type": "atomic" + } + } + }, + "listtype.Item": { + "type": "object", + "required": [ + "Protocol", + "Port" + ], + "properties": { + "Port": { + "type": "integer", + "format": "int32", + "default": 0 + }, + "Protocol": { + "type": "string", + "default": "" + }, + "a": { + "type": "integer", + "format": "int32", + "default": 0 + }, + "b": { + "type": "integer", + "format": "int32" + }, + "c": { + "type": "integer", + "format": "int32", + "default": 0 + } + } + }, + "listtype.MapList": { + "type": "object", + "required": [ + "Field" + ], + "properties": { + "Field": { + "type": "array", + "items": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/listtype.Item" + } + ] + }, + "x-kubernetes-list-map-keys": [ + "port" + ], + "x-kubernetes-list-type": "map" + } + } + }, + "listtype.SetList": { + "type": "object", + "required": [ + "Field" + ], + "properties": { + "Field": { + "type": "array", + "items": { + "type": "string", + "default": "" + }, + "x-kubernetes-list-type": "set" + } + } + }, + "maptype.AtomicMap": { + "type": "object", + "required": [ + "KeyValue" + ], + "properties": { + "KeyValue": { + "type": "object", + "additionalProperties": { + "type": "string", + "default": "" + }, + "x-kubernetes-map-type": "atomic" + } + } + }, + "maptype.GranularMap": { + "type": "object", + "required": [ + "KeyValue" + ], + "properties": { + "KeyValue": { + "type": "object", + "additionalProperties": { + "type": "string", + "default": "" + }, + "x-kubernetes-map-type": "granular" + } + } + }, + "structtype.AtomicStruct": { + "type": "object", + "required": [ + "Field", + "OtherField" + ], + "properties": { + "Field": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/structtype.ContainedStruct" + } + ], + "x-kubernetes-map-type": "atomic" + }, + "OtherField": { + "type": "integer", + "format": "int32", + "default": 0 + } + } + }, + "structtype.ContainedStruct": { + "type": "object" + }, + "structtype.DeclaredAtomicStruct": { + "type": "object", + "required": [ + "Field" + ], + "properties": { + "Field": { + "type": "integer", + "format": "int32", + "default": 0 + } + }, + "x-kubernetes-map-type": "atomic" + }, + "structtype.FieldLevelOverrideStruct": { + "type": "object", + "required": [ + "Field", + "OtherField" + ], + "properties": { + "Field": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/structtype.DeclaredAtomicStruct" + } + ], + "x-kubernetes-map-type": "atomic" + }, + "OtherField": { + "type": "integer", + "format": "int32", + "default": 0 + } + } + }, + "structtype.GranularStruct": { + "type": "object", + "required": [ + "Field", + "OtherField" + ], + "properties": { + "Field": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/structtype.ContainedStruct" + } + ], + "x-kubernetes-map-type": "granular" + }, + "OtherField": { + "type": "integer", + "format": "int32", + "default": 0 + } + } + }, + "uniontype.InlinedUnion": { + "type": "object", + "required": [ + "name", + "type" + ], + "properties": { + "alpha": { + "type": "integer", + "format": "int32" + }, + "beta": { + "type": "integer", + "format": "int32" + }, + "field1": { + "type": "integer", + "format": "int32" + }, + "field2": { + "type": "integer", + "format": "int32" + }, + "fieldA": { + "type": "integer", + "format": "int32" + }, + "fieldB": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string", + "default": "" + }, + "type": { + "type": "string", + "default": "" + }, + "unionType": { + "type": "string", + "default": "" + } + }, + "x-kubernetes-unions": [ + { + "discriminator": "unionType", + "fields-to-discriminateBy": { + "fieldA": "FieldA", + "fieldB": "FieldB" + } + }, + { + "discriminator": "type", + "fields-to-discriminateBy": { + "alpha": "Alpha", + "beta": "Beta" + } + }, + { + "fields-to-discriminateBy": { + "field1": "Field1", + "field2": "Field2" + } + } + ] + }, + "uniontype.TopLevelUnion": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "fieldA": { + "type": "integer", + "format": "int32" + }, + "fieldB": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string", + "default": "" + }, + "unionType": { + "type": "string", + "default": "" + } + }, + "x-kubernetes-unions": [ + { + "discriminator": "unionType", + "fields-to-discriminateBy": { + "fieldA": "FieldA", + "fieldB": "FieldB" + } + } + ] + }, + "valuevalidation.Foo": { + "type": "object", + "maxProperties": 5, + "minProperties": 1, + "required": [ + "StringValue", + "NumberValue", + "ArrayValue", + "MapValue" + ], + "properties": { + "ArrayValue": { + "type": "array", + "maxItems": 5, + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "default": "" + } + }, + "MapValue": { + "type": "object", + "maxProperties": 5, + "minProperties": 1, + "additionalProperties": { + "type": "string", + "default": "" + } + }, + "NumberValue": { + "type": "number", + "format": "double", + "default": 0, + "maximum": 5, + "exclusiveMaximum": true, + "minimum": 1, + "exclusiveMinimum": true, + "multipleOf": 2 + }, + "StringValue": { + "type": "string", + "default": "", + "maxLength": 5, + "minLength": 1, + "pattern": "^a.*b$" + }, + "celField": { + "type": "string", + "default": "", + "x-kubernetes-validations": [ + { + "message": "string message", + "rule": "self.length() \u003e 0" + }, + { + "messageExpression": "self + ' hello'", + "rule": "self.length() % 2 == 0" + } + ] + } + }, + "x-kubernetes-validations": [ + { + "message": "foo", + "rule": "self == oldSelf" + } + ] + }, + "valuevalidation.Foo2": { + "type": "object" + }, + "valuevalidation.Foo3": { + "description": "This one has a OneOf", + "format": "string", + "maxProperties": 5, + "oneOf": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "x-kubernetes-validations": [ + { + "message": "foo3", + "rule": "self == oldSelf" + } + ] + }, + "valuevalidation.Foo4": { + "type": "integer" + }, + "valuevalidation.Foo5": { + "type": "object" + } + }, + "responses": { + "NotFound": { + "description": "Entity not found." + } + } + } + } \ No newline at end of file diff --git a/test/integration/testdata/namedmodels/struct.go b/test/integration/testdata/namedmodels/struct.go new file mode 100644 index 000000000..58313a481 --- /dev/null +++ b/test/integration/testdata/namedmodels/struct.go @@ -0,0 +1,27 @@ +package structtype + +// +k8s:openapi-gen=true +type Struct struct { + Field ContainedStruct + OtherField int +} + +func (Struct) OpenAPIModelName() string { + return "com.example.Struct" +} + +// +k8s:openapi-gen=true +type ContainedStruct struct{} + +func (ContainedStruct) OpenAPIModelName() string { + return "com.example.ContainedStruct" +} + +// +k8s:openapi-gen=true +type AtomicStruct struct { + Field int +} + +func (AtomicStruct) OpenAPIModelName() string { + return "com.example.AtomicStruct" +}