Use full package path as definition name in OpenAPI

This commit is contained in:
mbohlool 2017-01-19 09:34:22 -08:00
parent 424a0e9e82
commit 239169a5a4
13 changed files with 326 additions and 82 deletions

View File

@ -295,7 +295,7 @@ func Run(s *options.ServerRunOptions) error {
genericConfig.Authenticator = apiAuthenticator
genericConfig.Authorizer = apiAuthorizer
genericConfig.AdmissionControl = admissionController
genericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(generatedopenapi.OpenAPIDefinitions)
genericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(generatedopenapi.GetOpenAPIDefinitions)
genericConfig.OpenAPIConfig.SecurityDefinitions = securityDefinitions
genericConfig.OpenAPIConfig.Info.Title = "Kubernetes"
genericConfig.SwaggerConfig = genericapiserver.DefaultSwaggerConfig()

View File

@ -133,7 +133,7 @@ const (
// openApiGen produces a file with auto-generated OpenAPI functions.
type openAPIGen struct {
generator.DefaultGen
// TargetPackage is the package that will get OpenAPIDefinitions variable contains all open API definitions.
// TargetPackage is the package that will get GetOpenAPIDefinitions function returns all open API definitions.
targetPackage *types.Package
imports namer.ImportTracker
context *generator.Context
@ -185,23 +185,24 @@ func (g *openAPIGen) Imports(c *generator.Context) []string {
func argsFromType(t *types.Type) generator.Args {
return generator.Args{
"type": t,
"OpenAPIDefinitions": types.Ref(openAPICommonPackagePath, "OpenAPIDefinitions"),
"OpenAPIDefinition": types.Ref(openAPICommonPackagePath, "OpenAPIDefinition"),
"SpecSchemaType": types.Ref(specPackagePath, "Schema"),
"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("var OpenAPIDefinitions *$.OpenAPIDefinitions|raw$ = ", argsFromType(nil))
sw.Do("&$.OpenAPIDefinitions|raw${\n", argsFromType(nil))
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()
}
@ -308,9 +309,9 @@ func (g openAPITypeWriter) generate(t *types.Type) error {
switch t.Kind {
case types.Struct:
args := argsFromType(t)
g.Do("\"$.$\": ", typeShortName(t))
g.Do("\"$.$\": ", t.Name)
if hasOpenAPIDefinitionMethod(t) {
g.Do("$.type|raw${}.OpenAPIDefinition(),", args)
g.Do("$.type|raw${}.OpenAPIDefinition(),\n", args)
return nil
}
g.Do("{\nSchema: spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
@ -437,14 +438,8 @@ func (g openAPITypeWriter) generateSimpleProperty(typeString, format string) {
}
func (g openAPITypeWriter) generateReferenceProperty(t *types.Type) {
var name string
if t.Name.Package == "" {
name = t.Name.Name
} else {
name = filepath.Base(t.Name.Package) + "." + t.Name.Name
}
g.refTypes[name] = t
g.Do("Ref: spec.MustCreateRef(\"#/definitions/$.$\"),\n", name)
g.refTypes[t.Name.String()] = t
g.Do("Ref: ref(\"$.$\"),\n", t.Name.String())
}
func resolveAliasAndPtrType(t *types.Type) *types.Type {

View File

@ -112,7 +112,7 @@ type Blah struct {
if err != nil {
t.Fatal(err)
}
assert.Equal(`"foo.Blah": {
assert.Equal(`"base/foo.Blah": {
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "Blah is a test.",
@ -280,7 +280,7 @@ type Blah struct {
if err != nil {
t.Fatal(err)
}
assert.Equal(`"foo.Blah": {
assert.Equal(`"base/foo.Blah": {
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "PointerSample demonstrate pointer's properties",
@ -295,7 +295,7 @@ Format: "",
"StructPointer": {
SchemaProps: spec.SchemaProps{
Description: "A struct pointer",
Ref: spec.MustCreateRef("#/definitions/foo.Blah"),
Ref: ref("base/foo.Blah"),
},
},
"SlicePointer": {
@ -331,7 +331,7 @@ Required: []string{"StringPointer","StructPointer","SlicePointer","MapPointer"},
},
},
Dependencies: []string{
"foo.Blah",},
"base/foo.Blah",},
},
`, buffer.String())
}

View File

@ -175,7 +175,7 @@ func Run(s *options.ServerRunOptions) error {
genericConfig.Authenticator = apiAuthenticator
genericConfig.Authorizer = apiAuthorizer
genericConfig.AdmissionControl = admissionController
genericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(openapi.OpenAPIDefinitions)
genericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(openapi.GetOpenAPIDefinitions)
genericConfig.OpenAPIConfig.SecurityDefinitions = securityDefinitions
genericConfig.SwaggerConfig = genericapiserver.DefaultSwaggerConfig()
genericConfig.LongRunningFunc = filters.BasicLongRunningRequestCheck(

View File

@ -19,16 +19,25 @@ package openapi
import (
"bytes"
"fmt"
"reflect"
"strings"
"unicode"
"github.com/emicklei/go-restful"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/util/trie"
"sort"
)
var verbs = trie.New([]string{"get", "log", "read", "replace", "patch", "delete", "deletecollection", "watch", "connect", "proxy", "list", "create", "patch"})
const (
extensionGVK = "x-kubernetes-group-version-kind"
)
// ToValidOperationID makes an string a valid op ID (e.g. removing punctuations and whitespaces and make it camel case)
func ToValidOperationID(s string, capitalizeFirstLetter bool) string {
var buffer bytes.Buffer
@ -85,3 +94,77 @@ func GetOperationIDAndTags(servePath string, r *restful.Route) (string, []string
return op, tags, nil
}
}
type groupVersionKinds []v1.GroupVersionKind
func (s groupVersionKinds) Len() int {
return len(s)
}
func (s groupVersionKinds) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s groupVersionKinds) Less(i, j int) bool {
if s[i].Group == s[j].Group {
if s[i].Version == s[j].Version {
return s[i].Kind < s[j].Kind
}
return s[i].Version < s[j].Version
}
return s[i].Group < s[j].Group
}
// DefinitionNamer is the type to customize OpenAPI definition name.
type DefinitionNamer struct {
typeGroupVersionKinds map[string]groupVersionKinds
}
func gvkConvert(gvk schema.GroupVersionKind) v1.GroupVersionKind {
return v1.GroupVersionKind{
Group: gvk.Group,
Version: gvk.Version,
Kind: gvk.Kind,
}
}
func friendlyName(name string) string {
nameParts := strings.Split(name, "/")
// Reverse first part. e.g., io.k8s... instead of k8s.io...
if len(nameParts) > 0 && strings.Contains(nameParts[0], ".") {
parts := strings.Split(nameParts[0], ".")
for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 {
parts[i], parts[j] = parts[j], parts[i]
}
nameParts[0] = strings.Join(parts, ".")
}
return strings.Join(nameParts, ".")
}
func typeName(t reflect.Type) string {
return fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())
}
// NewDefinitionNamer constructs a new DefinitionNamer to be used to customize OpenAPI spec.
func NewDefinitionNamer(s *runtime.Scheme) DefinitionNamer {
ret := DefinitionNamer{
typeGroupVersionKinds: map[string]groupVersionKinds{},
}
for gvk, rtype := range s.AllKnownTypes() {
ret.typeGroupVersionKinds[typeName(rtype)] = append(ret.typeGroupVersionKinds[typeName(rtype)], gvkConvert(gvk))
}
for _, gvk := range ret.typeGroupVersionKinds {
sort.Sort(gvk)
}
return ret
}
// GetDefinitionName returns the name and tags for a given definition
func (d *DefinitionNamer) GetDefinitionName(servePath string, name string) (string, spec.Extensions) {
if groupVersionKinds, ok := d.typeGroupVersionKinds[name]; ok {
return friendlyName(name), spec.Extensions{
extensionGVK: []v1.GroupVersionKind(groupVersionKinds),
}
}
return friendlyName(name), nil
}

View File

@ -0,0 +1,86 @@
/*
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 openapi
import (
"reflect"
"strings"
"testing"
"github.com/go-openapi/spec"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
type TestType struct {
}
func (t TestType) GetObjectKind() schema.ObjectKind {
return t
}
func (t TestType) SetGroupVersionKind(kind schema.GroupVersionKind) {
}
func (t TestType) GroupVersionKind() schema.GroupVersionKind {
return schema.GroupVersionKind{
Group: "test",
Version: "v1",
Kind: "TestType",
}
}
func assertEqual(t *testing.T, expected, actual interface{}) {
var equal bool
if expected == nil || actual == nil {
equal = expected == actual
} else {
equal = reflect.DeepEqual(expected, actual)
}
if !equal {
t.Errorf("%v != %v", expected, actual)
}
}
func TestGetDefinitionName(t *testing.T) {
testType := TestType{}
typePkgName := "k8s.io/kubernetes/pkg/genericapiserver/endpoints/openapi.TestType"
typeFriendlyName := "io.k8s.kubernetes.pkg.genericapiserver.endpoints.openapi.TestType"
if strings.HasSuffix(reflect.TypeOf(testType).PkgPath(), "go_default_test") {
// the test is running inside bazel where the package name is changed and
// "go_default_test" will add to package path.
typePkgName = "k8s.io/kubernetes/pkg/genericapiserver/endpoints/openapi/go_default_test.TestType"
typeFriendlyName = "io.k8s.kubernetes.pkg.genericapiserver.endpoints.openapi.go_default_test.TestType"
}
s := runtime.NewScheme()
s.AddKnownTypeWithName(testType.GroupVersionKind(), &testType)
namer := NewDefinitionNamer(s)
n, e := namer.GetDefinitionName("", typePkgName)
assertEqual(t, typeFriendlyName, n)
assertEqual(t, e["x-kubernetes-group-version-kind"], []v1.GroupVersionKind{
{
Group: "test",
Version: "v1",
Kind: "TestType",
},
})
n, e2 := namer.GetDefinitionName("", "test.com/another.Type")
assertEqual(t, "com.test.another.Type", n)
assertEqual(t, e2, spec.Extensions(nil))
}

View File

@ -213,7 +213,8 @@ func NewConfig() *Config {
return config.ApplyOptions(defaultOptions)
}
func DefaultOpenAPIConfig(definitions *openapicommon.OpenAPIDefinitions) *openapicommon.Config {
func DefaultOpenAPIConfig(getDefinitions openapicommon.GetOpenAPIDefinitions) *openapicommon.Config {
defNamer := apiopenapi.NewDefinitionNamer(api.Scheme)
return &openapicommon.Config{
ProtocolList: []string{"https"},
IgnorePrefixes: []string{"/swaggerapi"},
@ -228,7 +229,8 @@ func DefaultOpenAPIConfig(definitions *openapicommon.OpenAPIDefinitions) *openap
},
},
GetOperationIDAndTags: apiopenapi.GetOperationIDAndTags,
Definitions: definitions,
GetDefinitionName: defNamer.GetDefinitionName,
GetDefinitions: getDefinitions,
}
}

View File

@ -61,7 +61,7 @@ func setUp(t *testing.T) (*etcdtesting.EtcdTestServer, Config, *assert.Assertion
config.RequestContextMapper = genericapirequest.NewRequestContextMapper()
config.LegacyAPIGroupPrefixes = sets.NewString("/api")
config.OpenAPIConfig = DefaultOpenAPIConfig(openapigen.OpenAPIDefinitions)
config.OpenAPIConfig = DefaultOpenAPIConfig(openapigen.GetOpenAPIDefinitions)
config.OpenAPIConfig.Info = &spec.Info{
InfoProps: spec.InfoProps{
Title: "Kubernetes",

View File

@ -40,6 +40,7 @@ type openAPI struct {
swagger *spec.Swagger
protocolList []string
servePath string
definitions map[string]openapi.OpenAPIDefinition
}
// RegisterOpenAPIService registers a handler to provides standard OpenAPI specification.
@ -79,6 +80,15 @@ func (o *openAPI) init(webServices []*restful.WebService) error {
return r.Operation, nil, nil
}
}
if o.config.GetDefinitionName == nil {
o.config.GetDefinitionName = func(_, name string) (string, spec.Extensions) {
return name[strings.LastIndex(name, "/")+1:], nil
}
}
o.definitions = o.config.GetDefinitions(func(name string) spec.Ref {
defName, _ := o.config.GetDefinitionName(o.servePath, name)
return spec.MustCreateRef("#/definitions/" + openapi.EscapeJsonPointer(defName))
})
if o.config.CommonResponses == nil {
o.config.CommonResponses = map[int]spec.Response{}
}
@ -93,12 +103,34 @@ func (o *openAPI) init(webServices []*restful.WebService) error {
return nil
}
func getCanonicalizeTypeName(t reflect.Type) string {
if t.PkgPath() == "" {
return t.Name()
}
path := t.PkgPath()
if strings.Contains(path, "/vendor/") {
path = path[strings.Index(path, "/vendor/")+len("/vendor/"):]
}
return path + "." + t.Name()
}
func (o *openAPI) buildDefinitionRecursively(name string) error {
if _, ok := o.swagger.Definitions[name]; ok {
uniqueName, extensions := o.config.GetDefinitionName(o.servePath, name)
if _, ok := o.swagger.Definitions[uniqueName]; ok {
return nil
}
if item, ok := (*o.config.Definitions)[name]; ok {
o.swagger.Definitions[name] = item.Schema
if item, ok := o.definitions[name]; ok {
schema := spec.Schema{
SchemaProps: item.Schema.SchemaProps,
SwaggerSchemaProps: item.Schema.SwaggerSchemaProps,
}
if extensions != nil {
schema.Extensions = spec.Extensions{}
for k, v := range extensions {
schema.Extensions[k] = v
}
}
o.swagger.Definitions[uniqueName] = schema
for _, v := range item.Dependencies {
if err := o.buildDefinitionRecursively(v); err != nil {
return err
@ -118,11 +150,12 @@ func (o *openAPI) buildDefinitionForType(sample interface{}) (string, error) {
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
name := t.String()
name := getCanonicalizeTypeName(t)
if err := o.buildDefinitionRecursively(name); err != nil {
return "", err
}
return "#/definitions/" + name, nil
defName, _ := o.config.GetDefinitionName(o.servePath, name)
return "#/definitions/" + openapi.EscapeJsonPointer(defName), nil
}
// buildPaths builds OpenAPI paths using go-restful's web services.
@ -244,18 +277,12 @@ func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map
if len(ret.Responses.StatusCodeResponses) == 0 {
ret.Responses.Default = o.config.DefaultResponse
}
// If there is a read sample, there will be a body param referring to it.
if route.ReadSample != nil {
if _, err := o.toSchema(reflect.TypeOf(route.ReadSample).String(), route.ReadSample); err != nil {
return ret, err
}
}
// Build non-common Parameters
ret.Parameters = make([]spec.Parameter, 0)
for _, param := range route.ParameterDocs {
if _, isCommon := inPathCommonParamsMap[mapKeyFromParam(param)]; !isCommon {
openAPIParam, err := o.buildParameter(param.Data())
openAPIParam, err := o.buildParameter(param.Data(), route.ReadSample)
if err != nil {
return ret, err
}
@ -266,8 +293,7 @@ func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map
}
func (o *openAPI) buildResponse(model interface{}, description string) (spec.Response, error) {
typeName := reflect.TypeOf(model).String()
schema, err := o.toSchema(typeName, model)
schema, err := o.toSchema(model)
if err != nil {
return spec.Response{}, err
}
@ -300,8 +326,9 @@ func (o *openAPI) findCommonParameters(routes []restful.Route) (map[interface{}]
}
}
for key, count := range paramOpsCountByName {
if count == len(routes) {
openAPIParam, err := o.buildParameter(paramNameKindToDataMap[key])
paramData := paramNameKindToDataMap[key]
if count == len(routes) && paramData.Kind != restful.BodyParameterKind {
openAPIParam, err := o.buildParameter(paramData, nil)
if err != nil {
return commonParamsMap, err
}
@ -311,8 +338,8 @@ func (o *openAPI) findCommonParameters(routes []restful.Route) (map[interface{}]
return commonParamsMap, nil
}
func (o *openAPI) toSchema(typeName string, model interface{}) (_ *spec.Schema, err error) {
if openAPIType, openAPIFormat := openapi.GetOpenAPITypeFormat(typeName); openAPIType != "" {
func (o *openAPI) toSchema(model interface{}) (_ *spec.Schema, err error) {
if openAPIType, openAPIFormat := openapi.GetOpenAPITypeFormat(getCanonicalizeTypeName(reflect.TypeOf(model))); openAPIType != "" {
return &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{openAPIType},
@ -320,12 +347,9 @@ func (o *openAPI) toSchema(typeName string, model interface{}) (_ *spec.Schema,
},
}, nil
} else {
ref := "#/definitions/" + typeName
if model != nil {
ref, err = o.buildDefinitionForType(model)
if err != nil {
return nil, err
}
ref, err := o.buildDefinitionForType(model)
if err != nil {
return nil, err
}
return &spec.Schema{
SchemaProps: spec.SchemaProps{
@ -335,7 +359,7 @@ func (o *openAPI) toSchema(typeName string, model interface{}) (_ *spec.Schema,
}
}
func (o *openAPI) buildParameter(restParam restful.ParameterData) (ret spec.Parameter, err error) {
func (o *openAPI) buildParameter(restParam restful.ParameterData, bodySample interface{}) (ret spec.Parameter, err error) {
ret = spec.Parameter{
ParamProps: spec.ParamProps{
Name: restParam.Name,
@ -345,9 +369,16 @@ func (o *openAPI) buildParameter(restParam restful.ParameterData) (ret spec.Para
}
switch restParam.Kind {
case restful.BodyParameterKind:
ret.In = "body"
ret.Schema, err = o.toSchema(restParam.DataType, nil)
return ret, err
if bodySample != nil {
ret.In = "body"
ret.Schema, err = o.toSchema(bodySample)
return ret, err
} else {
// There is not enough information in the body parameter to build the definition.
// Body parameter has a data type that is a short name but we need full package name
// of the type to create a definition.
return ret, fmt.Errorf("restful body parameters are not supported: %v", restParam.DataType)
}
case restful.PathParameterKind:
ret.In = "path"
if !restParam.Required {
@ -375,7 +406,7 @@ func (o *openAPI) buildParameter(restParam restful.ParameterData) (ret spec.Para
func (o *openAPI) buildParameters(restParam []*restful.Parameter) (ret []spec.Parameter, err error) {
ret = make([]spec.Parameter, len(restParam))
for i, v := range restParam {
ret[i], err = o.buildParameter(v.Data())
ret[i], err = o.buildParameter(v.Data(), nil)
if err != nil {
return ret, err
}

View File

@ -19,6 +19,7 @@ package openapi
import (
"fmt"
"net/http"
"strings"
"testing"
"github.com/emicklei/go-restful"
@ -186,9 +187,22 @@ func getConfig(fullMethods bool) (*openapi.Config, *restful.Container) {
Description: "Test API",
},
},
Definitions: &openapi.OpenAPIDefinitions{
"openapi.TestInput": *TestInput{}.OpenAPIDefinition(),
"openapi.TestOutput": *TestOutput{}.OpenAPIDefinition(),
GetDefinitions: func(_ openapi.ReferenceCallback) map[string]openapi.OpenAPIDefinition {
return map[string]openapi.OpenAPIDefinition{
"k8s.io/kubernetes/pkg/genericapiserver/server/openapi.TestInput": *TestInput{}.OpenAPIDefinition(),
"k8s.io/kubernetes/pkg/genericapiserver/server/openapi.TestOutput": *TestOutput{}.OpenAPIDefinition(),
// Bazel changes the package name, this is ok for testing, but we need to fix it if it happened
// in the main code.
"k8s.io/kubernetes/pkg/genericapiserver/server/openapi/go_default_test.TestInput": *TestInput{}.OpenAPIDefinition(),
"k8s.io/kubernetes/pkg/genericapiserver/server/openapi/go_default_test.TestOutput": *TestOutput{}.OpenAPIDefinition(),
}
},
GetDefinitionName: func(_ string, name string) (string, spec.Extensions) {
friendlyName := name[strings.LastIndex(name, "/")+1:]
if strings.HasPrefix(friendlyName, "go_default_test") {
friendlyName = "openapi" + friendlyName[len("go_default_test"):]
}
return friendlyName, nil
},
}, container
}
@ -216,12 +230,18 @@ func getTestPathItem(allMethods bool, opPrefix string) spec.PathItem {
}
ret.Get.Parameters = getAdditionalTestParameters()
if allMethods {
ret.PathItemProps.Put = getTestOperation("put", opPrefix)
ret.PathItemProps.Post = getTestOperation("post", opPrefix)
ret.PathItemProps.Head = getTestOperation("head", opPrefix)
ret.PathItemProps.Patch = getTestOperation("patch", opPrefix)
ret.PathItemProps.Delete = getTestOperation("delete", opPrefix)
ret.PathItemProps.Options = getTestOperation("options", opPrefix)
ret.Put = getTestOperation("put", opPrefix)
ret.Put.Parameters = getTestParameters()
ret.Post = getTestOperation("post", opPrefix)
ret.Post.Parameters = getTestParameters()
ret.Head = getTestOperation("head", opPrefix)
ret.Head.Parameters = getTestParameters()
ret.Patch = getTestOperation("patch", opPrefix)
ret.Patch.Parameters = getTestParameters()
ret.Delete = getTestOperation("delete", opPrefix)
ret.Delete.Parameters = getTestParameters()
ret.Options = getTestOperation("options", opPrefix)
ret.Options.Parameters = getTestParameters()
}
return ret
}
@ -250,16 +270,8 @@ func getTestResponses() *spec.Responses {
}
func getTestCommonParameters() []spec.Parameter {
ret := make([]spec.Parameter, 3)
ret := make([]spec.Parameter, 2)
ret[0] = spec.Parameter{
ParamProps: spec.ParamProps{
Name: "body",
In: "body",
Required: true,
Schema: getRefSchema("#/definitions/openapi.TestInput"),
},
}
ret[1] = spec.Parameter{
SimpleSchema: spec.SimpleSchema{
Type: "string",
},
@ -273,7 +285,7 @@ func getTestCommonParameters() []spec.Parameter {
UniqueItems: true,
},
}
ret[2] = spec.Parameter{
ret[1] = spec.Parameter{
SimpleSchema: spec.SimpleSchema{
Type: "string",
},
@ -289,9 +301,30 @@ func getTestCommonParameters() []spec.Parameter {
return ret
}
func getAdditionalTestParameters() []spec.Parameter {
ret := make([]spec.Parameter, 2)
func getTestParameters() []spec.Parameter {
ret := make([]spec.Parameter, 1)
ret[0] = spec.Parameter{
ParamProps: spec.ParamProps{
Name: "body",
In: "body",
Required: true,
Schema: getRefSchema("#/definitions/openapi.TestInput"),
},
}
return ret
}
func getAdditionalTestParameters() []spec.Parameter {
ret := make([]spec.Parameter, 3)
ret[0] = spec.Parameter{
ParamProps: spec.ParamProps{
Name: "body",
In: "body",
Required: true,
Schema: getRefSchema("#/definitions/openapi.TestInput"),
},
}
ret[1] = spec.Parameter{
ParamProps: spec.ParamProps{
Name: "fparam",
Description: "a test form parameter",
@ -304,7 +337,7 @@ func getAdditionalTestParameters() []spec.Parameter {
UniqueItems: true,
},
}
ret[1] = spec.Parameter{
ret[2] = spec.Parameter{
SimpleSchema: spec.SimpleSchema{
Type: "integer",
},

View File

@ -43,7 +43,7 @@ func TestValidOpenAPISpec(t *testing.T) {
defer etcdserver.Terminate(t)
config.GenericConfig.EnableIndex = true
config.GenericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(openapigen.OpenAPIDefinitions)
config.GenericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(openapigen.GetOpenAPIDefinitions)
config.GenericConfig.OpenAPIConfig.Info = &spec.Info{
InfoProps: spec.InfoProps{
Title: "Kubernetes",

View File

@ -19,6 +19,7 @@ package openapi
import (
"github.com/emicklei/go-restful"
"github.com/go-openapi/spec"
"strings"
)
// OpenAPIDefinition describes single type. Normally these definitions are auto-generated using gen-openapi.
@ -27,8 +28,10 @@ type OpenAPIDefinition struct {
Dependencies []string
}
type ReferenceCallback func(path string) spec.Ref
// OpenAPIDefinitions is collection of all definitions.
type OpenAPIDefinitions map[string]OpenAPIDefinition
type GetOpenAPIDefinitions func(ReferenceCallback) map[string]OpenAPIDefinition
// OpenAPIDefinitionGetter gets openAPI definitions for a given type. If a type implements this interface,
// the definition returned by it will be used, otherwise the auto-generated definitions will be used. See
@ -59,11 +62,15 @@ type Config struct {
// OpenAPIDefinitions should provide definition for all models used by routes. Failure to provide this map
// or any of the models will result in spec generation failure.
Definitions *OpenAPIDefinitions
GetDefinitions GetOpenAPIDefinitions
// GetOperationIDAndTags returns operation id and tags for a restful route. It is an optional function to customize operation IDs.
GetOperationIDAndTags func(servePath string, r *restful.Route) (string, []string, error)
// GetDefinitionName returns a friendly name for a definition base on the serving path. parameter `name` is the full name of the definition.
// It is an optional function to customize model names.
GetDefinitionName func(servePath string, name string) (string, spec.Extensions)
// SecurityDefinitions is list of all security definitions for OpenAPI service. If this is not nil, the user of config
// is responsible to provide DefaultSecurity and (maybe) add unauthorized response to CommonResponses.
SecurityDefinitions *spec.SecurityDefinitions
@ -141,3 +148,10 @@ func GetOpenAPITypeFormat(typeName string) (string, string) {
}
return mapped[0], mapped[1]
}
func EscapeJsonPointer(p string) string {
// Escaping reference name using rfc6901
p = strings.Replace(p, "~", "~0", -1)
p = strings.Replace(p, "/", "~1", -1)
return p
}

View File

@ -183,7 +183,7 @@ func startMasterOrDie(masterConfig *master.Config, incomingServer *httptest.Serv
masterConfig = NewMasterConfig()
masterConfig.GenericConfig.EnableProfiling = true
masterConfig.GenericConfig.EnableMetrics = true
masterConfig.GenericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(openapi.OpenAPIDefinitions)
masterConfig.GenericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(openapi.GetOpenAPIDefinitions)
masterConfig.GenericConfig.OpenAPIConfig.Info = &spec.Info{
InfoProps: spec.InfoProps{
Title: "Kubernetes",
@ -195,7 +195,7 @@ func startMasterOrDie(masterConfig *master.Config, incomingServer *httptest.Serv
Description: "Default Response.",
},
}
masterConfig.GenericConfig.OpenAPIConfig.Definitions = openapi.OpenAPIDefinitions
masterConfig.GenericConfig.OpenAPIConfig.GetDefinitions = openapi.GetOpenAPIDefinitions
masterConfig.GenericConfig.SwaggerConfig = genericapiserver.DefaultSwaggerConfig()
}