Bumping k8s dependencies to 1.13
This commit is contained in:
@@ -4,8 +4,9 @@
|
||||
- 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
|
||||
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 used to pass more information to client generators or
|
||||
documentation generators. For example a type might have a friendly name to be displayed in documentation or
|
||||
@@ -17,6 +18,7 @@ Custom types which otherwise don't map directly to OpenAPI can override their
|
||||
OpenAPI definition by implementing a function named "OpenAPIDefinition" with
|
||||
the following signature:
|
||||
|
||||
```go
|
||||
import openapi "k8s.io/kube-openapi/pkg/common"
|
||||
|
||||
// ...
|
||||
@@ -35,12 +37,13 @@ the following signature:
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, the type can avoid the "openapi" import by defining the following
|
||||
methods. The following example produces the same OpenAPI definition as the
|
||||
example above:
|
||||
|
||||
```go
|
||||
func (_ Time) OpenAPISchemaType() []string { return []string{"string"} }
|
||||
func (_ Time) OpenAPISchemaFormat() string { return "date-time" }
|
||||
|
||||
TODO(mehdy): Make k8s:openapi-gen a parameter to the generator now that OpenAPI has its own repo.
|
||||
```
|
219
vendor/k8s.io/kube-openapi/pkg/generators/api_linter.go
generated
vendored
Normal file
219
vendor/k8s.io/kube-openapi/pkg/generators/api_linter.go
generated
vendored
Normal file
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
Copyright 2018 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"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"k8s.io/kube-openapi/pkg/generators/rules"
|
||||
|
||||
"k8s.io/gengo/generator"
|
||||
"k8s.io/gengo/types"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
const apiViolationFileType = "api-violation"
|
||||
|
||||
type apiViolationFile struct {
|
||||
// Since our file actually is unrelated to the package structure, use a
|
||||
// path that hasn't been mangled by the framework.
|
||||
unmangledPath string
|
||||
}
|
||||
|
||||
func (a apiViolationFile) AssembleFile(f *generator.File, path string) error {
|
||||
path = a.unmangledPath
|
||||
klog.V(2).Infof("Assembling file %q", path)
|
||||
if path == "-" {
|
||||
_, err := io.Copy(os.Stdout, &f.Body)
|
||||
return err
|
||||
}
|
||||
|
||||
output, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer output.Close()
|
||||
_, err = io.Copy(output, &f.Body)
|
||||
return err
|
||||
}
|
||||
|
||||
func (a apiViolationFile) VerifyFile(f *generator.File, path string) error {
|
||||
if path == "-" {
|
||||
// Nothing to verify against.
|
||||
return nil
|
||||
}
|
||||
path = a.unmangledPath
|
||||
|
||||
formatted := f.Body.Bytes()
|
||||
existing, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read file %q for comparison: %v", path, err)
|
||||
}
|
||||
if bytes.Compare(formatted, existing) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Be nice and find the first place where they differ
|
||||
// (Copied from gengo's default file type)
|
||||
i := 0
|
||||
for i < len(formatted) && i < len(existing) && formatted[i] == existing[i] {
|
||||
i++
|
||||
}
|
||||
eDiff, fDiff := existing[i:], formatted[i:]
|
||||
if len(eDiff) > 100 {
|
||||
eDiff = eDiff[:100]
|
||||
}
|
||||
if len(fDiff) > 100 {
|
||||
fDiff = fDiff[:100]
|
||||
}
|
||||
return fmt.Errorf("output for %q differs; first existing/expected diff: \n %q\n %q", path, string(eDiff), string(fDiff))
|
||||
}
|
||||
|
||||
func newAPIViolationGen() *apiViolationGen {
|
||||
return &apiViolationGen{
|
||||
linter: newAPILinter(),
|
||||
}
|
||||
}
|
||||
|
||||
type apiViolationGen struct {
|
||||
generator.DefaultGen
|
||||
|
||||
linter *apiLinter
|
||||
}
|
||||
|
||||
func (v *apiViolationGen) FileType() string { return apiViolationFileType }
|
||||
func (v *apiViolationGen) Filename() string {
|
||||
return "this file is ignored by the file assembler"
|
||||
}
|
||||
|
||||
func (v *apiViolationGen) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
|
||||
klog.V(5).Infof("validating API rules for type %v", t)
|
||||
if err := v.linter.validate(t); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Finalize prints the API rule violations to report file (if specified from
|
||||
// arguments) or stdout (default)
|
||||
func (v *apiViolationGen) Finalize(c *generator.Context, w io.Writer) error {
|
||||
// NOTE: we don't return error here because we assume that the report file will
|
||||
// get evaluated afterwards to determine if error should be raised. For example,
|
||||
// you can have make rules that compare the report file with existing known
|
||||
// violations (whitelist) and determine no error if no change is detected.
|
||||
v.linter.report(w)
|
||||
return nil
|
||||
}
|
||||
|
||||
// apiLinter is the framework hosting multiple API rules and recording API rule
|
||||
// violations
|
||||
type apiLinter struct {
|
||||
// API rules that implement APIRule interface and output API rule violations
|
||||
rules []APIRule
|
||||
violations []apiViolation
|
||||
}
|
||||
|
||||
// newAPILinter creates an apiLinter object with API rules in package rules. Please
|
||||
// add APIRule here when new API rule is implemented.
|
||||
func newAPILinter() *apiLinter {
|
||||
return &apiLinter{
|
||||
rules: []APIRule{
|
||||
&rules.NamesMatch{},
|
||||
&rules.OmitEmptyMatchCase{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// apiViolation uniquely identifies single API rule violation
|
||||
type apiViolation struct {
|
||||
// Name of rule from APIRule.Name()
|
||||
rule string
|
||||
|
||||
packageName string
|
||||
typeName string
|
||||
|
||||
// Optional: name of field that violates API rule. Empty fieldName implies that
|
||||
// the entire type violates the rule.
|
||||
field string
|
||||
}
|
||||
|
||||
// apiViolations implements sort.Interface for []apiViolation based on the fields: rule,
|
||||
// packageName, typeName and field.
|
||||
type apiViolations []apiViolation
|
||||
|
||||
func (a apiViolations) Len() int { return len(a) }
|
||||
func (a apiViolations) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a apiViolations) Less(i, j int) bool {
|
||||
if a[i].rule != a[j].rule {
|
||||
return a[i].rule < a[j].rule
|
||||
}
|
||||
if a[i].packageName != a[j].packageName {
|
||||
return a[i].packageName < a[j].packageName
|
||||
}
|
||||
if a[i].typeName != a[j].typeName {
|
||||
return a[i].typeName < a[j].typeName
|
||||
}
|
||||
return a[i].field < a[j].field
|
||||
}
|
||||
|
||||
// APIRule is the interface for validating API rule on Go types
|
||||
type APIRule interface {
|
||||
// Validate evaluates API rule on type t and returns a list of field names in
|
||||
// the type that violate the rule. Empty field name [""] implies the entire
|
||||
// type violates the rule.
|
||||
Validate(t *types.Type) ([]string, error)
|
||||
|
||||
// Name returns the name of APIRule
|
||||
Name() string
|
||||
}
|
||||
|
||||
// validate runs all API rules on type t and records any API rule violation
|
||||
func (l *apiLinter) validate(t *types.Type) error {
|
||||
for _, r := range l.rules {
|
||||
klog.V(5).Infof("validating API rule %v for type %v", r.Name(), t)
|
||||
fields, err := r.Validate(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, field := range fields {
|
||||
l.violations = append(l.violations, apiViolation{
|
||||
rule: r.Name(),
|
||||
packageName: t.Name.Package,
|
||||
typeName: t.Name.Name,
|
||||
field: field,
|
||||
})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// report prints any API rule violation to writer w and returns error if violation exists
|
||||
func (l *apiLinter) report(w io.Writer) error {
|
||||
sort.Sort(apiViolations(l.violations))
|
||||
for _, v := range l.violations {
|
||||
fmt.Fprintf(w, "API rule violation: %s,%s,%s,%s\n", v.rule, v.packageName, v.typeName, v.field)
|
||||
}
|
||||
if len(l.violations) > 0 {
|
||||
return fmt.Errorf("API rule violations exist")
|
||||
}
|
||||
return nil
|
||||
}
|
91
vendor/k8s.io/kube-openapi/pkg/generators/config.go
generated
vendored
Normal file
91
vendor/k8s.io/kube-openapi/pkg/generators/config.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
Copyright 2018 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 (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"k8s.io/gengo/args"
|
||||
"k8s.io/gengo/generator"
|
||||
"k8s.io/gengo/namer"
|
||||
"k8s.io/gengo/types"
|
||||
"k8s.io/klog"
|
||||
|
||||
generatorargs "k8s.io/kube-openapi/cmd/openapi-gen/args"
|
||||
)
|
||||
|
||||
type identityNamer struct{}
|
||||
|
||||
func (_ identityNamer) Name(t *types.Type) string {
|
||||
return t.Name.String()
|
||||
}
|
||||
|
||||
var _ namer.Namer = identityNamer{}
|
||||
|
||||
// NameSystems returns the name system used by the generators in this package.
|
||||
func NameSystems() namer.NameSystems {
|
||||
return namer.NameSystems{
|
||||
"raw": namer.NewRawNamer("", nil),
|
||||
"sorting_namer": identityNamer{},
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultNameSystem returns the default name system for ordering the types to be
|
||||
// processed by the generators in this package.
|
||||
func DefaultNameSystem() string {
|
||||
return "sorting_namer"
|
||||
}
|
||||
|
||||
func Packages(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages {
|
||||
boilerplate, err := arguments.LoadGoBoilerplate()
|
||||
if err != nil {
|
||||
klog.Fatalf("Failed loading boilerplate: %v", err)
|
||||
}
|
||||
header := append([]byte(fmt.Sprintf("// +build !%s\n\n", arguments.GeneratedBuildTag)), boilerplate...)
|
||||
header = append(header, []byte(
|
||||
`
|
||||
// This file was autogenerated by openapi-gen. Do not edit it manually!
|
||||
|
||||
`)...)
|
||||
|
||||
reportPath := "-"
|
||||
if customArgs, ok := arguments.CustomArgs.(*generatorargs.CustomArgs); ok {
|
||||
reportPath = customArgs.ReportFilename
|
||||
}
|
||||
context.FileTypes[apiViolationFileType] = apiViolationFile{
|
||||
unmangledPath: reportPath,
|
||||
}
|
||||
|
||||
return generator.Packages{
|
||||
&generator.DefaultPackage{
|
||||
PackageName: filepath.Base(arguments.OutputPackagePath),
|
||||
PackagePath: arguments.OutputPackagePath,
|
||||
HeaderText: header,
|
||||
GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
|
||||
return []generator.Generator{
|
||||
newOpenAPIGen(
|
||||
arguments.OutputFileBaseName,
|
||||
arguments.OutputPackagePath,
|
||||
),
|
||||
newAPIViolationGen(),
|
||||
}
|
||||
},
|
||||
FilterFunc: apiTypeFilterFunc,
|
||||
},
|
||||
}
|
||||
}
|
182
vendor/k8s.io/kube-openapi/pkg/generators/extension.go
generated
vendored
Normal file
182
vendor/k8s.io/kube-openapi/pkg/generators/extension.go
generated
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
Copyright 2018 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 (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"k8s.io/gengo/examples/set-gen/sets"
|
||||
"k8s.io/gengo/types"
|
||||
)
|
||||
|
||||
const extensionPrefix = "x-kubernetes-"
|
||||
|
||||
// extensionAttributes encapsulates common traits for particular extensions.
|
||||
type extensionAttributes struct {
|
||||
xName string
|
||||
kind types.Kind
|
||||
allowedValues sets.String
|
||||
}
|
||||
|
||||
// Extension tag to openapi extension attributes
|
||||
var tagToExtension = map[string]extensionAttributes{
|
||||
"patchMergeKey": {
|
||||
xName: "x-kubernetes-patch-merge-key",
|
||||
kind: types.Slice,
|
||||
},
|
||||
"patchStrategy": {
|
||||
xName: "x-kubernetes-patch-strategy",
|
||||
kind: types.Slice,
|
||||
allowedValues: sets.NewString("merge", "retainKeys"),
|
||||
},
|
||||
"listMapKey": {
|
||||
xName: "x-kubernetes-list-map-keys",
|
||||
kind: types.Slice,
|
||||
},
|
||||
"listType": {
|
||||
xName: "x-kubernetes-list-type",
|
||||
kind: types.Slice,
|
||||
allowedValues: sets.NewString("atomic", "set", "map"),
|
||||
},
|
||||
}
|
||||
|
||||
// Extension encapsulates information necessary to generate an OpenAPI extension.
|
||||
type extension struct {
|
||||
idlTag string // Example: listType
|
||||
xName string // Example: x-kubernetes-list-type
|
||||
values []string // Example: [atomic]
|
||||
}
|
||||
|
||||
func (e extension) hasAllowedValues() bool {
|
||||
return tagToExtension[e.idlTag].allowedValues.Len() > 0
|
||||
}
|
||||
|
||||
func (e extension) allowedValues() sets.String {
|
||||
return tagToExtension[e.idlTag].allowedValues
|
||||
}
|
||||
|
||||
func (e extension) hasKind() bool {
|
||||
return len(tagToExtension[e.idlTag].kind) > 0
|
||||
}
|
||||
|
||||
func (e extension) kind() types.Kind {
|
||||
return tagToExtension[e.idlTag].kind
|
||||
}
|
||||
|
||||
func (e extension) validateAllowedValues() error {
|
||||
// allowedValues not set means no restrictions on values.
|
||||
if !e.hasAllowedValues() {
|
||||
return nil
|
||||
}
|
||||
// Check for missing value.
|
||||
if len(e.values) == 0 {
|
||||
return fmt.Errorf("%s needs a value, none given.", e.idlTag)
|
||||
}
|
||||
// For each extension value, validate that it is allowed.
|
||||
allowedValues := e.allowedValues()
|
||||
if !allowedValues.HasAll(e.values...) {
|
||||
return fmt.Errorf("%v not allowed for %s. Allowed values: %v",
|
||||
e.values, e.idlTag, allowedValues.List())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e extension) validateType(kind types.Kind) error {
|
||||
// If this extension class has no kind, then don't validate the type.
|
||||
if !e.hasKind() {
|
||||
return nil
|
||||
}
|
||||
if kind != e.kind() {
|
||||
return fmt.Errorf("tag %s on type %v; only allowed on type %v",
|
||||
e.idlTag, kind, e.kind())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e extension) hasMultipleValues() bool {
|
||||
return len(e.values) > 1
|
||||
}
|
||||
|
||||
// Returns sorted list of map keys. Needed for deterministic testing.
|
||||
func sortedMapKeys(m map[string][]string) []string {
|
||||
keys := make([]string, len(m))
|
||||
i := 0
|
||||
for k := range m {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
// Parses comments to return openapi extensions. Returns a list of
|
||||
// extensions which parsed correctly, as well as a list of the
|
||||
// parse errors. Validating extensions is performed separately.
|
||||
// NOTE: Non-empty errors does not mean extensions is empty.
|
||||
func parseExtensions(comments []string) ([]extension, []error) {
|
||||
extensions := []extension{}
|
||||
errors := []error{}
|
||||
// First, generate extensions from "+k8s:openapi-gen=x-kubernetes-*" annotations.
|
||||
values := getOpenAPITagValue(comments)
|
||||
for _, val := range values {
|
||||
// Example: x-kubernetes-member-tag:member_test
|
||||
if strings.HasPrefix(val, extensionPrefix) {
|
||||
parts := strings.SplitN(val, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
errors = append(errors, fmt.Errorf("invalid extension value: %v", val))
|
||||
continue
|
||||
}
|
||||
e := extension{
|
||||
idlTag: tagName, // Example: k8s:openapi-gen
|
||||
xName: parts[0], // Example: x-kubernetes-member-tag
|
||||
values: []string{parts[1]}, // Example: member_test
|
||||
}
|
||||
extensions = append(extensions, e)
|
||||
}
|
||||
}
|
||||
// Next, generate extensions from "idlTags" (e.g. +listType)
|
||||
tagValues := types.ExtractCommentTags("+", comments)
|
||||
for _, idlTag := range sortedMapKeys(tagValues) {
|
||||
xAttrs, exists := tagToExtension[idlTag]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
values := tagValues[idlTag]
|
||||
e := extension{
|
||||
idlTag: idlTag, // listType
|
||||
xName: xAttrs.xName, // x-kubernetes-list-type
|
||||
values: values, // [atomic]
|
||||
}
|
||||
extensions = append(extensions, e)
|
||||
}
|
||||
return extensions, errors
|
||||
}
|
||||
|
||||
func validateMemberExtensions(extensions []extension, m *types.Member) []error {
|
||||
errors := []error{}
|
||||
for _, e := range extensions {
|
||||
if err := e.validateAllowedValues(); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
if err := e.validateType(m.Type.Kind); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
return errors
|
||||
}
|
454
vendor/k8s.io/kube-openapi/pkg/generators/extension_test.go
generated
vendored
Normal file
454
vendor/k8s.io/kube-openapi/pkg/generators/extension_test.go
generated
vendored
Normal file
@@ -0,0 +1,454 @@
|
||||
/*
|
||||
Copyright 2018 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 (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/gengo/examples/set-gen/sets"
|
||||
"k8s.io/gengo/types"
|
||||
)
|
||||
|
||||
func TestSingleTagExtension(t *testing.T) {
|
||||
|
||||
// Comments only contain one tag extension and one value.
|
||||
var tests = []struct {
|
||||
comments []string
|
||||
extensionTag string
|
||||
extensionName string
|
||||
extensionValues []string
|
||||
}{
|
||||
{
|
||||
comments: []string{"+patchMergeKey=name"},
|
||||
extensionTag: "patchMergeKey",
|
||||
extensionName: "x-kubernetes-patch-merge-key",
|
||||
extensionValues: []string{"name"},
|
||||
},
|
||||
{
|
||||
comments: []string{"+patchStrategy=merge"},
|
||||
extensionTag: "patchStrategy",
|
||||
extensionName: "x-kubernetes-patch-strategy",
|
||||
extensionValues: []string{"merge"},
|
||||
},
|
||||
{
|
||||
comments: []string{"+listType=atomic"},
|
||||
extensionTag: "listType",
|
||||
extensionName: "x-kubernetes-list-type",
|
||||
extensionValues: []string{"atomic"},
|
||||
},
|
||||
{
|
||||
comments: []string{"+listMapKey=port"},
|
||||
extensionTag: "listMapKey",
|
||||
extensionName: "x-kubernetes-list-map-keys",
|
||||
extensionValues: []string{"port"},
|
||||
},
|
||||
{
|
||||
comments: []string{"+k8s:openapi-gen=x-kubernetes-member-tag:member_test"},
|
||||
extensionTag: "k8s:openapi-gen",
|
||||
extensionName: "x-kubernetes-member-tag",
|
||||
extensionValues: []string{"member_test"},
|
||||
},
|
||||
{
|
||||
comments: []string{"+k8s:openapi-gen=x-kubernetes-member-tag:member_test:member_test2"},
|
||||
extensionTag: "k8s:openapi-gen",
|
||||
extensionName: "x-kubernetes-member-tag",
|
||||
extensionValues: []string{"member_test:member_test2"},
|
||||
},
|
||||
{
|
||||
// Test that poorly formatted extensions aren't added.
|
||||
comments: []string{
|
||||
"+k8s:openapi-gen=x-kubernetes-no-value",
|
||||
"+k8s:openapi-gen=x-kubernetes-member-success:success",
|
||||
"+k8s:openapi-gen=x-kubernetes-wrong-separator;error",
|
||||
},
|
||||
extensionTag: "k8s:openapi-gen",
|
||||
extensionName: "x-kubernetes-member-success",
|
||||
extensionValues: []string{"success"},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
extensions, _ := parseExtensions(test.comments)
|
||||
actual := extensions[0]
|
||||
if actual.idlTag != test.extensionTag {
|
||||
t.Errorf("Extension Tag: expected (%s), actual (%s)\n", test.extensionTag, actual.idlTag)
|
||||
}
|
||||
if actual.xName != test.extensionName {
|
||||
t.Errorf("Extension Name: expected (%s), actual (%s)\n", test.extensionName, actual.xName)
|
||||
}
|
||||
if !reflect.DeepEqual(actual.values, test.extensionValues) {
|
||||
t.Errorf("Extension Values: expected (%s), actual (%s)\n", test.extensionValues, actual.values)
|
||||
}
|
||||
if actual.hasMultipleValues() {
|
||||
t.Errorf("%s: hasMultipleValues() should be false\n", actual.xName)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMultipleTagExtensions(t *testing.T) {
|
||||
|
||||
var tests = []struct {
|
||||
comments []string
|
||||
extensionTag string
|
||||
extensionName string
|
||||
extensionValues []string
|
||||
}{
|
||||
{
|
||||
comments: []string{
|
||||
"+listMapKey=port",
|
||||
"+listMapKey=protocol",
|
||||
},
|
||||
extensionTag: "listMapKey",
|
||||
extensionName: "x-kubernetes-list-map-keys",
|
||||
extensionValues: []string{"port", "protocol"},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
extensions, errors := parseExtensions(test.comments)
|
||||
if len(errors) > 0 {
|
||||
t.Errorf("Unexpected errors: %v\n", errors)
|
||||
}
|
||||
actual := extensions[0]
|
||||
if actual.idlTag != test.extensionTag {
|
||||
t.Errorf("Extension Tag: expected (%s), actual (%s)\n", test.extensionTag, actual.idlTag)
|
||||
}
|
||||
if actual.xName != test.extensionName {
|
||||
t.Errorf("Extension Name: expected (%s), actual (%s)\n", test.extensionName, actual.xName)
|
||||
}
|
||||
if !reflect.DeepEqual(actual.values, test.extensionValues) {
|
||||
t.Errorf("Extension Values: expected (%s), actual (%s)\n", test.extensionValues, actual.values)
|
||||
}
|
||||
if !actual.hasMultipleValues() {
|
||||
t.Errorf("%s: hasMultipleValues() should be true\n", actual.xName)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestExtensionParseErrors(t *testing.T) {
|
||||
|
||||
var tests = []struct {
|
||||
comments []string
|
||||
errorMessage string
|
||||
}{
|
||||
{
|
||||
// Missing extension value should be an error.
|
||||
comments: []string{
|
||||
"+k8s:openapi-gen=x-kubernetes-no-value",
|
||||
},
|
||||
errorMessage: "x-kubernetes-no-value",
|
||||
},
|
||||
{
|
||||
// Wrong separator should be an error.
|
||||
comments: []string{
|
||||
"+k8s:openapi-gen=x-kubernetes-wrong-separator;error",
|
||||
},
|
||||
errorMessage: "x-kubernetes-wrong-separator;error",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
_, errors := parseExtensions(test.comments)
|
||||
if len(errors) == 0 {
|
||||
t.Errorf("Expected errors while parsing: %v\n", test.comments)
|
||||
}
|
||||
error := errors[0]
|
||||
if !strings.Contains(error.Error(), test.errorMessage) {
|
||||
t.Errorf("Error (%v) should contain substring (%s)\n", error, test.errorMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtensionAllowedValues(t *testing.T) {
|
||||
|
||||
var methodTests = []struct {
|
||||
e extension
|
||||
allowedValues sets.String
|
||||
}{
|
||||
{
|
||||
e: extension{
|
||||
idlTag: "patchStrategy",
|
||||
},
|
||||
allowedValues: sets.NewString("merge", "retainKeys"),
|
||||
},
|
||||
{
|
||||
e: extension{
|
||||
idlTag: "patchMergeKey",
|
||||
},
|
||||
allowedValues: nil,
|
||||
},
|
||||
{
|
||||
e: extension{
|
||||
idlTag: "listType",
|
||||
},
|
||||
allowedValues: sets.NewString("atomic", "set", "map"),
|
||||
},
|
||||
{
|
||||
e: extension{
|
||||
idlTag: "listMapKey",
|
||||
},
|
||||
allowedValues: nil,
|
||||
},
|
||||
{
|
||||
e: extension{
|
||||
idlTag: "k8s:openapi-gen",
|
||||
},
|
||||
allowedValues: nil,
|
||||
},
|
||||
}
|
||||
for _, test := range methodTests {
|
||||
if test.allowedValues != nil {
|
||||
if !test.e.hasAllowedValues() {
|
||||
t.Errorf("hasAllowedValues() expected (true), but received: false")
|
||||
}
|
||||
if !reflect.DeepEqual(test.allowedValues, test.e.allowedValues()) {
|
||||
t.Errorf("allowedValues() expected (%v), but received: %v",
|
||||
test.allowedValues, test.e.allowedValues())
|
||||
}
|
||||
}
|
||||
if test.allowedValues == nil && test.e.hasAllowedValues() {
|
||||
t.Errorf("hasAllowedValues() expected (false), but received: true")
|
||||
}
|
||||
}
|
||||
|
||||
var successTests = []struct {
|
||||
e extension
|
||||
}{
|
||||
{
|
||||
e: extension{
|
||||
idlTag: "patchStrategy",
|
||||
xName: "x-kubernetes-patch-strategy",
|
||||
values: []string{"merge"},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Validate multiple values.
|
||||
e: extension{
|
||||
idlTag: "patchStrategy",
|
||||
xName: "x-kubernetes-patch-strategy",
|
||||
values: []string{"merge", "retainKeys"},
|
||||
},
|
||||
},
|
||||
{
|
||||
e: extension{
|
||||
idlTag: "patchMergeKey",
|
||||
xName: "x-kubernetes-patch-merge-key",
|
||||
values: []string{"key1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
e: extension{
|
||||
idlTag: "listType",
|
||||
xName: "x-kubernetes-list-type",
|
||||
values: []string{"atomic"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range successTests {
|
||||
actualErr := test.e.validateAllowedValues()
|
||||
if actualErr != nil {
|
||||
t.Errorf("Expected no error for (%v), but received: %v\n", test.e, actualErr)
|
||||
}
|
||||
}
|
||||
|
||||
var failureTests = []struct {
|
||||
e extension
|
||||
}{
|
||||
{
|
||||
// Every value must be allowed.
|
||||
e: extension{
|
||||
idlTag: "patchStrategy",
|
||||
xName: "x-kubernetes-patch-strategy",
|
||||
values: []string{"disallowed", "merge"},
|
||||
},
|
||||
},
|
||||
{
|
||||
e: extension{
|
||||
idlTag: "patchStrategy",
|
||||
xName: "x-kubernetes-patch-strategy",
|
||||
values: []string{"foo"},
|
||||
},
|
||||
},
|
||||
{
|
||||
e: extension{
|
||||
idlTag: "listType",
|
||||
xName: "x-kubernetes-list-type",
|
||||
values: []string{"not-allowed"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range failureTests {
|
||||
actualErr := test.e.validateAllowedValues()
|
||||
if actualErr == nil {
|
||||
t.Errorf("Expected error, but received none: %v\n", test.e)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestExtensionKind(t *testing.T) {
|
||||
|
||||
var methodTests = []struct {
|
||||
e extension
|
||||
kind types.Kind
|
||||
}{
|
||||
{
|
||||
e: extension{
|
||||
idlTag: "patchStrategy",
|
||||
},
|
||||
kind: types.Slice,
|
||||
},
|
||||
{
|
||||
e: extension{
|
||||
idlTag: "patchMergeKey",
|
||||
},
|
||||
kind: types.Slice,
|
||||
},
|
||||
{
|
||||
e: extension{
|
||||
idlTag: "listType",
|
||||
},
|
||||
kind: types.Slice,
|
||||
},
|
||||
{
|
||||
e: extension{
|
||||
idlTag: "listMapKey",
|
||||
},
|
||||
kind: types.Slice,
|
||||
},
|
||||
{
|
||||
e: extension{
|
||||
idlTag: "k8s:openapi-gen",
|
||||
},
|
||||
kind: "",
|
||||
},
|
||||
}
|
||||
for _, test := range methodTests {
|
||||
if len(test.kind) > 0 {
|
||||
if !test.e.hasKind() {
|
||||
t.Errorf("%v: hasKind() expected (true), but received: false", test.e)
|
||||
}
|
||||
if test.kind != test.e.kind() {
|
||||
t.Errorf("%v: kind() expected (%v), but received: %v", test.e, test.kind, test.e.kind())
|
||||
}
|
||||
} else {
|
||||
if test.e.hasKind() {
|
||||
t.Errorf("%v: hasKind() expected (false), but received: true", test.e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateMemberExtensions(t *testing.T) {
|
||||
|
||||
patchStrategyExtension := extension{
|
||||
idlTag: "patchStrategy",
|
||||
xName: "x-kubernetes-patch-strategy",
|
||||
values: []string{"merge"},
|
||||
}
|
||||
patchMergeKeyExtension := extension{
|
||||
idlTag: "patchMergeKey",
|
||||
xName: "x-kubernetes-patch-merge-key",
|
||||
values: []string{"key1", "key2"},
|
||||
}
|
||||
listTypeExtension := extension{
|
||||
idlTag: "listType",
|
||||
xName: "x-kubernetes-list-type",
|
||||
values: []string{"atomic"},
|
||||
}
|
||||
listMapKeysExtension := extension{
|
||||
idlTag: "listMapKey",
|
||||
xName: "x-kubernetes-map-keys",
|
||||
values: []string{"key1"},
|
||||
}
|
||||
genExtension := extension{
|
||||
idlTag: "k8s:openapi-gen",
|
||||
xName: "x-kubernetes-member-type",
|
||||
values: []string{"value1"},
|
||||
}
|
||||
|
||||
sliceField := types.Member{
|
||||
Name: "Containers",
|
||||
Type: &types.Type{
|
||||
Kind: types.Slice,
|
||||
},
|
||||
}
|
||||
mapField := types.Member{
|
||||
Name: "Containers",
|
||||
Type: &types.Type{
|
||||
Kind: types.Map,
|
||||
},
|
||||
}
|
||||
|
||||
var successTests = []struct {
|
||||
extensions []extension
|
||||
member types.Member
|
||||
}{
|
||||
// Test single member extension
|
||||
{
|
||||
extensions: []extension{patchStrategyExtension},
|
||||
member: sliceField,
|
||||
},
|
||||
// Test multiple member extensions
|
||||
{
|
||||
extensions: []extension{
|
||||
patchMergeKeyExtension,
|
||||
listTypeExtension,
|
||||
listMapKeysExtension,
|
||||
genExtension, // Should not generate errors during type validation
|
||||
},
|
||||
member: sliceField,
|
||||
},
|
||||
}
|
||||
for _, test := range successTests {
|
||||
errors := validateMemberExtensions(test.extensions, &test.member)
|
||||
if len(errors) > 0 {
|
||||
t.Errorf("validateMemberExtensions: %v should have produced no errors. Errors: %v",
|
||||
test.extensions, errors)
|
||||
}
|
||||
}
|
||||
|
||||
var failureTests = []struct {
|
||||
extensions []extension
|
||||
member types.Member
|
||||
}{
|
||||
// Test single member extension
|
||||
{
|
||||
extensions: []extension{patchStrategyExtension},
|
||||
member: mapField,
|
||||
},
|
||||
// Test multiple member extensions
|
||||
{
|
||||
extensions: []extension{
|
||||
patchMergeKeyExtension,
|
||||
listTypeExtension,
|
||||
listMapKeysExtension,
|
||||
},
|
||||
member: mapField,
|
||||
},
|
||||
}
|
||||
for _, test := range failureTests {
|
||||
errors := validateMemberExtensions(test.extensions, &test.member)
|
||||
if len(errors) != len(test.extensions) {
|
||||
t.Errorf("validateMemberExtensions: %v should have produced all errors. Errors: %v",
|
||||
test.extensions, errors)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
267
vendor/k8s.io/kube-openapi/pkg/generators/openapi.go
generated
vendored
267
vendor/k8s.io/kube-openapi/pkg/generators/openapi.go
generated
vendored
@@ -25,13 +25,12 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"k8s.io/gengo/args"
|
||||
"k8s.io/gengo/generator"
|
||||
"k8s.io/gengo/namer"
|
||||
"k8s.io/gengo/types"
|
||||
openapi "k8s.io/kube-openapi/pkg/common"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
// This is the comment tag that carries parameters for open API generation.
|
||||
@@ -40,15 +39,17 @@ const tagOptional = "optional"
|
||||
|
||||
// Known values for the tag.
|
||||
const (
|
||||
tagValueTrue = "true"
|
||||
tagValueFalse = "false"
|
||||
tagExtensionPrefix = "x-kubernetes-"
|
||||
tagPatchStrategy = "patchStrategy"
|
||||
tagPatchMergeKey = "patchMergeKey"
|
||||
patchStrategyExtensionName = "patch-strategy"
|
||||
patchMergeKeyExtensionName = "patch-merge-key"
|
||||
tagValueTrue = "true"
|
||||
tagValueFalse = "false"
|
||||
)
|
||||
|
||||
// Used for temporary validation of patch struct tags.
|
||||
// TODO: Remove patch struct tag validation because they we are now consuming OpenAPI on server.
|
||||
var tempPatchTags = [...]string{
|
||||
"patchMergeKey",
|
||||
"patchStrategy",
|
||||
}
|
||||
|
||||
func getOpenAPITagValue(comments []string) []string {
|
||||
return types.ExtractCommentTags("+", comments)[tagName]
|
||||
}
|
||||
@@ -84,64 +85,19 @@ func hasOptionalTag(m *types.Member) bool {
|
||||
return hasOptionalCommentTag || hasOptionalJsonTag
|
||||
}
|
||||
|
||||
type identityNamer struct{}
|
||||
|
||||
func (_ identityNamer) Name(t *types.Type) string {
|
||||
return t.Name.String()
|
||||
}
|
||||
|
||||
var _ namer.Namer = identityNamer{}
|
||||
|
||||
// NameSystems returns the name system used by the generators in this package.
|
||||
func NameSystems() namer.NameSystems {
|
||||
return namer.NameSystems{
|
||||
"raw": namer.NewRawNamer("", nil),
|
||||
"sorting_namer": identityNamer{},
|
||||
func apiTypeFilterFunc(c *generator.Context, t *types.Type) bool {
|
||||
// There is a conflict between this codegen and codecgen, we should avoid types generated for codecgen
|
||||
if strings.HasPrefix(t.Name.Name, "codecSelfer") {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultNameSystem returns the default name system for ordering the types to be
|
||||
// processed by the generators in this package.
|
||||
func DefaultNameSystem() string {
|
||||
return "sorting_namer"
|
||||
}
|
||||
|
||||
func Packages(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages {
|
||||
boilerplate, err := arguments.LoadGoBoilerplate()
|
||||
if err != nil {
|
||||
glog.Fatalf("Failed loading boilerplate: %v", err)
|
||||
pkg := c.Universe.Package(t.Name.Package)
|
||||
if hasOpenAPITagValue(pkg.Comments, tagValueTrue) {
|
||||
return !hasOpenAPITagValue(t.CommentLines, tagValueFalse)
|
||||
}
|
||||
header := append([]byte(fmt.Sprintf("// +build !%s\n\n", arguments.GeneratedBuildTag)), boilerplate...)
|
||||
header = append(header, []byte(
|
||||
`
|
||||
// This file was autogenerated by openapi-gen. Do not edit it manually!
|
||||
|
||||
`)...)
|
||||
|
||||
return generator.Packages{
|
||||
&generator.DefaultPackage{
|
||||
PackageName: filepath.Base(arguments.OutputPackagePath),
|
||||
PackagePath: arguments.OutputPackagePath,
|
||||
HeaderText: header,
|
||||
GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
|
||||
return []generator.Generator{NewOpenAPIGen(arguments.OutputFileBaseName, arguments.OutputPackagePath, context)}
|
||||
},
|
||||
FilterFunc: func(c *generator.Context, t *types.Type) bool {
|
||||
// There is a conflict between this codegen and codecgen, we should avoid types generated for codecgen
|
||||
if strings.HasPrefix(t.Name.Name, "codecSelfer") {
|
||||
return false
|
||||
}
|
||||
pkg := context.Universe.Package(t.Name.Package)
|
||||
if hasOpenAPITagValue(pkg.Comments, tagValueTrue) {
|
||||
return !hasOpenAPITagValue(t.CommentLines, tagValueFalse)
|
||||
}
|
||||
if hasOpenAPITagValue(t.CommentLines, tagValueTrue) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
},
|
||||
if hasOpenAPITagValue(t.CommentLines, tagValueTrue) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -155,35 +111,33 @@ type openAPIGen struct {
|
||||
// TargetPackage is the package that will get GetOpenAPIDefinitions function returns all open API definitions.
|
||||
targetPackage string
|
||||
imports namer.ImportTracker
|
||||
context *generator.Context
|
||||
}
|
||||
|
||||
func NewOpenAPIGen(sanitizedName string, targetPackage string, context *generator.Context) generator.Generator {
|
||||
func newOpenAPIGen(sanitizedName string, targetPackage string) generator.Generator {
|
||||
return &openAPIGen{
|
||||
DefaultGen: generator.DefaultGen{
|
||||
OptionalName: sanitizedName,
|
||||
},
|
||||
imports: generator.NewImportTracker(),
|
||||
targetPackage: targetPackage,
|
||||
context: context,
|
||||
}
|
||||
}
|
||||
|
||||
const nameTmpl = "schema_$.type|private$"
|
||||
|
||||
func (g *openAPIGen) Namers(c *generator.Context) namer.NameSystems {
|
||||
// Have the raw namer for this file track what it imports.
|
||||
return namer.NameSystems{
|
||||
"raw": namer.NewRawNamer(g.targetPackage, g.imports),
|
||||
"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/...
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (g *openAPIGen) Filter(c *generator.Context, t *types.Type) bool {
|
||||
// There is a conflict between this codegen and codecgen, we should avoid types generated for codecgen
|
||||
if strings.HasPrefix(t.Name.Name, "codecSelfer") {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *openAPIGen) isOtherPackage(pkg string) bool {
|
||||
if pkg == g.targetPackage {
|
||||
return false
|
||||
@@ -215,18 +169,22 @@ func (g *openAPIGen) Init(c *generator.Context, w io.Writer) error {
|
||||
sw := generator.NewSnippetWriter(w, c, "$", "$")
|
||||
sw.Do("func GetOpenAPIDefinitions(ref $.ReferenceCallback|raw$) map[string]$.OpenAPIDefinition|raw$ {\n", argsFromType(nil))
|
||||
sw.Do("return map[string]$.OpenAPIDefinition|raw${\n", argsFromType(nil))
|
||||
return sw.Error()
|
||||
}
|
||||
|
||||
func (g *openAPIGen) Finalize(c *generator.Context, w io.Writer) error {
|
||||
sw := generator.NewSnippetWriter(w, c, "$", "$")
|
||||
sw.Do("}\n", nil)
|
||||
for _, t := range c.Order {
|
||||
err := newOpenAPITypeWriter(sw).generateCall(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
sw.Do("}\n", nil)
|
||||
sw.Do("}\n\n", nil)
|
||||
|
||||
return sw.Error()
|
||||
}
|
||||
|
||||
func (g *openAPIGen) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
|
||||
glog.V(5).Infof("generating for type %v", t)
|
||||
klog.V(5).Infof("generating for type %v", t)
|
||||
sw := generator.NewSnippetWriter(w, c, "$", "$")
|
||||
err := newOpenAPITypeWriter(sw).generate(t)
|
||||
if err != nil {
|
||||
@@ -243,10 +201,6 @@ 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 {
|
||||
@@ -335,14 +289,14 @@ func (g openAPITypeWriter) generateMembers(t *types.Type, required []string) ([]
|
||||
required = append(required, name)
|
||||
}
|
||||
if err = g.generateProperty(&m, t); err != nil {
|
||||
glog.Errorf("Error when generating: %v, %v\n", name, m)
|
||||
klog.Errorf("Error when generating: %v, %v\n", name, m)
|
||||
return required, err
|
||||
}
|
||||
}
|
||||
return required, nil
|
||||
}
|
||||
|
||||
func (g openAPITypeWriter) generate(t *types.Type) error {
|
||||
func (g openAPITypeWriter) generateCall(t *types.Type) error {
|
||||
// Only generate for struct type and ignore the rest
|
||||
switch t.Kind {
|
||||
case types.Struct:
|
||||
@@ -350,31 +304,37 @@ func (g openAPITypeWriter) generate(t *types.Type) error {
|
||||
g.Do("\"$.$\": ", t.Name)
|
||||
if hasOpenAPIDefinitionMethod(t) {
|
||||
g.Do("$.type|raw${}.OpenAPIDefinition(),\n", args)
|
||||
} else {
|
||||
g.Do(nameTmpl+"(ref),\n", args)
|
||||
}
|
||||
}
|
||||
return g.Error()
|
||||
}
|
||||
|
||||
func (g openAPITypeWriter) generate(t *types.Type) error {
|
||||
// Only generate for struct type and ignore the rest
|
||||
switch t.Kind {
|
||||
case types.Struct:
|
||||
if hasOpenAPIDefinitionMethod(t) {
|
||||
// already invoked directly
|
||||
return nil
|
||||
}
|
||||
|
||||
args := argsFromType(t)
|
||||
g.Do("func "+nameTmpl+"(ref $.ReferenceCallback|raw$) $.OpenAPIDefinition|raw$ {\n", args)
|
||||
if hasOpenAPIDefinitionMethods(t) {
|
||||
// Since this generated snippet is part of a map:
|
||||
//
|
||||
// map[string]common.OpenAPIDefinition: {
|
||||
// "TYPE_NAME": {
|
||||
// Schema: spec.Schema{ ... },
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// For compliance with gofmt -s it's important we elide the
|
||||
// struct type. The type is implied by the map and will be
|
||||
// removed otherwise.
|
||||
g.Do("{\n"+
|
||||
g.Do("return $.OpenAPIDefinition|raw${\n"+
|
||||
"Schema: spec.Schema{\n"+
|
||||
"SchemaProps: spec.SchemaProps{\n"+
|
||||
"Type:$.type|raw${}.OpenAPISchemaType(),\n"+
|
||||
"SchemaProps: spec.SchemaProps{\n", args)
|
||||
g.generateDescription(t.CommentLines)
|
||||
g.Do("Type:$.type|raw${}.OpenAPISchemaType(),\n"+
|
||||
"Format:$.type|raw${}.OpenAPISchemaFormat(),\n"+
|
||||
"},\n"+
|
||||
"},\n"+
|
||||
"},\n", args)
|
||||
"}\n}\n\n", args)
|
||||
return nil
|
||||
}
|
||||
g.Do("{\nSchema: spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
|
||||
g.Do("return $.OpenAPIDefinition|raw${\nSchema: spec.Schema{\nSchemaProps: spec.SchemaProps{\n", args)
|
||||
g.generateDescription(t.CommentLines)
|
||||
g.Do("Properties: map[string]$.SpecSchemaType|raw${\n", args)
|
||||
required, err := g.generateMembers(t, []string{})
|
||||
@@ -386,7 +346,7 @@ func (g openAPITypeWriter) generate(t *types.Type) error {
|
||||
g.Do("Required: []string{\"$.$\"},\n", strings.Join(required, "\",\""))
|
||||
}
|
||||
g.Do("},\n", nil)
|
||||
if err := g.generateExtensions(t.CommentLines); err != nil {
|
||||
if err := g.generateStructExtensions(t); err != nil {
|
||||
return err
|
||||
}
|
||||
g.Do("},\n", nil)
|
||||
@@ -406,70 +366,73 @@ func (g openAPITypeWriter) generate(t *types.Type) error {
|
||||
}
|
||||
g.Do("\"$.$\",", k)
|
||||
}
|
||||
g.Do("},\n},\n", nil)
|
||||
g.Do("},\n}\n}\n\n", nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g openAPITypeWriter) generateExtensions(CommentLines []string) error {
|
||||
tagValues := getOpenAPITagValue(CommentLines)
|
||||
type NameValue struct {
|
||||
Name, Value string
|
||||
}
|
||||
extensions := []NameValue{}
|
||||
for _, val := range tagValues {
|
||||
if strings.HasPrefix(val, tagExtensionPrefix) {
|
||||
parts := strings.SplitN(val, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid extension value: %v", val)
|
||||
}
|
||||
extensions = append(extensions, NameValue{parts[0], parts[1]})
|
||||
func (g openAPITypeWriter) generateStructExtensions(t *types.Type) error {
|
||||
extensions, errors := parseExtensions(t.CommentLines)
|
||||
// Initially, we will only log struct extension errors.
|
||||
if len(errors) > 0 {
|
||||
for _, e := range errors {
|
||||
klog.V(2).Infof("[%s]: %s\n", t.String(), e)
|
||||
}
|
||||
}
|
||||
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})
|
||||
// TODO(seans3): Validate struct extensions here.
|
||||
g.emitExtensions(extensions)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g openAPITypeWriter) generateMemberExtensions(m *types.Member, parent *types.Type) error {
|
||||
extensions, parseErrors := parseExtensions(m.CommentLines)
|
||||
validationErrors := validateMemberExtensions(extensions, m)
|
||||
errors := append(parseErrors, validationErrors...)
|
||||
// Initially, we will only log member extension errors.
|
||||
if len(errors) > 0 {
|
||||
errorPrefix := fmt.Sprintf("[%s] %s:", parent.String(), m.String())
|
||||
for _, e := range errors {
|
||||
klog.V(2).Infof("%s %s\n", errorPrefix, e)
|
||||
}
|
||||
}
|
||||
g.emitExtensions(extensions)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g openAPITypeWriter) emitExtensions(extensions []extension) {
|
||||
// If any extensions exist, then emit code to create them.
|
||||
if len(extensions) == 0 {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
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("\"$.$\": ", extension.xName)
|
||||
if extension.hasMultipleValues() {
|
||||
g.Do("[]string{\n", nil)
|
||||
}
|
||||
for _, value := range extension.values {
|
||||
g.Do("\"$.$\",\n", value)
|
||||
}
|
||||
if extension.hasMultipleValues() {
|
||||
g.Do("},\n", nil)
|
||||
}
|
||||
}
|
||||
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())
|
||||
// TODO: Remove patch struct tag validation because they we are now consuming OpenAPI on server.
|
||||
for _, tagKey := range tempPatchTags {
|
||||
structTagValue := reflect.StructTag(m.Tags).Get(tagKey)
|
||||
commentTagValue, err := getSingleTagsValue(m.CommentLines, tagKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if structTagValue != commentTagValue {
|
||||
return fmt.Errorf("Tags in comment and struct should match for member (%s) of (%s)",
|
||||
m.Name, parent.Name.String())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -526,7 +489,7 @@ func (g openAPITypeWriter) generateProperty(m *types.Member, parent *types.Type)
|
||||
return err
|
||||
}
|
||||
g.Do("\"$.$\": {\n", name)
|
||||
if err := g.generateExtensions(m.CommentLines); err != nil {
|
||||
if err := g.generateMemberExtensions(m, parent); err != nil {
|
||||
return err
|
||||
}
|
||||
g.Do("SchemaProps: spec.SchemaProps{\n", nil)
|
||||
|
227
vendor/k8s.io/kube-openapi/pkg/generators/openapi_test.go
generated
vendored
227
vendor/k8s.io/kube-openapi/pkg/generators/openapi_test.go
generated
vendored
@@ -20,6 +20,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -46,7 +47,7 @@ func construct(t *testing.T, files map[string]string, testNamer namer.Namer) (*p
|
||||
return b, u, o
|
||||
}
|
||||
|
||||
func testOpenAPITypeWritter(t *testing.T, code string) (error, *assert.Assertions, *bytes.Buffer) {
|
||||
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,
|
||||
@@ -54,20 +55,33 @@ func testOpenAPITypeWritter(t *testing.T, code string) (error, *assert.Assertion
|
||||
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)
|
||||
}
|
||||
buffer := &bytes.Buffer{}
|
||||
sw := generator.NewSnippetWriter(buffer, context, "$", "$")
|
||||
blahT := universe.Type(types.Name{Package: "base/foo", Name: "Blah"})
|
||||
return newOpenAPITypeWriter(sw).generate(blahT), assert, buffer
|
||||
|
||||
callBuffer := &bytes.Buffer{}
|
||||
callSW := generator.NewSnippetWriter(callBuffer, context, "$", "$")
|
||||
callError := newOpenAPITypeWriter(callSW).generateCall(blahT)
|
||||
|
||||
funcBuffer := &bytes.Buffer{}
|
||||
funcSW := generator.NewSnippetWriter(funcBuffer, context, "$", "$")
|
||||
funcError := newOpenAPITypeWriter(funcSW).generate(blahT)
|
||||
|
||||
return callError, funcError, assert, callBuffer, funcBuffer
|
||||
}
|
||||
|
||||
func TestSimple(t *testing.T) {
|
||||
err, assert, buffer := testOpenAPITypeWritter(t, `
|
||||
callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
|
||||
package foo
|
||||
|
||||
// Blah is a test.
|
||||
@@ -112,15 +126,24 @@ type Blah struct {
|
||||
// +k8s:openapi-gen=x-kubernetes-member-tag:member_test
|
||||
WithExtension string
|
||||
// a member with struct tag as extension
|
||||
// +patchStrategy=ps
|
||||
// +patchStrategy=merge
|
||||
// +patchMergeKey=pmk
|
||||
WithStructTagExtension string `+"`"+`patchStrategy:"ps" patchMergeKey:"pmk"`+"`"+`
|
||||
WithStructTagExtension string `+"`"+`patchStrategy:"merge" patchMergeKey:"pmk"`+"`"+`
|
||||
// a member with a list type
|
||||
// +listType=atomic
|
||||
WithListType []string
|
||||
}
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
if callErr != nil {
|
||||
t.Fatal(callErr)
|
||||
}
|
||||
assert.Equal(`"base/foo.Blah": {
|
||||
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.",
|
||||
@@ -246,7 +269,7 @@ Format: "",
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-patch-merge-key": "pmk",
|
||||
"x-kubernetes-patch-strategy": "ps",
|
||||
"x-kubernetes-patch-strategy": "merge",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
@@ -255,8 +278,27 @@ Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"WithListType": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-list-type": "atomic",
|
||||
},
|
||||
Required: []string{"String","Int64","Int32","Int16","Int8","Uint","Uint64","Uint32","Uint16","Uint8","Byte","Bool","Float64","Float32","ByteArray","WithExtension","WithStructTagExtension"},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "a member with a list type",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"String","Int64","Int32","Int16","Int8","Uint","Uint64","Uint32","Uint16","Uint8","Byte","Bool","Float64","Float32","ByteArray","WithExtension","WithStructTagExtension","WithListType"},
|
||||
},
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
@@ -266,12 +308,14 @@ Extensions: spec.Extensions{
|
||||
},
|
||||
Dependencies: []string{
|
||||
},
|
||||
},
|
||||
`, buffer.String())
|
||||
}
|
||||
}
|
||||
|
||||
`, funcBuffer.String())
|
||||
}
|
||||
|
||||
func TestFailingSample1(t *testing.T) {
|
||||
err, assert, _ := testOpenAPITypeWritter(t, `
|
||||
_, funcErr, assert, _, _ := testOpenAPITypeWriter(t, `
|
||||
package foo
|
||||
|
||||
// Map sample tests openAPIGen.generateMapProperty method.
|
||||
@@ -280,13 +324,13 @@ type Blah struct {
|
||||
StringToArray map[string]map[string]string
|
||||
}
|
||||
`)
|
||||
if assert.Error(err, "An error was expected") {
|
||||
assert.Equal(err, fmt.Errorf("map Element kind Map is not supported in map[string]map[string]string"))
|
||||
if assert.Error(funcErr, "An error was expected") {
|
||||
assert.Equal(funcErr, fmt.Errorf("map Element kind Map is not supported in map[string]map[string]string"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailingSample2(t *testing.T) {
|
||||
err, assert, _ := testOpenAPITypeWritter(t, `
|
||||
_, funcErr, assert, _, _ := testOpenAPITypeWriter(t, `
|
||||
package foo
|
||||
|
||||
// Map sample tests openAPIGen.generateMapProperty method.
|
||||
@@ -294,13 +338,13 @@ type Blah struct {
|
||||
// A sample String to String map
|
||||
StringToArray map[int]string
|
||||
} `)
|
||||
if assert.Error(err, "An error was expected") {
|
||||
assert.Equal(err, fmt.Errorf("map with non-string keys are not supported by OpenAPI in map[int]string"))
|
||||
if assert.Error(funcErr, "An error was expected") {
|
||||
assert.Equal(funcErr, fmt.Errorf("map with non-string keys are not supported by OpenAPI in map[int]string"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomDef(t *testing.T) {
|
||||
err, assert, buffer := testOpenAPITypeWritter(t, `
|
||||
callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
|
||||
package foo
|
||||
|
||||
import openapi "k8s.io/kube-openapi/pkg/common"
|
||||
@@ -319,39 +363,53 @@ func (_ Blah) OpenAPIDefinition() openapi.OpenAPIDefinition {
|
||||
}
|
||||
}
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
if callErr != nil {
|
||||
t.Fatal(callErr)
|
||||
}
|
||||
if funcErr != nil {
|
||||
t.Fatal(funcErr)
|
||||
}
|
||||
assert.Equal(`"base/foo.Blah": foo.Blah{}.OpenAPIDefinition(),
|
||||
`, buffer.String())
|
||||
`, callBuffer.String())
|
||||
assert.Equal(``, funcBuffer.String())
|
||||
}
|
||||
|
||||
func TestCustomDefs(t *testing.T) {
|
||||
err, assert, buffer := testOpenAPITypeWritter(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 err != nil {
|
||||
t.Fatal(err)
|
||||
if callErr != nil {
|
||||
t.Fatal(callErr)
|
||||
}
|
||||
assert.Equal(`"base/foo.Blah": {
|
||||
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(),
|
||||
},
|
||||
},
|
||||
},
|
||||
`, buffer.String())
|
||||
}
|
||||
}
|
||||
|
||||
`, funcBuffer.String())
|
||||
}
|
||||
|
||||
func TestPointer(t *testing.T) {
|
||||
err, assert, buffer := testOpenAPITypeWritter(t, `
|
||||
callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
|
||||
package foo
|
||||
|
||||
// PointerSample demonstrate pointer's properties
|
||||
@@ -366,10 +424,16 @@ type Blah struct {
|
||||
MapPointer *map[string]string
|
||||
}
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
if callErr != nil {
|
||||
t.Fatal(callErr)
|
||||
}
|
||||
assert.Equal(`"base/foo.Blah": {
|
||||
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",
|
||||
@@ -421,12 +485,14 @@ Required: []string{"StringPointer","StructPointer","SlicePointer","MapPointer"},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"base/foo.Blah",},
|
||||
},
|
||||
`, buffer.String())
|
||||
}
|
||||
}
|
||||
|
||||
`, funcBuffer.String())
|
||||
}
|
||||
|
||||
func TestNestedLists(t *testing.T) {
|
||||
err, assert, buffer := testOpenAPITypeWritter(t, `
|
||||
callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
|
||||
package foo
|
||||
|
||||
// Blah is a test.
|
||||
@@ -437,10 +503,16 @@ type Blah struct {
|
||||
NestedList [][]int64
|
||||
}
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
if callErr != nil {
|
||||
t.Fatal(callErr)
|
||||
}
|
||||
assert.Equal(`"base/foo.Blah": {
|
||||
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.",
|
||||
@@ -477,6 +549,77 @@ Extensions: spec.Extensions{
|
||||
},
|
||||
Dependencies: []string{
|
||||
},
|
||||
},
|
||||
`, buffer.String())
|
||||
}
|
||||
}
|
||||
|
||||
`, 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
|
||||
// +listType=map
|
||||
// +listMapKey=port
|
||||
// +listMapKey=protocol
|
||||
WithListField []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.",
|
||||
Properties: map[string]spec.Schema{
|
||||
"WithListField": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-list-map-keys": []string{
|
||||
"port",
|
||||
"protocol",
|
||||
},
|
||||
"x-kubernetes-list-type": "map",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "a member with a list type",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"WithListField"},
|
||||
},
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-type-tag": "type_test",
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
`, funcBuffer.String())
|
||||
}
|
||||
|
4
vendor/k8s.io/kube-openapi/pkg/generators/rules/OWNERS
generated
vendored
Executable file
4
vendor/k8s.io/kube-openapi/pkg/generators/rules/OWNERS
generated
vendored
Executable file
@@ -0,0 +1,4 @@
|
||||
reviewers:
|
||||
- roycaihw
|
||||
approvers:
|
||||
- roycaihw
|
23
vendor/k8s.io/kube-openapi/pkg/generators/rules/doc.go
generated
vendored
Normal file
23
vendor/k8s.io/kube-openapi/pkg/generators/rules/doc.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
Copyright 2018 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 rules contains API rules that are enforced in OpenAPI spec generation
|
||||
// as part of the machinery. Files under this package implement APIRule interface
|
||||
// which evaluates Go type and produces list of API rule violations.
|
||||
//
|
||||
// Implementations of APIRule should be added to API linter under openAPIGen code-
|
||||
// generator to get integrated in the generation process.
|
||||
package rules
|
172
vendor/k8s.io/kube-openapi/pkg/generators/rules/names_match.go
generated
vendored
Normal file
172
vendor/k8s.io/kube-openapi/pkg/generators/rules/names_match.go
generated
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
Copyright 2018 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 rules
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"k8s.io/kube-openapi/pkg/util/sets"
|
||||
|
||||
"k8s.io/gengo/types"
|
||||
)
|
||||
|
||||
var (
|
||||
// Blacklist of JSON tags that should skip match evaluation
|
||||
jsonTagBlacklist = sets.NewString(
|
||||
// Omitted field is ignored by the package
|
||||
"-",
|
||||
)
|
||||
|
||||
// Blacklist of JSON names that should skip match evaluation
|
||||
jsonNameBlacklist = sets.NewString(
|
||||
// Empty name is used for inline struct field (e.g. metav1.TypeMeta)
|
||||
"",
|
||||
// Special case for object and list meta
|
||||
"metadata",
|
||||
)
|
||||
|
||||
// List of substrings that aren't allowed in Go name and JSON name
|
||||
disallowedNameSubstrings = sets.NewString(
|
||||
// Underscore is not allowed in either name
|
||||
"_",
|
||||
// Dash is not allowed in either name. Note that since dash is a valid JSON tag, this should be checked
|
||||
// after JSON tag blacklist check.
|
||||
"-",
|
||||
)
|
||||
)
|
||||
|
||||
/*
|
||||
NamesMatch implements APIRule interface.
|
||||
Go field names must be CamelCase. JSON field names must be camelCase. Other than capitalization of the
|
||||
initial letter, the two should almost always match. No underscores nor dashes in either.
|
||||
This rule verifies the convention "Other than capitalization of the initial letter, the two should almost always match."
|
||||
Examples (also in unit test):
|
||||
Go name | JSON name | match
|
||||
podSpec false
|
||||
PodSpec podSpec true
|
||||
PodSpec PodSpec false
|
||||
podSpec podSpec false
|
||||
PodSpec spec false
|
||||
Spec podSpec false
|
||||
JSONSpec jsonSpec true
|
||||
JSONSpec jsonspec false
|
||||
HTTPJSONSpec httpJSONSpec true
|
||||
NOTE: this validator cannot tell two sequential all-capital words from one word, therefore the case below
|
||||
is also considered matched.
|
||||
HTTPJSONSpec httpjsonSpec true
|
||||
NOTE: JSON names in jsonNameBlacklist should skip evaluation
|
||||
true
|
||||
podSpec true
|
||||
podSpec - true
|
||||
podSpec metadata true
|
||||
*/
|
||||
type NamesMatch struct{}
|
||||
|
||||
// Name returns the name of APIRule
|
||||
func (n *NamesMatch) Name() string {
|
||||
return "names_match"
|
||||
}
|
||||
|
||||
// Validate evaluates API rule on type t and returns a list of field names in
|
||||
// the type that violate the rule. Empty field name [""] implies the entire
|
||||
// type violates the rule.
|
||||
func (n *NamesMatch) Validate(t *types.Type) ([]string, error) {
|
||||
fields := make([]string, 0)
|
||||
|
||||
// Only validate struct type and ignore the rest
|
||||
switch t.Kind {
|
||||
case types.Struct:
|
||||
for _, m := range t.Members {
|
||||
goName := m.Name
|
||||
jsonTag, ok := reflect.StructTag(m.Tags).Lookup("json")
|
||||
// Distinguish empty JSON tag and missing JSON tag. Empty JSON tag / name is
|
||||
// allowed (in JSON name blacklist) but missing JSON tag is invalid.
|
||||
if !ok {
|
||||
fields = append(fields, goName)
|
||||
continue
|
||||
}
|
||||
if jsonTagBlacklist.Has(jsonTag) {
|
||||
continue
|
||||
}
|
||||
jsonName := strings.Split(jsonTag, ",")[0]
|
||||
if !namesMatch(goName, jsonName) {
|
||||
fields = append(fields, goName)
|
||||
}
|
||||
}
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
// namesMatch evaluates if goName and jsonName match the API rule
|
||||
// TODO: Use an off-the-shelf CamelCase solution instead of implementing this logic. The following existing
|
||||
// packages have been tried out:
|
||||
// github.com/markbates/inflect
|
||||
// github.com/segmentio/go-camelcase
|
||||
// github.com/iancoleman/strcase
|
||||
// github.com/fatih/camelcase
|
||||
// Please see https://github.com/kubernetes/kube-openapi/pull/83#issuecomment-400842314 for more details
|
||||
// about why they don't satisfy our need. What we need can be a function that detects an acronym at the
|
||||
// beginning of a string.
|
||||
func namesMatch(goName, jsonName string) bool {
|
||||
if jsonNameBlacklist.Has(jsonName) {
|
||||
return true
|
||||
}
|
||||
if !isAllowedName(goName) || !isAllowedName(jsonName) {
|
||||
return false
|
||||
}
|
||||
if strings.ToLower(goName) != strings.ToLower(jsonName) {
|
||||
return false
|
||||
}
|
||||
// Go field names must be CamelCase. JSON field names must be camelCase.
|
||||
if !isCapital(goName[0]) || isCapital(jsonName[0]) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(goName); i++ {
|
||||
if goName[i] == jsonName[i] {
|
||||
// goName[0:i-1] is uppercase and jsonName[0:i-1] is lowercase, goName[i:]
|
||||
// and jsonName[i:] should match;
|
||||
// goName[i] should be lowercase if i is equal to 1, e.g.:
|
||||
// goName | jsonName
|
||||
// PodSpec podSpec
|
||||
// or uppercase if i is greater than 1, e.g.:
|
||||
// goname | jsonName
|
||||
// JSONSpec jsonSpec
|
||||
// This is to rule out cases like:
|
||||
// goname | jsonName
|
||||
// JSONSpec jsonspec
|
||||
return goName[i:] == jsonName[i:] && (i == 1 || isCapital(goName[i]))
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// isCaptical returns true if one character is capital
|
||||
func isCapital(b byte) bool {
|
||||
return b >= 'A' && b <= 'Z'
|
||||
}
|
||||
|
||||
// isAllowedName checks the list of disallowedNameSubstrings and returns true if name doesn't contain
|
||||
// any disallowed substring.
|
||||
func isAllowedName(name string) bool {
|
||||
for _, substr := range disallowedNameSubstrings.UnsortedList() {
|
||||
if strings.Contains(name, substr) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
359
vendor/k8s.io/kube-openapi/pkg/generators/rules/names_match_test.go
generated
vendored
Normal file
359
vendor/k8s.io/kube-openapi/pkg/generators/rules/names_match_test.go
generated
vendored
Normal file
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
Copyright 2018 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 rules
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/gengo/types"
|
||||
)
|
||||
|
||||
func TestNamesMatch(t *testing.T) {
|
||||
tcs := []struct {
|
||||
// name of test case
|
||||
name string
|
||||
t *types.Type
|
||||
|
||||
// expected list of violation fields
|
||||
expected []string
|
||||
}{
|
||||
// The comments are in format of {goName, jsonName, match},
|
||||
// {"PodSpec", "podSpec", true},
|
||||
{
|
||||
name: "simple",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "PodSpec",
|
||||
Tags: `json:"podSpec"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{},
|
||||
},
|
||||
// {"PodSpec", "podSpec", true},
|
||||
{
|
||||
name: "multiple_json_tags",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "PodSpec",
|
||||
Tags: `json:"podSpec,omitempty"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{},
|
||||
},
|
||||
// {"PodSpec", "podSpec", true},
|
||||
{
|
||||
name: "protobuf_tag",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "PodSpec",
|
||||
Tags: `json:"podSpec,omitempty" protobuf:"bytes,1,opt,name=podSpec"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{},
|
||||
},
|
||||
// {"", "podSpec", false},
|
||||
{
|
||||
name: "empty",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "",
|
||||
Tags: `json:"podSpec"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{""},
|
||||
},
|
||||
// {"PodSpec", "PodSpec", false},
|
||||
{
|
||||
name: "CamelCase_CamelCase",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "PodSpec",
|
||||
Tags: `json:"PodSpec"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{"PodSpec"},
|
||||
},
|
||||
// {"podSpec", "podSpec", false},
|
||||
{
|
||||
name: "camelCase_camelCase",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "podSpec",
|
||||
Tags: `json:"podSpec"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{"podSpec"},
|
||||
},
|
||||
// {"PodSpec", "spec", false},
|
||||
{
|
||||
name: "short_json_name",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "PodSpec",
|
||||
Tags: `json:"spec"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{"PodSpec"},
|
||||
},
|
||||
// {"Spec", "podSpec", false},
|
||||
{
|
||||
name: "long_json_name",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "Spec",
|
||||
Tags: `json:"podSpec"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{"Spec"},
|
||||
},
|
||||
// {"JSONSpec", "jsonSpec", true},
|
||||
{
|
||||
name: "acronym",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "JSONSpec",
|
||||
Tags: `json:"jsonSpec"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{},
|
||||
},
|
||||
// {"JSONSpec", "jsonspec", false},
|
||||
{
|
||||
name: "acronym_invalid",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "JSONSpec",
|
||||
Tags: `json:"jsonspec"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{"JSONSpec"},
|
||||
},
|
||||
// {"HTTPJSONSpec", "httpJSONSpec", true},
|
||||
{
|
||||
name: "multiple_acronym",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "HTTPJSONSpec",
|
||||
Tags: `json:"httpJSONSpec"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{},
|
||||
},
|
||||
// // NOTE: this validator cannot tell two sequential all-capital words from one word,
|
||||
// // therefore the case below is also considered matched.
|
||||
// {"HTTPJSONSpec", "httpjsonSpec", true},
|
||||
{
|
||||
name: "multiple_acronym_as_one",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "HTTPJSONSpec",
|
||||
Tags: `json:"httpjsonSpec"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{},
|
||||
},
|
||||
// NOTE: JSON tags in jsonTagBlacklist should skip evaluation
|
||||
{
|
||||
name: "blacklist_tag_dash",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "podSpec",
|
||||
Tags: `json:"-"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{},
|
||||
},
|
||||
// {"PodSpec", "-", false},
|
||||
{
|
||||
name: "invalid_json_name_dash",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "PodSpec",
|
||||
Tags: `json:"-,"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{"PodSpec"},
|
||||
},
|
||||
// NOTE: JSON names in jsonNameBlacklist should skip evaluation
|
||||
// {"", "", true},
|
||||
{
|
||||
name: "unspecified",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "",
|
||||
Tags: `json:""`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{},
|
||||
},
|
||||
// {"podSpec", "", true},
|
||||
{
|
||||
name: "blacklist_empty",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "podSpec",
|
||||
Tags: `json:""`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{},
|
||||
},
|
||||
// {"podSpec", "metadata", true},
|
||||
{
|
||||
name: "blacklist_metadata",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "podSpec",
|
||||
Tags: `json:"metadata"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "non_struct",
|
||||
t: &types.Type{
|
||||
Kind: types.Map,
|
||||
},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "no_json_tag",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "PodSpec",
|
||||
Tags: `podSpec`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{"PodSpec"},
|
||||
},
|
||||
// NOTE: this is to expand test coverage
|
||||
// {"S", "s", true},
|
||||
{
|
||||
name: "single_character",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "S",
|
||||
Tags: `json:"s"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{},
|
||||
},
|
||||
// NOTE: names with disallowed substrings should fail evaluation
|
||||
// {"Pod-Spec", "pod-Spec", false},
|
||||
{
|
||||
name: "disallowed_substring_dash",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "Pod-Spec",
|
||||
Tags: `json:"pod-Spec"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{"Pod-Spec"},
|
||||
},
|
||||
// {"Pod_Spec", "pod_Spec", false},
|
||||
{
|
||||
name: "disallowed_substring_underscore",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "Pod_Spec",
|
||||
Tags: `json:"pod_Spec"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{"Pod_Spec"},
|
||||
},
|
||||
}
|
||||
|
||||
n := &NamesMatch{}
|
||||
for _, tc := range tcs {
|
||||
if violations, _ := n.Validate(tc.t); !reflect.DeepEqual(violations, tc.expected) {
|
||||
t.Errorf("unexpected validation result: test name %v, want: %v, got: %v",
|
||||
tc.name, tc.expected, violations)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestRuleName tests the Name of API rule. This is to expand test coverage
|
||||
func TestRuleName(t *testing.T) {
|
||||
ruleName := "names_match"
|
||||
n := &NamesMatch{}
|
||||
if n.Name() != ruleName {
|
||||
t.Errorf("unexpected API rule name: want: %v, got: %v", ruleName, n.Name())
|
||||
}
|
||||
}
|
64
vendor/k8s.io/kube-openapi/pkg/generators/rules/omitempty_match_case.go
generated
vendored
Normal file
64
vendor/k8s.io/kube-openapi/pkg/generators/rules/omitempty_match_case.go
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
Copyright 2018 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 rules
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"k8s.io/gengo/types"
|
||||
)
|
||||
|
||||
// OmitEmptyMatchCase implements APIRule interface.
|
||||
// "omitempty" must appear verbatim (no case variants).
|
||||
type OmitEmptyMatchCase struct{}
|
||||
|
||||
func (n *OmitEmptyMatchCase) Name() string {
|
||||
return "omitempty_match_case"
|
||||
}
|
||||
|
||||
func (n *OmitEmptyMatchCase) Validate(t *types.Type) ([]string, error) {
|
||||
fields := make([]string, 0)
|
||||
|
||||
// Only validate struct type and ignore the rest
|
||||
switch t.Kind {
|
||||
case types.Struct:
|
||||
for _, m := range t.Members {
|
||||
goName := m.Name
|
||||
jsonTag, ok := reflect.StructTag(m.Tags).Lookup("json")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.Split(jsonTag, ",")
|
||||
if len(parts) < 2 {
|
||||
// no tags other than name
|
||||
continue
|
||||
}
|
||||
if parts[0] == "-" {
|
||||
// not serialized
|
||||
continue
|
||||
}
|
||||
for _, part := range parts[1:] {
|
||||
if strings.EqualFold(part, "omitempty") && part != "omitempty" {
|
||||
fields = append(fields, goName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fields, nil
|
||||
}
|
110
vendor/k8s.io/kube-openapi/pkg/generators/rules/omitempty_match_case_test.go
generated
vendored
Normal file
110
vendor/k8s.io/kube-openapi/pkg/generators/rules/omitempty_match_case_test.go
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
Copyright 2018 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 rules
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/gengo/types"
|
||||
)
|
||||
|
||||
func TestOmitEmptyMatchCase(t *testing.T) {
|
||||
tcs := []struct {
|
||||
// name of test case
|
||||
name string
|
||||
t *types.Type
|
||||
|
||||
// expected list of violation fields
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "PodSpec",
|
||||
Tags: `json:"podSpec"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "unserialized",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "PodSpec",
|
||||
Tags: `json:"-,inline"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "named_omitEmpty",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "OmitEmpty",
|
||||
Tags: `json:"omitEmpty,inline"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "valid",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "PodSpec",
|
||||
Tags: `json:"podSpec,omitempty"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "invalid",
|
||||
t: &types.Type{
|
||||
Kind: types.Struct,
|
||||
Members: []types.Member{
|
||||
types.Member{
|
||||
Name: "PodSpec",
|
||||
Tags: `json:"podSpec,omitEmpty"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{"PodSpec"},
|
||||
},
|
||||
}
|
||||
|
||||
n := &OmitEmptyMatchCase{}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if violations, _ := n.Validate(tc.t); !reflect.DeepEqual(violations, tc.expected) {
|
||||
t.Errorf("unexpected validation result: want: %v, got: %v", tc.expected, violations)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user