diff --git a/cmd/libs/go2idl/openapi-gen/README b/cmd/libs/go2idl/openapi-gen/README index 8f2225fba70..e6dcc85d0d6 100644 --- a/cmd/libs/go2idl/openapi-gen/README +++ b/cmd/libs/go2idl/openapi-gen/README @@ -2,3 +2,12 @@ - To generate definition for a specific type or package add "+k8s:openapi-gen=true" tag to the type/package comment lines. - To exclude a type or a member from a tagged package/type, add "+k8s:openapi-gen=false" tag to the comment lines. + +# OpenAPI Extensions +OpenAPI spec can have extensions on types. To define one or more extensions on a type or its member +add "+k8s:openapi-gen=x-kubernetes-$NAME:$VALUE" to the comment lines before type/member. A type/member can +have multiple extensions. The rest of the line in the comment will be used as $VALUE so there is no need to +escape or quote the value string. Extensions can be use to pass more information to client generators or +documentation generators. For example a type my have a friendly name to be displayed in documentation or +being used in a client's fluent interface. + diff --git a/cmd/libs/go2idl/openapi-gen/generators/openapi.go b/cmd/libs/go2idl/openapi-gen/generators/openapi.go index 5acaa75c70e..47ab5481899 100644 --- a/cmd/libs/go2idl/openapi-gen/generators/openapi.go +++ b/cmd/libs/go2idl/openapi-gen/generators/openapi.go @@ -40,15 +40,17 @@ const tagOptional = "optional" // Known values for the tag. const ( - tagValueTrue = "true" - tagValueFalse = "false" + tagValueTrue = "true" + tagValueFalse = "false" + tagExtensionPrefix = "x-kubernetes-" ) +func getOpenAPITagValue(comments []string) []string { + return types.ExtractCommentTags("+", comments)[tagName] +} + func hasOpenAPITagValue(comments []string, value string) bool { - tagValues := types.ExtractCommentTags("+", comments)[tagName] - if tagValues == nil { - return false - } + tagValues := getOpenAPITagValue(comments) for _, val := range tagValues { if val == value { return true @@ -342,6 +344,33 @@ func (g openAPITypeWriter) generate(t *types.Type) error { } g.Do("\"$.$\",", k) } + g.Do("},\n", nil) + if err := g.generateExtensions(t.CommentLines); err != nil { + return err + } + g.Do("},\n", nil) + } + return nil +} + +func (g openAPITypeWriter) generateExtensions(CommentLines []string) error { + tagValues := getOpenAPITagValue(CommentLines) + anyExtension := false + for _, val := range tagValues { + if strings.HasPrefix(val, tagExtensionPrefix) { + if !anyExtension { + g.Do("spec.VendorExtensible: {\nExtensions: spec.Extensions{\n", nil) + anyExtension = true + } + parts := strings.SplitN(val, ":", 2) + if len(parts) != 2 { + return fmt.Errorf("Invalid extension value: %v", val) + } + g.Do("\"$.$\": ", parts[0]) + g.Do("\"$.$\",\n", parts[1]) + } + } + if anyExtension { g.Do("},\n},\n", nil) } return nil @@ -396,6 +425,9 @@ func (g openAPITypeWriter) generateProperty(m *types.Member) error { return nil } g.Do("\"$.$\": {\n", name) + if err := g.generateExtensions(m.CommentLines); err != nil { + return err + } g.Do("SchemaProps: spec.SchemaProps{\n", nil) g.generateDescription(m.CommentLines) jsonTags := getJsonTags(m) diff --git a/cmd/libs/go2idl/openapi-gen/generators/openapi_test.go b/cmd/libs/go2idl/openapi-gen/generators/openapi_test.go index 0f12addf503..2da271177b2 100644 --- a/cmd/libs/go2idl/openapi-gen/generators/openapi_test.go +++ b/cmd/libs/go2idl/openapi-gen/generators/openapi_test.go @@ -71,7 +71,8 @@ func TestSimple(t *testing.T) { package foo // Blah is a test. -// +k8s:openapi=true +// +k8s:openapi-gen=true +// +k8s:openapi-gen=x-kubernetes-type-tag:type_test type Blah struct { // A simple string String string @@ -107,6 +108,9 @@ type Blah struct { Float32 float32 // a base64 encoded characters ByteArray []byte + // a member with an extension + // +k8s:openapi-gen=x-kubernetes-member-tag:member_test + WithExtension string } `) if err != nil { @@ -222,12 +226,29 @@ Type: []string{"string"}, Format: "byte", }, }, +"WithExtension": { +spec.VendorExtensible: { +Extensions: spec.Extensions{ +"x-kubernetes-member-tag": "member_test", }, -Required: []string{"String","Int64","Int32","Int16","Int8","Uint","Uint64","Uint32","Uint16","Uint8","Byte","Bool","Float64","Float32","ByteArray"}, +}, +SchemaProps: spec.SchemaProps{ +Description: "a member with an extension", +Type: []string{"string"}, +Format: "", +}, +}, +}, +Required: []string{"String","Int64","Int32","Int16","Int8","Uint","Uint64","Uint32","Uint16","Uint8","Byte","Bool","Float64","Float32","ByteArray","WithExtension"}, }, }, Dependencies: []string{ }, +spec.VendorExtensible: { +Extensions: spec.Extensions{ +"x-kubernetes-type-tag": "type_test", +}, +}, }, `, buffer.String()) }