mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-14 18:53:35 +00:00
vendor files
This commit is contained in:
46
vendor/k8s.io/kube-openapi/pkg/generators/README
generated
vendored
Normal file
46
vendor/k8s.io/kube-openapi/pkg/generators/README
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
# Generate OpenAPI definitions
|
||||
|
||||
- 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 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
|
||||
being used in a client's fluent interface.
|
||||
|
||||
# Custom OpenAPI type definitions
|
||||
|
||||
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:
|
||||
|
||||
import openapi "k8s.io/kube-openapi/pkg/common"
|
||||
|
||||
// ...
|
||||
|
||||
type Time struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func (_ Time) OpenAPIDefinition() openapi.OpenAPIDefinition {
|
||||
return openapi.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "date-time",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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:
|
||||
|
||||
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.
|
642
vendor/k8s.io/kube-openapi/pkg/generators/openapi.go
generated
vendored
Normal file
642
vendor/k8s.io/kube-openapi/pkg/generators/openapi.go
generated
vendored
Normal file
@ -0,0 +1,642 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package generators
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"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"
|
||||
)
|
||||
|
||||
// This is the comment tag that carries parameters for open API generation.
|
||||
const tagName = "k8s:openapi-gen"
|
||||
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"
|
||||
)
|
||||
|
||||
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 {
|
||||
if val == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasOptionalTag returns true if the member has +optional in its comments or
|
||||
// omitempty in its json tags.
|
||||
func hasOptionalTag(m *types.Member) bool {
|
||||
hasOptionalCommentTag := types.ExtractCommentTags(
|
||||
"+", m.CommentLines)[tagOptional] != nil
|
||||
hasOptionalJsonTag := strings.Contains(
|
||||
reflect.StructTag(m.Tags).Get("json"), "omitempty")
|
||||
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{},
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
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
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
specPackagePath = "github.com/go-openapi/spec"
|
||||
openAPICommonPackagePath = "k8s.io/kube-openapi/pkg/common"
|
||||
)
|
||||
|
||||
// openApiGen produces a file with auto-generated OpenAPI functions.
|
||||
type openAPIGen struct {
|
||||
generator.DefaultGen
|
||||
// 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 {
|
||||
return &openAPIGen{
|
||||
DefaultGen: generator.DefaultGen{
|
||||
OptionalName: sanitizedName,
|
||||
},
|
||||
imports: generator.NewImportTracker(),
|
||||
targetPackage: targetPackage,
|
||||
context: context,
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if strings.HasSuffix(pkg, "\""+g.targetPackage+"\"") {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *openAPIGen) Imports(c *generator.Context) []string {
|
||||
importLines := []string{}
|
||||
for _, singleImport := range g.imports.ImportLines() {
|
||||
importLines = append(importLines, singleImport)
|
||||
}
|
||||
return importLines
|
||||
}
|
||||
|
||||
func argsFromType(t *types.Type) generator.Args {
|
||||
return generator.Args{
|
||||
"type": t,
|
||||
"ReferenceCallback": types.Ref(openAPICommonPackagePath, "ReferenceCallback"),
|
||||
"OpenAPIDefinition": types.Ref(openAPICommonPackagePath, "OpenAPIDefinition"),
|
||||
"SpecSchemaType": types.Ref(specPackagePath, "Schema"),
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
sw.Do("}\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)
|
||||
sw := generator.NewSnippetWriter(w, c, "$", "$")
|
||||
err := newOpenAPITypeWriter(sw).generate(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sw.Error()
|
||||
}
|
||||
|
||||
func getJsonTags(m *types.Member) []string {
|
||||
jsonTag := reflect.StructTag(m.Tags).Get("json")
|
||||
if jsonTag == "" {
|
||||
return []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 {
|
||||
if jsonTags[0] == "-" {
|
||||
return ""
|
||||
} else {
|
||||
return jsonTags[0]
|
||||
}
|
||||
} else {
|
||||
return m.Name
|
||||
}
|
||||
}
|
||||
|
||||
func shouldInlineMembers(m *types.Member) bool {
|
||||
jsonTags := getJsonTags(m)
|
||||
return len(jsonTags) > 1 && jsonTags[1] == "inline"
|
||||
}
|
||||
|
||||
type openAPITypeWriter struct {
|
||||
*generator.SnippetWriter
|
||||
refTypes map[string]*types.Type
|
||||
GetDefinitionInterface *types.Type
|
||||
}
|
||||
|
||||
func newOpenAPITypeWriter(sw *generator.SnippetWriter) openAPITypeWriter {
|
||||
return openAPITypeWriter{
|
||||
SnippetWriter: sw,
|
||||
refTypes: map[string]*types.Type{},
|
||||
}
|
||||
}
|
||||
|
||||
func methodReturnsValue(mt *types.Type, pkg, name string) bool {
|
||||
if len(mt.Signature.Parameters) != 0 || len(mt.Signature.Results) != 1 {
|
||||
return false
|
||||
}
|
||||
r := mt.Signature.Results[0]
|
||||
return r.Name.Name == name && r.Name.Package == pkg
|
||||
}
|
||||
|
||||
func hasOpenAPIDefinitionMethod(t *types.Type) bool {
|
||||
for mn, mt := range t.Methods {
|
||||
if mn != "OpenAPIDefinition" {
|
||||
continue
|
||||
}
|
||||
return methodReturnsValue(mt, openAPICommonPackagePath, "OpenAPIDefinition")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasOpenAPIDefinitionMethods(t *types.Type) bool {
|
||||
var hasSchemaTypeMethod, hasOpenAPISchemaFormat bool
|
||||
for mn, mt := range t.Methods {
|
||||
switch mn {
|
||||
case "OpenAPISchemaType":
|
||||
hasSchemaTypeMethod = methodReturnsValue(mt, "", "[]string")
|
||||
case "OpenAPISchemaFormat":
|
||||
hasOpenAPISchemaFormat = methodReturnsValue(mt, "", "string")
|
||||
}
|
||||
}
|
||||
return hasSchemaTypeMethod && hasOpenAPISchemaFormat
|
||||
}
|
||||
|
||||
// typeShortName returns short package name (e.g. the name x appears in package x definition) dot type name.
|
||||
func typeShortName(t *types.Type) string {
|
||||
return filepath.Base(t.Name.Package) + "." + t.Name.Name
|
||||
}
|
||||
|
||||
func (g openAPITypeWriter) generateMembers(t *types.Type, required []string) ([]string, error) {
|
||||
var err error
|
||||
for _, m := range t.Members {
|
||||
if hasOpenAPITagValue(m.CommentLines, tagValueFalse) {
|
||||
continue
|
||||
}
|
||||
if shouldInlineMembers(&m) {
|
||||
required, err = g.generateMembers(m.Type, required)
|
||||
if err != nil {
|
||||
return required, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
name := getReferableName(&m)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if !hasOptionalTag(&m) {
|
||||
required = append(required, name)
|
||||
}
|
||||
if err = g.generateProperty(&m, t); err != nil {
|
||||
glog.Errorf("Error when generating: %v, %v\n", name, m)
|
||||
return required, err
|
||||
}
|
||||
}
|
||||
return required, nil
|
||||
}
|
||||
|
||||
func (g openAPITypeWriter) generate(t *types.Type) error {
|
||||
// Only generate for struct type and ignore the rest
|
||||
switch t.Kind {
|
||||
case types.Struct:
|
||||
args := argsFromType(t)
|
||||
g.Do("\"$.$\": ", t.Name)
|
||||
if hasOpenAPIDefinitionMethod(t) {
|
||||
g.Do("$.type|raw${}.OpenAPIDefinition(),\n", args)
|
||||
return nil
|
||||
}
|
||||
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"+
|
||||
"Schema: spec.Schema{\n"+
|
||||
"SchemaProps: spec.SchemaProps{\n"+
|
||||
"Type:$.type|raw${}.OpenAPISchemaType(),\n"+
|
||||
"Format:$.type|raw${}.OpenAPISchemaFormat(),\n"+
|
||||
"},\n"+
|
||||
"},\n"+
|
||||
"},\n", args)
|
||||
return nil
|
||||
}
|
||||
g.Do("{\nSchema: spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
|
||||
g.generateDescription(t.CommentLines)
|
||||
g.Do("Properties: map[string]$.SpecSchemaType|raw${\n", args)
|
||||
required, err := g.generateMembers(t, []string{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.Do("},\n", nil)
|
||||
if len(required) > 0 {
|
||||
g.Do("Required: []string{\"$.$\"},\n", strings.Join(required, "\",\""))
|
||||
}
|
||||
g.Do("},\n", nil)
|
||||
if err := g.generateExtensions(t.CommentLines); err != nil {
|
||||
return err
|
||||
}
|
||||
g.Do("},\n", nil)
|
||||
g.Do("Dependencies: []string{\n", args)
|
||||
// Map order is undefined, sort them or we may get a different file generated each time.
|
||||
keys := []string{}
|
||||
for k := range g.refTypes {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
v := g.refTypes[k]
|
||||
if t, _ := openapi.GetOpenAPITypeFormat(v.String()); t != "" {
|
||||
// This is a known type, we do not need a reference to it
|
||||
// Will eliminate special case of time.Time
|
||||
continue
|
||||
}
|
||||
g.Do("\"$.$\",", k)
|
||||
}
|
||||
g.Do("},\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]})
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (g openAPITypeWriter) generateDescription(CommentLines []string) {
|
||||
var buffer bytes.Buffer
|
||||
delPrevChar := func() {
|
||||
if buffer.Len() > 0 {
|
||||
buffer.Truncate(buffer.Len() - 1) // Delete the last " " or "\n"
|
||||
}
|
||||
}
|
||||
|
||||
for _, line := range CommentLines {
|
||||
// Ignore all lines after ---
|
||||
if line == "---" {
|
||||
break
|
||||
}
|
||||
line = strings.TrimRight(line, " ")
|
||||
leading := strings.TrimLeft(line, " ")
|
||||
switch {
|
||||
case len(line) == 0: // Keep paragraphs
|
||||
delPrevChar()
|
||||
buffer.WriteString("\n\n")
|
||||
case strings.HasPrefix(leading, "TODO"): // Ignore one line TODOs
|
||||
case strings.HasPrefix(leading, "+"): // Ignore instructions to go2idl
|
||||
default:
|
||||
if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") {
|
||||
delPrevChar()
|
||||
line = "\n" + line + "\n" // Replace it with newline. This is useful when we have a line with: "Example:\n\tJSON-someting..."
|
||||
} else {
|
||||
line += " "
|
||||
}
|
||||
buffer.WriteString(line)
|
||||
}
|
||||
}
|
||||
|
||||
postDoc := strings.TrimRight(buffer.String(), "\n")
|
||||
postDoc = strings.Replace(postDoc, "\\\"", "\"", -1) // replace user's \" to "
|
||||
postDoc = strings.Replace(postDoc, "\"", "\\\"", -1) // Escape "
|
||||
postDoc = strings.Replace(postDoc, "\n", "\\n", -1)
|
||||
postDoc = strings.Replace(postDoc, "\t", "\\t", -1)
|
||||
postDoc = strings.Trim(postDoc, " ")
|
||||
if postDoc != "" {
|
||||
g.Do("Description: \"$.$\",\n", postDoc)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
g.Do("SchemaProps: spec.SchemaProps{\n", nil)
|
||||
g.generateDescription(m.CommentLines)
|
||||
jsonTags := getJsonTags(m)
|
||||
if len(jsonTags) > 1 && jsonTags[1] == "string" {
|
||||
g.generateSimpleProperty("string", "")
|
||||
g.Do("},\n},\n", nil)
|
||||
return nil
|
||||
}
|
||||
t := resolveAliasAndPtrType(m.Type)
|
||||
// If we can get a openAPI type and format for this type, we consider it to be simple property
|
||||
typeString, format := openapi.GetOpenAPITypeFormat(t.String())
|
||||
if typeString != "" {
|
||||
g.generateSimpleProperty(typeString, format)
|
||||
g.Do("},\n},\n", nil)
|
||||
return nil
|
||||
}
|
||||
switch t.Kind {
|
||||
case types.Builtin:
|
||||
return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", t)
|
||||
case types.Map:
|
||||
if err := g.generateMapProperty(t); err != nil {
|
||||
return err
|
||||
}
|
||||
case types.Slice, types.Array:
|
||||
if err := g.generateSliceProperty(t); err != nil {
|
||||
return err
|
||||
}
|
||||
case types.Struct, types.Interface:
|
||||
g.generateReferenceProperty(t)
|
||||
default:
|
||||
return fmt.Errorf("cannot generate spec for type %v", t)
|
||||
}
|
||||
g.Do("},\n},\n", nil)
|
||||
return g.Error()
|
||||
}
|
||||
|
||||
func (g openAPITypeWriter) generateSimpleProperty(typeString, format string) {
|
||||
g.Do("Type: []string{\"$.$\"},\n", typeString)
|
||||
g.Do("Format: \"$.$\",\n", format)
|
||||
}
|
||||
|
||||
func (g openAPITypeWriter) generateReferenceProperty(t *types.Type) {
|
||||
g.refTypes[t.Name.String()] = t
|
||||
g.Do("Ref: ref(\"$.$\"),\n", t.Name.String())
|
||||
}
|
||||
|
||||
func resolveAliasAndPtrType(t *types.Type) *types.Type {
|
||||
var prev *types.Type
|
||||
for prev != t {
|
||||
prev = t
|
||||
if t.Kind == types.Alias {
|
||||
t = t.Underlying
|
||||
}
|
||||
if t.Kind == types.Pointer {
|
||||
t = t.Elem
|
||||
}
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (g openAPITypeWriter) generateMapProperty(t *types.Type) error {
|
||||
keyType := resolveAliasAndPtrType(t.Key)
|
||||
elemType := resolveAliasAndPtrType(t.Elem)
|
||||
|
||||
// According to OpenAPI examples, only map from string is supported
|
||||
if keyType.Name.Name != "string" {
|
||||
return fmt.Errorf("map with non-string keys are not supported by OpenAPI in %v", t)
|
||||
}
|
||||
g.Do("Type: []string{\"object\"},\n", nil)
|
||||
g.Do("AdditionalProperties: &spec.SchemaOrBool{\nSchema: &spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
|
||||
typeString, format := openapi.GetOpenAPITypeFormat(elemType.String())
|
||||
if typeString != "" {
|
||||
g.generateSimpleProperty(typeString, format)
|
||||
g.Do("},\n},\n},\n", nil)
|
||||
return nil
|
||||
}
|
||||
switch elemType.Kind {
|
||||
case types.Builtin:
|
||||
return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", elemType)
|
||||
case types.Struct:
|
||||
g.generateReferenceProperty(elemType)
|
||||
case types.Slice, types.Array:
|
||||
g.generateSliceProperty(elemType)
|
||||
default:
|
||||
return fmt.Errorf("map Element kind %v is not supported in %v", elemType.Kind, t.Name)
|
||||
}
|
||||
g.Do("},\n},\n},\n", nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g openAPITypeWriter) generateSliceProperty(t *types.Type) error {
|
||||
elemType := resolveAliasAndPtrType(t.Elem)
|
||||
g.Do("Type: []string{\"array\"},\n", nil)
|
||||
g.Do("Items: &spec.SchemaOrArray{\nSchema: &spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
|
||||
typeString, format := openapi.GetOpenAPITypeFormat(elemType.String())
|
||||
if typeString != "" {
|
||||
g.generateSimpleProperty(typeString, format)
|
||||
g.Do("},\n},\n},\n", nil)
|
||||
return nil
|
||||
}
|
||||
switch elemType.Kind {
|
||||
case types.Builtin:
|
||||
return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", elemType)
|
||||
case types.Struct:
|
||||
g.generateReferenceProperty(elemType)
|
||||
default:
|
||||
return fmt.Errorf("slice Element kind %v is not supported in %v", elemType.Kind, t)
|
||||
}
|
||||
g.Do("},\n},\n},\n", nil)
|
||||
return nil
|
||||
}
|
426
vendor/k8s.io/kube-openapi/pkg/generators/openapi_test.go
generated
vendored
Normal file
426
vendor/k8s.io/kube-openapi/pkg/generators/openapi_test.go
generated
vendored
Normal file
@ -0,0 +1,426 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package generators
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"k8s.io/gengo/generator"
|
||||
"k8s.io/gengo/namer"
|
||||
"k8s.io/gengo/parser"
|
||||
"k8s.io/gengo/types"
|
||||
)
|
||||
|
||||
func construct(t *testing.T, files map[string]string, testNamer namer.Namer) (*parser.Builder, types.Universe, []*types.Type) {
|
||||
b := parser.New()
|
||||
for name, src := range files {
|
||||
if err := b.AddFileForTest(filepath.Dir(name), name, []byte(src)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
u, err := b.FindTypes()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
orderer := namer.Orderer{Namer: testNamer}
|
||||
o := orderer.OrderUniverse(u)
|
||||
return b, u, o
|
||||
}
|
||||
|
||||
func testOpenAPITypeWritter(t *testing.T, code string) (error, *assert.Assertions, *bytes.Buffer) {
|
||||
assert := assert.New(t)
|
||||
var testFiles = map[string]string{
|
||||
"base/foo/bar.go": code,
|
||||
}
|
||||
rawNamer := namer.NewRawNamer("o", nil)
|
||||
namers := namer.NameSystems{
|
||||
"raw": namer.NewRawNamer("", nil),
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func TestSimple(t *testing.T) {
|
||||
err, assert, buffer := testOpenAPITypeWritter(t, `
|
||||
package foo
|
||||
|
||||
// Blah is a test.
|
||||
// +k8s:openapi-gen=true
|
||||
// +k8s:openapi-gen=x-kubernetes-type-tag:type_test
|
||||
type Blah struct {
|
||||
// A simple string
|
||||
String string
|
||||
// A simple int
|
||||
Int int `+"`"+`json:",omitempty"`+"`"+`
|
||||
// An int considered string simple int
|
||||
IntString int `+"`"+`json:",string"`+"`"+`
|
||||
// A simple int64
|
||||
Int64 int64
|
||||
// A simple int32
|
||||
Int32 int32
|
||||
// A simple int16
|
||||
Int16 int16
|
||||
// A simple int8
|
||||
Int8 int8
|
||||
// A simple int
|
||||
Uint uint
|
||||
// A simple int64
|
||||
Uint64 uint64
|
||||
// A simple int32
|
||||
Uint32 uint32
|
||||
// A simple int16
|
||||
Uint16 uint16
|
||||
// A simple int8
|
||||
Uint8 uint8
|
||||
// A simple byte
|
||||
Byte byte
|
||||
// A simple boolean
|
||||
Bool bool
|
||||
// A simple float64
|
||||
Float64 float64
|
||||
// A simple float32
|
||||
Float32 float32
|
||||
// a base64 encoded characters
|
||||
ByteArray []byte
|
||||
// a member with an extension
|
||||
// +k8s:openapi-gen=x-kubernetes-member-tag:member_test
|
||||
WithExtension string
|
||||
// a member with struct tag as extension
|
||||
// +patchStrategy=ps
|
||||
// +patchMergeKey=pmk
|
||||
WithStructTagExtension string `+"`"+`patchStrategy:"ps" patchMergeKey:"pmk"`+"`"+`
|
||||
}
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(`"base/foo.Blah": {
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Blah is a test.",
|
||||
Properties: map[string]spec.Schema{
|
||||
"String": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "A simple string",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"Int64": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "A simple int64",
|
||||
Type: []string{"integer"},
|
||||
Format: "int64",
|
||||
},
|
||||
},
|
||||
"Int32": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "A simple int32",
|
||||
Type: []string{"integer"},
|
||||
Format: "int32",
|
||||
},
|
||||
},
|
||||
"Int16": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "A simple int16",
|
||||
Type: []string{"integer"},
|
||||
Format: "int32",
|
||||
},
|
||||
},
|
||||
"Int8": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "A simple int8",
|
||||
Type: []string{"integer"},
|
||||
Format: "byte",
|
||||
},
|
||||
},
|
||||
"Uint": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "A simple int",
|
||||
Type: []string{"integer"},
|
||||
Format: "int32",
|
||||
},
|
||||
},
|
||||
"Uint64": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "A simple int64",
|
||||
Type: []string{"integer"},
|
||||
Format: "int64",
|
||||
},
|
||||
},
|
||||
"Uint32": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "A simple int32",
|
||||
Type: []string{"integer"},
|
||||
Format: "int64",
|
||||
},
|
||||
},
|
||||
"Uint16": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "A simple int16",
|
||||
Type: []string{"integer"},
|
||||
Format: "int32",
|
||||
},
|
||||
},
|
||||
"Uint8": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "A simple int8",
|
||||
Type: []string{"integer"},
|
||||
Format: "byte",
|
||||
},
|
||||
},
|
||||
"Byte": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "A simple byte",
|
||||
Type: []string{"integer"},
|
||||
Format: "byte",
|
||||
},
|
||||
},
|
||||
"Bool": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "A simple boolean",
|
||||
Type: []string{"boolean"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"Float64": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "A simple float64",
|
||||
Type: []string{"number"},
|
||||
Format: "double",
|
||||
},
|
||||
},
|
||||
"Float32": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "A simple float32",
|
||||
Type: []string{"number"},
|
||||
Format: "float",
|
||||
},
|
||||
},
|
||||
"ByteArray": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "a base64 encoded characters",
|
||||
Type: []string{"string"},
|
||||
Format: "byte",
|
||||
},
|
||||
},
|
||||
"WithExtension": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-member-tag": "member_test",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "a member with an extension",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"WithStructTagExtension": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-patch-merge-key": "pmk",
|
||||
"x-kubernetes-patch-strategy": "ps",
|
||||
},
|
||||
},
|
||||
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{
|
||||
"x-kubernetes-type-tag": "type_test",
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
},
|
||||
},
|
||||
`, buffer.String())
|
||||
}
|
||||
|
||||
func TestFailingSample1(t *testing.T) {
|
||||
err, assert, _ := testOpenAPITypeWritter(t, `
|
||||
package foo
|
||||
|
||||
// Map sample tests openAPIGen.generateMapProperty method.
|
||||
type Blah struct {
|
||||
// A sample String to String map
|
||||
StringToArray map[string]map[string]string
|
||||
}
|
||||
`)
|
||||
if 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"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailingSample2(t *testing.T) {
|
||||
err, assert, _ := testOpenAPITypeWritter(t, `
|
||||
package foo
|
||||
|
||||
// Map sample tests openAPIGen.generateMapProperty method.
|
||||
type Blah struct {
|
||||
// A sample String to String map
|
||||
StringToArray map[int]string
|
||||
} `)
|
||||
if assert.Error(err, "An error was expected") {
|
||||
assert.Equal(err, 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, `
|
||||
package foo
|
||||
|
||||
import openapi "k8s.io/kube-openapi/pkg/common"
|
||||
|
||||
type Blah struct {
|
||||
}
|
||||
|
||||
func (_ Blah) OpenAPIDefinition() openapi.OpenAPIDefinition {
|
||||
return openapi.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "date-time",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(`"base/foo.Blah": foo.Blah{}.OpenAPIDefinition(),
|
||||
`, buffer.String())
|
||||
}
|
||||
|
||||
func TestCustomDefs(t *testing.T) {
|
||||
err, assert, buffer := testOpenAPITypeWritter(t, `
|
||||
package foo
|
||||
|
||||
type Blah struct {
|
||||
}
|
||||
|
||||
func (_ Blah) OpenAPISchemaType() []string { return []string{"string"} }
|
||||
func (_ Blah) OpenAPISchemaFormat() string { return "date-time" }
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(`"base/foo.Blah": {
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type:foo.Blah{}.OpenAPISchemaType(),
|
||||
Format:foo.Blah{}.OpenAPISchemaFormat(),
|
||||
},
|
||||
},
|
||||
},
|
||||
`, buffer.String())
|
||||
}
|
||||
|
||||
func TestPointer(t *testing.T) {
|
||||
err, assert, buffer := testOpenAPITypeWritter(t, `
|
||||
package foo
|
||||
|
||||
// PointerSample demonstrate pointer's properties
|
||||
type Blah struct {
|
||||
// A string pointer
|
||||
StringPointer *string
|
||||
// A struct pointer
|
||||
StructPointer *Blah
|
||||
// A slice pointer
|
||||
SlicePointer *[]string
|
||||
// A map pointer
|
||||
MapPointer *map[string]string
|
||||
}
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(`"base/foo.Blah": {
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "PointerSample demonstrate pointer's properties",
|
||||
Properties: map[string]spec.Schema{
|
||||
"StringPointer": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "A string pointer",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"StructPointer": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "A struct pointer",
|
||||
Ref: ref("base/foo.Blah"),
|
||||
},
|
||||
},
|
||||
"SlicePointer": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "A slice pointer",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"MapPointer": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "A map pointer",
|
||||
Type: []string{"object"},
|
||||
AdditionalProperties: &spec.SchemaOrBool{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"StringPointer","StructPointer","SlicePointer","MapPointer"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"base/foo.Blah",},
|
||||
},
|
||||
`, buffer.String())
|
||||
}
|
Reference in New Issue
Block a user