From cac0144911eb07c941643dd76fe05a4b311f0916 Mon Sep 17 00:00:00 2001 From: mbohlool Date: Wed, 5 Apr 2017 17:52:33 -0700 Subject: [PATCH] Add patchMergeKey and patchStrategy support to OpenAPI --- .../go2idl/openapi-gen/generators/openapi.go | 90 ++++++++++++++++--- .../openapi-gen/generators/openapi_test.go | 19 +++- 2 files changed, 94 insertions(+), 15 deletions(-) diff --git a/cmd/libs/go2idl/openapi-gen/generators/openapi.go b/cmd/libs/go2idl/openapi-gen/generators/openapi.go index aec28b51f14..9d7d232f768 100644 --- a/cmd/libs/go2idl/openapi-gen/generators/openapi.go +++ b/cmd/libs/go2idl/openapi-gen/generators/openapi.go @@ -40,15 +40,30 @@ const tagOptional = "optional" // Known values for the tag. const ( - tagValueTrue = "true" - tagValueFalse = "false" - tagExtensionPrefix = "x-kubernetes-" + tagValueTrue = "true" + tagValueFalse = "false" + tagExtensionPrefix = "x-kubernetes-" + tagPatchStrategy = "patchStrategy" + tagPatchMergeKey = "patchMergeKey" + patchStrategyExtensionName = "patch-strategy" + patchMergeKeyExtensionName = "patch-merge-key" ) func getOpenAPITagValue(comments []string) []string { return types.ExtractCommentTags("+", comments)[tagName] } +func getSingleTagsValue(comments []string, tag string) (string, error) { + tags, ok := types.ExtractCommentTags("+", comments)[tag] + if !ok || len(tags) == 0 { + return "", nil + } + if len(tags) > 1 { + return "", fmt.Errorf("Multiple values are not allowed for tag %s", tag) + } + return tags[0], nil +} + func hasOpenAPITagValue(comments []string, value string) bool { tagValues := getOpenAPITagValue(comments) for _, val := range tagValues { @@ -235,6 +250,10 @@ func getJsonTags(m *types.Member) []string { return strings.Split(jsonTag, ",") } +func getPatchTags(m *types.Member) (string, string) { + return reflect.StructTag(m.Tags).Get(tagPatchMergeKey), reflect.StructTag(m.Tags).Get(tagPatchStrategy) +} + func getReferableName(m *types.Member) string { jsonTags := getJsonTags(m) if len(jsonTags) > 0 { @@ -308,7 +327,7 @@ func (g openAPITypeWriter) generateMembers(t *types.Type, required []string) ([] if !hasOptionalTag(&m) { required = append(required, name) } - if err = g.generateProperty(&m); err != nil { + if err = g.generateProperty(&m, t); err != nil { return required, err } } @@ -364,23 +383,63 @@ func (g openAPITypeWriter) generate(t *types.Type) error { func (g openAPITypeWriter) generateExtensions(CommentLines []string) error { tagValues := getOpenAPITagValue(CommentLines) - anyExtension := false + type NameValue struct { + Name, Value string + } + extensions := []NameValue{} for _, val := range tagValues { if strings.HasPrefix(val, tagExtensionPrefix) { - if !anyExtension { - g.Do("VendorExtensible: 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]) + extensions = append(extensions, NameValue{parts[0], parts[1]}) } } - if anyExtension { - g.Do("},\n},\n", nil) + patchMergeKeyTag, err := getSingleTagsValue(CommentLines, tagPatchMergeKey) + if err != nil { + return err + } + if len(patchMergeKeyTag) > 0 { + extensions = append(extensions, NameValue{tagExtensionPrefix + patchMergeKeyExtensionName, patchMergeKeyTag}) + } + patchStrategyTag, err := getSingleTagsValue(CommentLines, tagPatchStrategy) + if err != nil { + return err + } + if len(patchStrategyTag) > 0 { + extensions = append(extensions, NameValue{tagExtensionPrefix + patchStrategyExtensionName, patchStrategyTag}) + } + if len(extensions) == 0 { + return nil + } + g.Do("VendorExtensible: spec.VendorExtensible{\nExtensions: spec.Extensions{\n", nil) + for _, extension := range extensions { + g.Do("\"$.$\": ", extension.Name) + g.Do("\"$.$\",\n", extension.Value) + } + g.Do("},\n},\n", nil) + return nil +} + +// TODO(#44005): Move this validation outside of this generator (probably to policy verifier) +func (g openAPITypeWriter) validatePatchTags(m *types.Member, parent *types.Type) error { + patchMergeKeyStructTag, patchStrategyStructTag := getPatchTags(m) + patchMergeKeyCommentTag, err := getSingleTagsValue(m.CommentLines, tagPatchMergeKey) + if err != nil { + return err + } + patchStrategyCommentTag, err := getSingleTagsValue(m.CommentLines, tagPatchStrategy) + if err != nil { + return err + } + if patchMergeKeyStructTag != patchMergeKeyCommentTag { + return fmt.Errorf("patchMergeKey in comment and struct tags should match for member (%s) of (%s)", + m.Name, parent.Name.String()) + } + if patchStrategyStructTag != patchStrategyCommentTag { + return fmt.Errorf("patchStrategy in comment and struct tags should match for member (%s) of (%s)", + m.Name, parent.Name.String()) } return nil } @@ -428,11 +487,14 @@ func (g openAPITypeWriter) generateDescription(CommentLines []string) { } } -func (g openAPITypeWriter) generateProperty(m *types.Member) error { +func (g openAPITypeWriter) generateProperty(m *types.Member, parent *types.Type) error { name := getReferableName(m) if name == "" { return nil } + if err := g.validatePatchTags(m, parent); err != nil { + return err + } g.Do("\"$.$\": {\n", name) if err := g.generateExtensions(m.CommentLines); err != nil { return err diff --git a/cmd/libs/go2idl/openapi-gen/generators/openapi_test.go b/cmd/libs/go2idl/openapi-gen/generators/openapi_test.go index 6f06308fa3d..70e52ea1ac9 100644 --- a/cmd/libs/go2idl/openapi-gen/generators/openapi_test.go +++ b/cmd/libs/go2idl/openapi-gen/generators/openapi_test.go @@ -111,6 +111,10 @@ type Blah struct { // a member with an extension // +k8s:openapi-gen=x-kubernetes-member-tag:member_test WithExtension string + // a member with struct tag as extension + // +patchStrategy=ps + // +patchMergeKey=pmk + WithStructTagExtension string `+"`"+`patchStrategy:"ps" patchMergeKey:"pmk"`+"`"+` } `) if err != nil { @@ -238,8 +242,21 @@ Type: []string{"string"}, Format: "", }, }, +"WithStructTagExtension": { +VendorExtensible: spec.VendorExtensible{ +Extensions: spec.Extensions{ +"x-kubernetes-patch-merge-key": "pmk", +"x-kubernetes-patch-strategy": "ps", }, -Required: []string{"String","Int64","Int32","Int16","Int8","Uint","Uint64","Uint32","Uint16","Uint8","Byte","Bool","Float64","Float32","ByteArray","WithExtension"}, +}, +SchemaProps: spec.SchemaProps{ +Description: "a member with struct tag as extension", +Type: []string{"string"}, +Format: "", +}, +}, +}, +Required: []string{"String","Int64","Int32","Int16","Int8","Uint","Uint64","Uint32","Uint16","Uint8","Byte","Bool","Float64","Float32","ByteArray","WithExtension","WithStructTagExtension"}, }, VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{