From c4d1aeb2819f9cf8d679c02786d75e766d905e98 Mon Sep 17 00:00:00 2001 From: austin ce Date: Mon, 27 Sep 2021 16:37:58 -0400 Subject: [PATCH 1/6] Decouple REST Framework Takes ownership of the route interfaces and removes exposing the `go-restful` dependency. Adds an adapter for the currently used version of `go-restful`. --- pkg/builder/openapi.go | 117 +++++++++++------- pkg/builder/util.go | 16 +-- pkg/common/common.go | 7 ++ pkg/common/interfaces.go | 88 +++++++++++++ pkg/common/restfuladapter/adapter.go | 16 +++ pkg/common/restfuladapter/param_adapter.go | 54 ++++++++ .../restfuladapter/response_error_adapter.go | 25 ++++ pkg/common/restfuladapter/route_adapter.go | 67 ++++++++++ .../restfuladapter/webservice_adapter.go | 34 +++++ pkg/handler/handler.go | 11 +- 10 files changed, 379 insertions(+), 56 deletions(-) create mode 100644 pkg/common/interfaces.go create mode 100644 pkg/common/restfuladapter/adapter.go create mode 100644 pkg/common/restfuladapter/param_adapter.go create mode 100644 pkg/common/restfuladapter/response_error_adapter.go create mode 100644 pkg/common/restfuladapter/route_adapter.go create mode 100644 pkg/common/restfuladapter/webservice_adapter.go diff --git a/pkg/builder/openapi.go b/pkg/builder/openapi.go index 3e9e675a0..2b5a3d8b1 100644 --- a/pkg/builder/openapi.go +++ b/pkg/builder/openapi.go @@ -25,6 +25,7 @@ import ( restful "github.com/emicklei/go-restful" "k8s.io/kube-openapi/pkg/common" + "k8s.io/kube-openapi/pkg/common/restfuladapter" "k8s.io/kube-openapi/pkg/util" "k8s.io/kube-openapi/pkg/validation/spec" ) @@ -40,10 +41,17 @@ type openAPI struct { definitions map[string]common.OpenAPIDefinition } -// BuildOpenAPISpec builds OpenAPI spec given a list of webservices (containing routes) and common.Config to customize it. -func BuildOpenAPISpec(webServices []*restful.WebService, config *common.Config) (*spec.Swagger, error) { +// BuildOpenAPISpec builds OpenAPI spec given a list of route containers and common.Config to customize it. +// +// Deprecated: BuildOpenAPISpecFromRoutes should be used instead. +func BuildOpenAPISpec(routeContainers []*restful.WebService, config *common.Config) (*spec.Swagger, error) { + return BuildOpenAPISpecFromRoutes(restfuladapter.AdaptWebServices(routeContainers), config) +} + +// BuildOpenAPISpecFromRoutes builds OpenAPI spec given a list of route containers and common.Config to customize it. +func BuildOpenAPISpecFromRoutes(routeContainers []common.RouteContainer, config *common.Config) (*spec.Swagger, error) { o := newOpenAPI(config) - err := o.buildPaths(webServices) + err := o.buildPaths(routeContainers) if err != nil { return nil, err } @@ -95,11 +103,25 @@ func newOpenAPI(config *common.Config) openAPI { }, }, } - if o.config.GetOperationIDAndTags == nil { - o.config.GetOperationIDAndTags = func(r *restful.Route) (string, []string, error) { - return r.Operation, nil, nil + + if o.config.GetOperationIDAndTagsFromRoute == nil { + // Map the deprecated handler to the common interface, if provided. + if o.config.GetOperationIDAndTags != nil { + o.config.GetOperationIDAndTagsFromRoute = func(r common.Route) (string, []string, error) { + restfulRouteAdapter, ok := r.(*restfuladapter.RouteAdapter) + if !ok { + return "", nil, fmt.Errorf("config.GetOperationIDAndTags specified but route is not a restful v1 Route") + } + + return o.config.GetOperationIDAndTags(restfulRouteAdapter.Route) + } + } else { + o.config.GetOperationIDAndTagsFromRoute = func(r common.Route) (string, []string, error) { + return r.OperationName(), nil, nil + } } } + if o.config.GetDefinitionName == nil { o.config.GetDefinitionName = func(name string) (string, spec.Extensions) { return name[strings.LastIndex(name, "/")+1:], nil @@ -181,10 +203,10 @@ func (o *openAPI) buildDefinitionForType(name string) (string, error) { } // buildPaths builds OpenAPI paths using go-restful's web services. -func (o *openAPI) buildPaths(webServices []*restful.WebService) error { +func (o *openAPI) buildPaths(routeContainers []common.RouteContainer) error { pathsToIgnore := util.NewTrie(o.config.IgnorePrefixes) duplicateOpId := make(map[string]string) - for _, w := range webServices { + for _, w := range routeContainers { rootPath := w.RootPath() if pathsToIgnore.HasPrefix(rootPath) { continue @@ -234,7 +256,7 @@ func (o *openAPI) buildPaths(webServices []*restful.WebService) error { } else { duplicateOpId[op.ID] = path } - switch strings.ToUpper(route.Method) { + switch strings.ToUpper(route.Method()) { case "GET": pathItem.Get = op case "POST": @@ -258,12 +280,12 @@ func (o *openAPI) buildPaths(webServices []*restful.WebService) error { } // buildOperations builds operations for each webservice path -func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map[interface{}]spec.Parameter) (ret *spec.Operation, err error) { +func (o *openAPI) buildOperations(route common.Route, inPathCommonParamsMap map[interface{}]spec.Parameter) (ret *spec.Operation, err error) { ret = &spec.Operation{ OperationProps: spec.OperationProps{ - Description: route.Doc, - Consumes: route.Consumes, - Produces: route.Produces, + Description: route.Description(), + Consumes: route.Consumes(), + Produces: route.Produces(), Schemes: o.config.ProtocolList, Responses: &spec.Responses{ ResponsesProps: spec.ResponsesProps{ @@ -272,7 +294,7 @@ func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map }, }, } - for k, v := range route.Metadata { + for k, v := range route.Metadata() { if strings.HasPrefix(k, common.ExtensionPrefix) { if ret.Extensions == nil { ret.Extensions = spec.Extensions{} @@ -280,20 +302,20 @@ func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map ret.Extensions.Add(k, v) } } - if ret.ID, ret.Tags, err = o.config.GetOperationIDAndTags(&route); err != nil { + if ret.ID, ret.Tags, err = o.config.GetOperationIDAndTagsFromRoute(route); err != nil { return ret, err } // Build responses - for _, resp := range route.ResponseErrors { - ret.Responses.StatusCodeResponses[resp.Code], err = o.buildResponse(resp.Model, resp.Message) + for _, resp := range route.StatusCodeResponses() { + ret.Responses.StatusCodeResponses[resp.Code()], err = o.buildResponse(resp.Model(), resp.Message()) if err != nil { return ret, err } } // If there is no response but a write sample, assume that write sample is an http.StatusOK response. - if len(ret.Responses.StatusCodeResponses) == 0 && route.WriteSample != nil { - ret.Responses.StatusCodeResponses[http.StatusOK], err = o.buildResponse(route.WriteSample, "OK") + if len(ret.Responses.StatusCodeResponses) == 0 && route.ResponsePayloadSample() != nil { + ret.Responses.StatusCodeResponses[http.StatusOK], err = o.buildResponse(route.ResponsePayloadSample(), "OK") if err != nil { return ret, err } @@ -310,9 +332,9 @@ func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map // Build non-common Parameters ret.Parameters = make([]spec.Parameter, 0) - for _, param := range route.ParameterDocs { + for _, param := range route.Parameters() { if _, isCommon := inPathCommonParamsMap[mapKeyFromParam(param)]; !isCommon { - openAPIParam, err := o.buildParameter(param.Data(), route.ReadSample) + openAPIParam, err := o.buildParameter(param, route.RequestPayloadSample()) if err != nil { return ret, err } @@ -335,29 +357,30 @@ func (o *openAPI) buildResponse(model interface{}, description string) (spec.Res }, nil } -func (o *openAPI) findCommonParameters(routes []restful.Route) (map[interface{}]spec.Parameter, error) { +func (o *openAPI) findCommonParameters(routes []common.Route) (map[interface{}]spec.Parameter, error) { commonParamsMap := make(map[interface{}]spec.Parameter, 0) paramOpsCountByName := make(map[interface{}]int, 0) - paramNameKindToDataMap := make(map[interface{}]restful.ParameterData, 0) + paramNameKindToDataMap := make(map[interface{}]common.Parameter, 0) for _, route := range routes { routeParamDuplicateMap := make(map[interface{}]bool) s := "" - for _, param := range route.ParameterDocs { - m, _ := json.Marshal(param.Data()) + params := route.Parameters() + for _, param := range params { + m, _ := json.Marshal(param) s += string(m) + "\n" key := mapKeyFromParam(param) if routeParamDuplicateMap[key] { - msg, _ := json.Marshal(route.ParameterDocs) - return commonParamsMap, fmt.Errorf("duplicate parameter %v for route %v, %v", param.Data().Name, string(msg), s) + msg, _ := json.Marshal(params) + return commonParamsMap, fmt.Errorf("duplicate parameter %v for route %v, %v", param.Name(), string(msg), s) } routeParamDuplicateMap[key] = true paramOpsCountByName[key]++ - paramNameKindToDataMap[key] = param.Data() + paramNameKindToDataMap[key] = param } } for key, count := range paramOpsCountByName { paramData := paramNameKindToDataMap[key] - if count == len(routes) && paramData.Kind != restful.BodyParameterKind { + if count == len(routes) && paramData.Kind() != common.BodyParameterKind { openAPIParam, err := o.buildParameter(paramData, nil) if err != nil { return commonParamsMap, err @@ -389,16 +412,16 @@ func (o *openAPI) toSchema(name string) (_ *spec.Schema, err error) { } } -func (o *openAPI) buildParameter(restParam restful.ParameterData, bodySample interface{}) (ret spec.Parameter, err error) { +func (o *openAPI) buildParameter(restParam common.Parameter, bodySample interface{}) (ret spec.Parameter, err error) { ret = spec.Parameter{ ParamProps: spec.ParamProps{ - Name: restParam.Name, - Description: restParam.Description, - Required: restParam.Required, + Name: restParam.Name(), + Description: restParam.Description(), + Required: restParam.Required(), }, } - switch restParam.Kind { - case restful.BodyParameterKind: + switch restParam.Kind() { + case common.BodyParameterKind: if bodySample != nil { ret.In = "body" ret.Schema, err = o.toSchema(util.GetCanonicalTypeName(bodySample)) @@ -407,36 +430,36 @@ func (o *openAPI) buildParameter(restParam restful.ParameterData, bodySample int // 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) + return ret, fmt.Errorf("restful body parameters are not supported: %v", restParam.DataType()) } - case restful.PathParameterKind: + case common.PathParameterKind: ret.In = "path" - if !restParam.Required { + if !restParam.Required() { return ret, fmt.Errorf("path parameters should be marked at required for parameter %v", restParam) } - case restful.QueryParameterKind: + case common.QueryParameterKind: ret.In = "query" - case restful.HeaderParameterKind: + case common.HeaderParameterKind: ret.In = "header" - case restful.FormParameterKind: + case common.FormParameterKind: ret.In = "formData" default: - return ret, fmt.Errorf("unknown restful operation kind : %v", restParam.Kind) + return ret, fmt.Errorf("unknown restful operation kind : %v", restParam.Kind()) } - openAPIType, openAPIFormat := common.OpenAPITypeFormat(restParam.DataType) + openAPIType, openAPIFormat := common.OpenAPITypeFormat(restParam.DataType()) if openAPIType == "" { - return ret, fmt.Errorf("non-body Restful parameter type should be a simple type, but got : %v", restParam.DataType) + return ret, fmt.Errorf("non-body Restful parameter type should be a simple type, but got : %v", restParam.DataType()) } ret.Type = openAPIType ret.Format = openAPIFormat - ret.UniqueItems = !restParam.AllowMultiple + ret.UniqueItems = !restParam.AllowMultiple() return ret, nil } -func (o *openAPI) buildParameters(restParam []*restful.Parameter) (ret []spec.Parameter, err error) { +func (o *openAPI) buildParameters(restParam []common.Parameter) (ret []spec.Parameter, err error) { ret = make([]spec.Parameter, len(restParam)) for i, v := range restParam { - ret[i], err = o.buildParameter(v.Data(), nil) + ret[i], err = o.buildParameter(v, nil) if err != nil { return ret, err } diff --git a/pkg/builder/util.go b/pkg/builder/util.go index c7ea40d91..3621a4de1 100644 --- a/pkg/builder/util.go +++ b/pkg/builder/util.go @@ -19,7 +19,7 @@ package builder import ( "sort" - "github.com/emicklei/go-restful" + "k8s.io/kube-openapi/pkg/common" "k8s.io/kube-openapi/pkg/validation/spec" ) @@ -42,20 +42,20 @@ func sortParameters(p []spec.Parameter) { sort.Sort(byNameIn{p}) } -func groupRoutesByPath(routes []restful.Route) map[string][]restful.Route { - pathToRoutes := make(map[string][]restful.Route) +func groupRoutesByPath(routes []common.Route) map[string][]common.Route { + pathToRoutes := make(map[string][]common.Route) for _, r := range routes { - pathToRoutes[r.Path] = append(pathToRoutes[r.Path], r) + pathToRoutes[r.Path()] = append(pathToRoutes[r.Path()], r) } return pathToRoutes } -func mapKeyFromParam(param *restful.Parameter) interface{} { +func mapKeyFromParam(param common.Parameter) interface{} { return struct { Name string - Kind int + Kind common.ParameterKind }{ - Name: param.Data().Name, - Kind: param.Data().Kind, + Name: param.Name(), + Kind: param.Kind(), } } diff --git a/pkg/common/common.go b/pkg/common/common.go index cf1d01f90..4956b3a79 100644 --- a/pkg/common/common.go +++ b/pkg/common/common.go @@ -21,6 +21,7 @@ import ( "strings" "github.com/emicklei/go-restful" + "k8s.io/kube-openapi/pkg/validation/spec" ) @@ -92,8 +93,14 @@ type Config struct { GetDefinitions GetOpenAPIDefinitions // GetOperationIDAndTags returns operation id and tags for a restful route. It is an optional function to customize operation IDs. + // + // Deprecated: GetOperationIDAndTagsFromRoute should be used instead. This cannot be specified if using the new Route + // interface set of funcs. GetOperationIDAndTags func(r *restful.Route) (string, []string, error) + // GetOperationIDAndTagsFromRoute returns operation id and tags for a Route. It is an optional function to customize operation IDs. + GetOperationIDAndTagsFromRoute func(r 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(name string) (string, spec.Extensions) diff --git a/pkg/common/interfaces.go b/pkg/common/interfaces.go new file mode 100644 index 000000000..059fc551b --- /dev/null +++ b/pkg/common/interfaces.go @@ -0,0 +1,88 @@ +package common + +// RouteContainer is the entrypoint for a service, which may contain multiple +// routes under a common path with a common set of path parameters. +type RouteContainer interface { + // RootPath is the path that all contained routes are nested under. + RootPath() string + // PathParameters are common parameters defined in the root path. + PathParameters() []Parameter + // Routes are all routes exposed under the root path. + Routes() []Route +} + +// Route is a logical endpoint of a service. +type Route interface { + // Method defines the HTTP Method. + Method() string + // Path defines the route's endpoint. + Path() string + // OperationName defines a machine-readable ID for the route. + OperationName() string + // Parameters defines the list of accepted parameters. + Parameters() []Parameter + // Description is a human-readable route description. + Description() string + // Consumes defines the consumed content-types. + Consumes() []string + // Produces defines the produced content-types. + Produces() []string + // Metadata allows adding extensions to the generated spec. + Metadata() map[string]interface{} + // RequestPayloadSample defines an example request payload. Can return nil. + RequestPayloadSample() interface{} + // ResponsePayloadSample defines an example response payload. Can return nil. + ResponsePayloadSample() interface{} + // StatusCodeResponses defines a mapping of HTTP Status Codes to the specific response(s). + // Multiple responses with the same HTTP Status Code are acceptable. + StatusCodeResponses() []StatusCodeResponse +} + +// StatusCodeResponse is an explicit response type with an HTTP Status Code. +type StatusCodeResponse interface { + // Code defines the HTTP Status Code. + Code() int + // Message returns the human-readable message. + Message() string + // Model defines an example payload for this response. + Model() interface{} +} + +// Parameter is a Route parameter. +type Parameter interface { + // Name defines the unique-per-route identifier. + Name() string + // Description is the human-readable description of the param. + Description() string + // Required defines if this parameter must be provided. + Required() bool + // Kind defines the type of the parameter itself. + Kind() ParameterKind + // DataType defines the type of data the parameter carries. + DataType() string + // AllowMultiple defines if more than one value can be supplied for the parameter. + AllowMultiple() bool +} + +// ParameterKind is an enum of route parameter types. +type ParameterKind int + +const ( + // PathParameterKind indicates the request parameter type is "path". + PathParameterKind = ParameterKind(iota) + + // QueryParameterKind indicates the request parameter type is "query". + QueryParameterKind + + // BodyParameterKind indicates the request parameter type is "body". + BodyParameterKind + + // HeaderParameterKind indicates the request parameter type is "header". + HeaderParameterKind + + // FormParameterKind indicates the request parameter type is "form". + FormParameterKind + + // UnknownParameterKind indicates the request parameter type has not been specified. + UnknownParameterKind +) diff --git a/pkg/common/restfuladapter/adapter.go b/pkg/common/restfuladapter/adapter.go new file mode 100644 index 000000000..e59697a58 --- /dev/null +++ b/pkg/common/restfuladapter/adapter.go @@ -0,0 +1,16 @@ +package restfuladapter + +import ( + "github.com/emicklei/go-restful" + "k8s.io/kube-openapi/pkg/common" +) + +// AdaptWebServices adapts a slice of restful.WebService into the common interfaces. +func AdaptWebServices(webServices []*restful.WebService) []common.RouteContainer { + var containers []common.RouteContainer + for _, ws := range webServices { + containers = append(containers, &WebServiceAdapter{ws}) + } + return containers +} + diff --git a/pkg/common/restfuladapter/param_adapter.go b/pkg/common/restfuladapter/param_adapter.go new file mode 100644 index 000000000..be0b45650 --- /dev/null +++ b/pkg/common/restfuladapter/param_adapter.go @@ -0,0 +1,54 @@ +package restfuladapter + +import ( + "encoding/json" + "github.com/emicklei/go-restful" + "k8s.io/kube-openapi/pkg/common" +) + +var _ common.Parameter = &ParamAdapter{} + +type ParamAdapter struct { + Param *restful.Parameter +} + +func (r *ParamAdapter) MarshalJSON() ([]byte, error) { + return json.Marshal(r.Param) +} + +func (r *ParamAdapter) Name() string { + return r.Param.Data().Name +} + +func (r *ParamAdapter) Description() string { + return r.Param.Data().Description +} + +func (r *ParamAdapter) Required() bool { + return r.Param.Data().Required +} + +func (r *ParamAdapter) Kind() common.ParameterKind { + switch r.Param.Kind() { + case restful.PathParameterKind: + return common.PathParameterKind + case restful.QueryParameterKind: + return common.QueryParameterKind + case restful.BodyParameterKind: + return common.BodyParameterKind + case restful.HeaderParameterKind: + return common.HeaderParameterKind + case restful.FormParameterKind: + return common.FormParameterKind + default: + return common.UnknownParameterKind + } +} + +func (r *ParamAdapter) DataType() string { + return r.Param.Data().DataType +} + +func (r *ParamAdapter) AllowMultiple() bool { + return r.Param.Data().AllowMultiple +} diff --git a/pkg/common/restfuladapter/response_error_adapter.go b/pkg/common/restfuladapter/response_error_adapter.go new file mode 100644 index 000000000..019cfb8d9 --- /dev/null +++ b/pkg/common/restfuladapter/response_error_adapter.go @@ -0,0 +1,25 @@ +package restfuladapter + +import ( + "github.com/emicklei/go-restful" + "k8s.io/kube-openapi/pkg/common" +) + +var _ common.StatusCodeResponse = &ResponseErrorAdapter{} + +// ResponseErrorAdapter adapts a restful.ResponseError to common.StatusCodeResponse. +type ResponseErrorAdapter struct { + Err *restful.ResponseError +} + +func (r *ResponseErrorAdapter) Message() string { + return r.Err.Message +} + +func (r *ResponseErrorAdapter) Model() interface{} { + return r.Err.Model +} + +func (r *ResponseErrorAdapter) Code() int { + return r.Err.Code +} diff --git a/pkg/common/restfuladapter/route_adapter.go b/pkg/common/restfuladapter/route_adapter.go new file mode 100644 index 000000000..61759fc06 --- /dev/null +++ b/pkg/common/restfuladapter/route_adapter.go @@ -0,0 +1,67 @@ +package restfuladapter + +import ( + "github.com/emicklei/go-restful" + "k8s.io/kube-openapi/pkg/common" +) + +var _ common.Route = &RouteAdapter{} + +// RouteAdapter adapts a restful.Route to common.Route. +type RouteAdapter struct { + Route *restful.Route +} + +func (r *RouteAdapter) StatusCodeResponses() []common.StatusCodeResponse { + // go-restful uses the ResponseErrors field to contain both error and regular responses. + var responses []common.StatusCodeResponse + for _, res := range r.Route.ResponseErrors { + responses = append(responses, &ResponseErrorAdapter{&res}) + } + + return responses +} + +func (r *RouteAdapter) OperationName() string { + return r.Route.Operation +} + +func (r *RouteAdapter) Method() string { + return r.Route.Method +} + +func (r *RouteAdapter) Path() string { + return r.Route.Path +} + +func (r *RouteAdapter) Parameters() []common.Parameter { + var params []common.Parameter + for _, rParam := range r.Route.ParameterDocs { + params = append(params, &ParamAdapter{rParam}) + } + return params +} + +func (r *RouteAdapter) Description() string { + return r.Route.Doc +} + +func (r *RouteAdapter) Consumes() []string { + return r.Route.Consumes +} + +func (r *RouteAdapter) Produces() []string { + return r.Route.Produces +} + +func (r *RouteAdapter) Metadata() map[string]interface{} { + return r.Route.Metadata +} + +func (r *RouteAdapter) RequestPayloadSample() interface{} { + return r.Route.ReadSample +} + +func (r *RouteAdapter) ResponsePayloadSample() interface{} { + return r.Route.WriteSample +} diff --git a/pkg/common/restfuladapter/webservice_adapter.go b/pkg/common/restfuladapter/webservice_adapter.go new file mode 100644 index 000000000..e8ee6dee7 --- /dev/null +++ b/pkg/common/restfuladapter/webservice_adapter.go @@ -0,0 +1,34 @@ +package restfuladapter + +import ( + "github.com/emicklei/go-restful" + "k8s.io/kube-openapi/pkg/common" +) + +var _ common.RouteContainer = &WebServiceAdapter{} + +// WebServiceAdapter adapts a restful.WebService to common.RouteContainer. +type WebServiceAdapter struct { + WebService *restful.WebService +} + +func (r *WebServiceAdapter) RootPath() string { + return r.WebService.RootPath() +} + +func (r *WebServiceAdapter) PathParameters() []common.Parameter { + var params []common.Parameter + for _, rParam := range r.WebService.PathParameters() { + params = append(params, &ParamAdapter{rParam}) + } + return params +} + +func (r *WebServiceAdapter) Routes() []common.Route { + var routes []common.Route + for _, rRoute := range r.WebService.Routes() { + localRoute := rRoute + routes = append(routes, &RouteAdapter{&localRoute}) + } + return routes +} diff --git a/pkg/handler/handler.go b/pkg/handler/handler.go index 7338094cb..48cbdc470 100644 --- a/pkg/handler/handler.go +++ b/pkg/handler/handler.go @@ -36,6 +36,7 @@ import ( klog "k8s.io/klog/v2" "k8s.io/kube-openapi/pkg/builder" "k8s.io/kube-openapi/pkg/common" + "k8s.io/kube-openapi/pkg/common/restfuladapter" "k8s.io/kube-openapi/pkg/validation/spec" ) @@ -276,8 +277,16 @@ func (o *OpenAPIService) RegisterOpenAPIVersionedService(servePath string, handl // BuildAndRegisterOpenAPIVersionedService builds the spec and registers a handler to provide access to it. // Use this method if your OpenAPI spec is static. If you want to update the spec, use BuildOpenAPISpec then RegisterOpenAPIVersionedService. +// +// Deprecated: BuildAndRegisterOpenAPIVersionedServiceFromRoutes should be used instead. func BuildAndRegisterOpenAPIVersionedService(servePath string, webServices []*restful.WebService, config *common.Config, handler common.PathHandler) (*OpenAPIService, error) { - spec, err := builder.BuildOpenAPISpec(webServices, config) + return BuildAndRegisterOpenAPIVersionedServiceFromRoutes(servePath, restfuladapter.AdaptWebServices(webServices), config, handler) +} + +// BuildAndRegisterOpenAPIVersionedServiceFromRoutes builds the spec and registers a handler to provide access to it. +// Use this method if your OpenAPI spec is static. If you want to update the spec, use BuildOpenAPISpec then RegisterOpenAPIVersionedService. +func BuildAndRegisterOpenAPIVersionedServiceFromRoutes(servePath string, routeContainers []common.RouteContainer, config *common.Config, handler common.PathHandler) (*OpenAPIService, error) { + spec, err := builder.BuildOpenAPISpecFromRoutes(routeContainers, config) if err != nil { return nil, err } From eef04e76c109b0e3f55d613d33d3ce2c0577cb70 Mon Sep 17 00:00:00 2001 From: austin ce Date: Sun, 2 Jan 2022 15:34:43 -0500 Subject: [PATCH 2/6] Decouple REST Framework from v3 --- pkg/builder3/openapi.go | 118 ++++++++++++++++++++++++---------------- pkg/builder3/util.go | 10 ++-- 2 files changed, 76 insertions(+), 52 deletions(-) diff --git a/pkg/builder3/openapi.go b/pkg/builder3/openapi.go index 6d716ed58..662e0a7d6 100644 --- a/pkg/builder3/openapi.go +++ b/pkg/builder3/openapi.go @@ -21,6 +21,7 @@ import ( "fmt" restful "github.com/emicklei/go-restful" "k8s.io/kube-openapi/pkg/common" + "k8s.io/kube-openapi/pkg/common/restfuladapter" "k8s.io/kube-openapi/pkg/spec3" "k8s.io/kube-openapi/pkg/util" "k8s.io/kube-openapi/pkg/validation/spec" @@ -38,10 +39,10 @@ type openAPI struct { definitions map[string]common.OpenAPIDefinition } -func groupRoutesByPath(routes []restful.Route) map[string][]restful.Route { - pathToRoutes := make(map[string][]restful.Route) +func groupRoutesByPath(routes []common.Route) map[string][]common.Route { + pathToRoutes := make(map[string][]common.Route) for _, r := range routes { - pathToRoutes[r.Path] = append(pathToRoutes[r.Path], r) + pathToRoutes[r.Path()] = append(pathToRoutes[r.Path()], r) } return pathToRoutes } @@ -69,10 +70,10 @@ func (o *openAPI) buildResponse(model interface{}, description string, content [ return response, nil } -func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map[interface{}]*spec3.Parameter) (*spec3.Operation, error) { +func (o *openAPI) buildOperations(route common.Route, inPathCommonParamsMap map[interface{}]*spec3.Parameter) (*spec3.Operation, error) { ret := &spec3.Operation{ OperationProps: spec3.OperationProps{ - Description: route.Doc, + Description: route.Description(), Responses: &spec3.Responses{ ResponsesProps: spec3.ResponsesProps{ StatusCodeResponses: make(map[int]*spec3.Response), @@ -81,21 +82,21 @@ func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map }, } var err error - if ret.OperationId, ret.Tags, err = o.config.GetOperationIDAndTags(&route); err != nil { + if ret.OperationId, ret.Tags, err = o.config.GetOperationIDAndTagsFromRoute(route); err != nil { return ret, err } // Build responses - for _, resp := range route.ResponseErrors { - ret.Responses.StatusCodeResponses[resp.Code], err = o.buildResponse(resp.Model, resp.Message, route.Produces) + for _, resp := range route.StatusCodeResponses() { + ret.Responses.StatusCodeResponses[resp.Code()], err = o.buildResponse(resp.Model(), resp.Message(), route.Produces()) if err != nil { return ret, err } } // If there is no response but a write sample, assume that write sample is an http.StatusOK response. - if len(ret.Responses.StatusCodeResponses) == 0 && route.WriteSample != nil { - ret.Responses.StatusCodeResponses[http.StatusOK], err = o.buildResponse(route.WriteSample, "OK", route.Produces) + if len(ret.Responses.StatusCodeResponses) == 0 && route.ResponsePayloadSample() != nil { + ret.Responses.StatusCodeResponses[http.StatusOK], err = o.buildResponse(route.ResponsePayloadSample(), "OK", route.Produces()) if err != nil { return ret, err } @@ -104,10 +105,11 @@ func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map // TODO: Default response if needed. Common Response config ret.Parameters = make([]*spec3.Parameter, 0) - for _, param := range route.ParameterDocs { + params := route.Parameters() + for _, param := range params { _, isCommon := inPathCommonParamsMap[mapKeyFromParam(param)] - if !isCommon && param.Data().Kind != restful.BodyParameterKind { - openAPIParam, err := o.buildParameter(param.Data()) + if !isCommon && param.Kind() != common.BodyParameterKind { + openAPIParam, err := o.buildParameter(param) if err != nil { return ret, err } @@ -115,9 +117,9 @@ func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map } } - body, err := o.buildRequestBody(route.ParameterDocs, route.ReadSample) + body, err := o.buildRequestBody(params, route.RequestPayloadSample()) if err != nil { - return nil ,err + return nil, err } if body != nil { @@ -126,9 +128,9 @@ func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map return ret, nil } -func (o *openAPI) buildRequestBody(parameters []*restful.Parameter, bodySample interface{}) (*spec3.RequestBody, error) { +func (o *openAPI) buildRequestBody(parameters []common.Parameter, bodySample interface{}) (*spec3.RequestBody, error) { for _, param := range parameters { - if param.Data().Kind == restful.BodyParameterKind && bodySample != nil { + if param.Kind() == common.BodyParameterKind && bodySample != nil { schema, err := o.toSchema(util.GetCanonicalTypeName(bodySample)) if err != nil { return nil, err @@ -164,9 +166,22 @@ func newOpenAPI(config *common.Config) openAPI { }, }, } - if o.config.GetOperationIDAndTags == nil { - o.config.GetOperationIDAndTags = func(r *restful.Route) (string, []string, error) { - return r.Operation, nil, nil + + if o.config.GetOperationIDAndTagsFromRoute == nil { + // Map the deprecated handler to the common interface, if provided. + if o.config.GetOperationIDAndTags != nil { + o.config.GetOperationIDAndTagsFromRoute = func(r common.Route) (string, []string, error) { + restfulRouteAdapter, ok := r.(*restfuladapter.RouteAdapter) + if !ok { + return "", nil, fmt.Errorf("config.GetOperationIDAndTags specified but route is not a restful v1 Route") + } + + return o.config.GetOperationIDAndTags(restfulRouteAdapter.Route) + } + } else { + o.config.GetOperationIDAndTagsFromRoute = func(r common.Route) (string, []string, error) { + return r.OperationName(), nil, nil + } } } @@ -184,7 +199,7 @@ func newOpenAPI(config *common.Config) openAPI { return o } -func (o *openAPI) buildOpenAPISpec(webServices []*restful.WebService) error { +func (o *openAPI) buildOpenAPISpec(webServices []common.RouteContainer) error { pathsToIgnore := util.NewTrie(o.config.IgnorePrefixes) for _, w := range webServices { rootPath := w.RootPath() @@ -233,7 +248,7 @@ func (o *openAPI) buildOpenAPISpec(webServices []*restful.WebService) error { for _, route := range routes { op, _ := o.buildOperations(route, inPathCommonParamsMap) - switch strings.ToUpper(route.Method) { + switch strings.ToUpper(route.Method()) { case "GET": pathItem.Get = op case "POST": @@ -257,7 +272,15 @@ func (o *openAPI) buildOpenAPISpec(webServices []*restful.WebService) error { return nil } +// BuildOpenAPISpec builds OpenAPI v3 spec given a list of route containers and common.Config to customize it. +// +// Deprecated: BuildOpenAPISpecFromRoutes should be used instead. func BuildOpenAPISpec(webServices []*restful.WebService, config *common.Config) (*spec3.OpenAPI, error) { + return BuildOpenAPISpecFromRoutes(restfuladapter.AdaptWebServices(webServices), config) +} + +// BuildOpenAPISpecFromRoutes builds OpenAPI v3 spec given a list of route containers and common.Config to customize it. +func BuildOpenAPISpecFromRoutes(webServices []common.RouteContainer, config *common.Config) (*spec3.OpenAPI, error) { a := newOpenAPI(config) err := a.buildOpenAPISpec(webServices) if err != nil { @@ -266,29 +289,30 @@ func BuildOpenAPISpec(webServices []*restful.WebService, config *common.Config) return a.spec, nil } -func (o *openAPI) findCommonParameters(routes []restful.Route) (map[interface{}]*spec3.Parameter, error) { +func (o *openAPI) findCommonParameters(routes []common.Route) (map[interface{}]*spec3.Parameter, error) { commonParamsMap := make(map[interface{}]*spec3.Parameter, 0) paramOpsCountByName := make(map[interface{}]int, 0) - paramNameKindToDataMap := make(map[interface{}]restful.ParameterData, 0) + paramNameKindToDataMap := make(map[interface{}]common.Parameter, 0) for _, route := range routes { routeParamDuplicateMap := make(map[interface{}]bool) s := "" - for _, param := range route.ParameterDocs { - m, _ := json.Marshal(param.Data()) + params := route.Parameters() + for _, param := range params { + m, _ := json.Marshal(param) s += string(m) + "\n" key := mapKeyFromParam(param) if routeParamDuplicateMap[key] { - msg, _ := json.Marshal(route.ParameterDocs) - return commonParamsMap, fmt.Errorf("duplicate parameter %v for route %v, %v", param.Data().Name, string(msg), s) + msg, _ := json.Marshal(params) + return commonParamsMap, fmt.Errorf("duplicate parameter %v for route %v, %v", param.Name(), string(msg), s) } routeParamDuplicateMap[key] = true paramOpsCountByName[key]++ - paramNameKindToDataMap[key] = param.Data() + paramNameKindToDataMap[key] = param } } for key, count := range paramOpsCountByName { paramData := paramNameKindToDataMap[key] - if count == len(routes) && paramData.Kind != restful.BodyParameterKind { + if count == len(routes) && paramData.Kind() != common.BodyParameterKind { openAPIParam, err := o.buildParameter(paramData) if err != nil { return commonParamsMap, err @@ -299,10 +323,10 @@ func (o *openAPI) findCommonParameters(routes []restful.Route) (map[interface{}] return commonParamsMap, nil } -func (o *openAPI) buildParameters(restParam []*restful.Parameter) (ret []*spec3.Parameter, err error) { +func (o *openAPI) buildParameters(restParam []common.Parameter) (ret []*spec3.Parameter, err error) { ret = make([]*spec3.Parameter, len(restParam)) for i, v := range restParam { - ret[i], err = o.buildParameter(v.Data()) + ret[i], err = o.buildParameter(v) if err != nil { return ret, err } @@ -310,40 +334,40 @@ func (o *openAPI) buildParameters(restParam []*restful.Parameter) (ret []*spec3. return ret, nil } -func (o *openAPI) buildParameter(restParam restful.ParameterData) (ret *spec3.Parameter, err error) { +func (o *openAPI) buildParameter(restParam common.Parameter) (ret *spec3.Parameter, err error) { ret = &spec3.Parameter{ ParameterProps: spec3.ParameterProps{ - Name: restParam.Name, - Description: restParam.Description, - Required: restParam.Required, + Name: restParam.Name(), + Description: restParam.Description(), + Required: restParam.Required(), }, } - switch restParam.Kind { - case restful.BodyParameterKind: + switch restParam.Kind() { + case common.BodyParameterKind: return nil, nil - case restful.PathParameterKind: + case common.PathParameterKind: ret.In = "path" - if !restParam.Required { - return ret, fmt.Errorf("path parameters should be marked at required for parameter %v", restParam) + if !restParam.Required() { + return ret, fmt.Errorf("path parameters should be marked as required for parameter %v", restParam) } - case restful.QueryParameterKind: + case common.QueryParameterKind: ret.In = "query" - case restful.HeaderParameterKind: + case common.HeaderParameterKind: ret.In = "header" /* TODO: add support for the cookie param */ default: - return ret, fmt.Errorf("unsupported restful parameter kind : %v", restParam.Kind) + return ret, fmt.Errorf("unsupported restful parameter kind : %v", restParam.Kind()) } - openAPIType, openAPIFormat := common.OpenAPITypeFormat(restParam.DataType) + openAPIType, openAPIFormat := common.OpenAPITypeFormat(restParam.DataType()) if openAPIType == "" { - return ret, fmt.Errorf("non-body Restful parameter type should be a simple type, but got : %v", restParam.DataType) + return ret, fmt.Errorf("non-body Restful parameter type should be a simple type, but got : %v", restParam.DataType()) } ret.Schema = &spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{openAPIType}, Format: openAPIFormat, - UniqueItems: !restParam.AllowMultiple, + UniqueItems: !restParam.AllowMultiple(), }, } return ret, nil diff --git a/pkg/builder3/util.go b/pkg/builder3/util.go index d977bdf90..a8a90fa15 100644 --- a/pkg/builder3/util.go +++ b/pkg/builder3/util.go @@ -19,17 +19,17 @@ package builder3 import ( "sort" - "github.com/emicklei/go-restful" + "k8s.io/kube-openapi/pkg/common" "k8s.io/kube-openapi/pkg/spec3" ) -func mapKeyFromParam(param *restful.Parameter) interface{} { +func mapKeyFromParam(param common.Parameter) interface{} { return struct { Name string - Kind int + Kind common.ParameterKind }{ - Name: param.Data().Name, - Kind: param.Data().Kind, + Name: param.Name(), + Kind: param.Kind(), } } From e9ebba8d92031e0779d80953c269ebfc68da1020 Mon Sep 17 00:00:00 2001 From: austin ce Date: Mon, 27 Sep 2021 18:41:18 -0400 Subject: [PATCH 3/6] Decouple integretion tests from GOPATH The header file is loaded from the GOPATH k8s dir by default, which makes it impossible to develop if you do not have the local dependencies in your GOPATH. This commit ensures the header file is loaded from the project's directory. It also includes a cleanup of the README and bumps the timeout to 10s. Signed-off-by: austin ce --- test/integration/README.md | 4 +-- test/integration/integration_suite_test.go | 37 ++++++++++++---------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/test/integration/README.md b/test/integration/README.md index e5d3c7445..135f72553 100644 --- a/test/integration/README.md +++ b/test/integration/README.md @@ -19,7 +19,7 @@ on API rule violations. ```bash $ go run ../../cmd/openapi-gen/openapi-gen.go \ - -i "k8s.io/kube-openapi/test/integration/testdata/custom,k8s.io/kube-openapi/test/integration/testdata/listtype,k8s.io/kube-openapi/test/integration/testdata/maptype,k8s.io/kube-openapi/test/integration/testdata/structtype,k8s.io/kube-openapi/test/integration/testdata/dummytype,k8s.io/kube-openapi/test/integration/testdata/uniontype,k8s.io/kube-openapi/test/integration/testdata/defaults" \ + -i "k8s.io/kube-openapi/test/integration/testdata/custom,k8s.io/kube-openapi/test/integration/testdata/enumtype,k8s.io/kube-openapi/test/integration/testdata/listtype,k8s.io/kube-openapi/test/integration/testdata/maptype,k8s.io/kube-openapi/test/integration/testdata/structtype,k8s.io/kube-openapi/test/integration/testdata/dummytype,k8s.io/kube-openapi/test/integration/testdata/uniontype,k8s.io/kube-openapi/test/integration/testdata/defaults" \ -o pkg \ -p generated \ -O openapi_generated \ @@ -42,7 +42,7 @@ If you've created a new type, make sure you add it in `createWebServices()` in --- **NOTE:** -If you've created a new package, make sure you also add it to to the +If you've created a new package, make sure you also add it to the `inputDir` in `integration_suite_test.go`. --- diff --git a/test/integration/integration_suite_test.go b/test/integration/integration_suite_test.go index 13c55af82..fe3c9410a 100644 --- a/test/integration/integration_suite_test.go +++ b/test/integration/integration_suite_test.go @@ -29,9 +29,10 @@ import ( ) const ( - testdataDir = "./testdata" - testPkgDir = "k8s.io/kube-openapi/test/integration/testdata" - inputDir = testPkgDir + "/listtype" + + headerFilePath = "../../boilerplate/boilerplate.go.txt" + testdataDir = "./testdata" + testPkgDir = "k8s.io/kube-openapi/test/integration/testdata" + inputDir = testPkgDir + "/listtype" + "," + testPkgDir + "/maptype" + "," + testPkgDir + "/structtype" + "," + testPkgDir + "/dummytype" + @@ -39,17 +40,17 @@ const ( "," + testPkgDir + "/enumtype" + "," + testPkgDir + "/custom" + "," + testPkgDir + "/defaults" - outputBase = "pkg" - outputPackage = "generated" - outputBaseFileName = "openapi_generated" - generatedSwaggerFileName = "generated.v2.json" - generatedReportFileName = "generated.v2.report" - goldenSwaggerFileName = "golden.v2.json" - goldenReportFileName = "golden.v2.report" + outputBase = "pkg" + outputPackage = "generated" + outputBaseFileName = "openapi_generated" + generatedSwaggerFileName = "generated.v2.json" + generatedReportFileName = "generated.v2.report" + goldenSwaggerFileName = "golden.v2.json" + goldenReportFileName = "golden.v2.report" generatedOpenAPIv3FileName = "generated.v3.json" goldenOpenAPIv3Filename = "golden.v3.json" - timeoutSeconds = 5.0 + timeoutSeconds = 10.0 ) func TestGenerators(t *testing.T) { @@ -81,9 +82,9 @@ var _ = Describe("Open API Definitions Generation", func() { // Build the OpenAPI code generator. By("building openapi-gen") - binary_path, berr := gexec.Build("../../cmd/openapi-gen/openapi-gen.go") + binaryPath, berr := gexec.Build("../../cmd/openapi-gen/openapi-gen.go") Expect(berr).ShouldNot(HaveOccurred()) - openAPIGenPath = binary_path + openAPIGenPath = binaryPath // Run the OpenAPI code generator, creating OpenAPIDefinition code // to be compiled into builder. @@ -95,6 +96,7 @@ var _ = Describe("Open API Definitions Generation", func() { "-p", outputPackage, "-O", outputBaseFileName, "-r", gr, + "-h", headerFilePath, ) command.Dir = workingDirectory session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) @@ -103,13 +105,13 @@ var _ = Describe("Open API Definitions Generation", func() { By("writing swagger v2.0") // Create the OpenAPI swagger builder. - binary_path, berr = gexec.Build("./builder/main.go") + binaryPath, berr = gexec.Build("./builder/main.go") Expect(berr).ShouldNot(HaveOccurred()) // Execute the builder, generating an OpenAPI swagger file with definitions. gs := generatedFile(generatedSwaggerFileName) By("writing swagger to " + gs) - command = exec.Command(binary_path, gs) + command = exec.Command(binaryPath, gs) command.Dir = workingDirectory session, err = gexec.Start(command, GinkgoWriter, GinkgoWriter) Expect(err).ShouldNot(HaveOccurred()) @@ -117,13 +119,13 @@ var _ = Describe("Open API Definitions Generation", func() { By("writing OpenAPI v3.0") // Create the OpenAPI swagger builder. - binary_path, berr = gexec.Build("./builder3/main.go") + binaryPath, berr = gexec.Build("./builder3/main.go") Expect(berr).ShouldNot(HaveOccurred()) // Execute the builder, generating an OpenAPI swagger file with definitions. gov3 := generatedFile(generatedOpenAPIv3FileName) By("writing swagger to " + gov3) - command = exec.Command(binary_path, gov3) + command = exec.Command(binaryPath, gov3) command.Dir = workingDirectory session, err = gexec.Start(command, GinkgoWriter, GinkgoWriter) Expect(err).ShouldNot(HaveOccurred()) @@ -143,6 +145,7 @@ var _ = Describe("Open API Definitions Generation", func() { "-p", outputPackage, "-O", outputBaseFileName, "-r", testdataFile(goldenReportFileName), + "-h", headerFilePath, "--verify-only", ) command.Dir = workingDirectory From 41ddd1e7531d054316732fcbd6ed6213f4cb9bd0 Mon Sep 17 00:00:00 2001 From: austin ce Date: Tue, 11 Jan 2022 16:14:58 -0500 Subject: [PATCH 4/6] Take address of local loop var in route status code adapter --- pkg/common/restfuladapter/route_adapter.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/common/restfuladapter/route_adapter.go b/pkg/common/restfuladapter/route_adapter.go index 61759fc06..0622319c3 100644 --- a/pkg/common/restfuladapter/route_adapter.go +++ b/pkg/common/restfuladapter/route_adapter.go @@ -16,7 +16,8 @@ func (r *RouteAdapter) StatusCodeResponses() []common.StatusCodeResponse { // go-restful uses the ResponseErrors field to contain both error and regular responses. var responses []common.StatusCodeResponse for _, res := range r.Route.ResponseErrors { - responses = append(responses, &ResponseErrorAdapter{&res}) + localRes := res + responses = append(responses, &ResponseErrorAdapter{&localRes}) } return responses From 35b5ddc4848c55c10ac7658462d1fa8c75591ea4 Mon Sep 17 00:00:00 2001 From: austin ce Date: Tue, 11 Jan 2022 16:15:38 -0500 Subject: [PATCH 5/6] Add multiple status returns to int tests --- test/integration/README.md | 4 + .../pkg/generated/openapi_generated.go | 30 ++- test/integration/testdata/dummytype/alpha.go | 6 + test/integration/testdata/golden.v2.json | 252 +++++++++++++++--- test/integration/testdata/golden.v2.report | 2 + test/integration/testdata/golden.v3.json | 209 ++++++++++++--- test/integration/testutil/testutil.go | 79 ++++-- 7 files changed, 488 insertions(+), 94 deletions(-) diff --git a/test/integration/README.md b/test/integration/README.md index 135f72553..fd61d00ad 100644 --- a/test/integration/README.md +++ b/test/integration/README.md @@ -49,3 +49,7 @@ If you've created a new package, make sure you also add it to the ```bash $ go run builder/main.go testdata/golden.v2.json ``` + +```bash +$ go run builder3/main.go testdata/golden.v3.json +``` diff --git a/test/integration/pkg/generated/openapi_generated.go b/test/integration/pkg/generated/openapi_generated.go index 12c2f23ba..fd76ec7ae 100644 --- a/test/integration/pkg/generated/openapi_generated.go +++ b/test/integration/pkg/generated/openapi_generated.go @@ -17,7 +17,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Code generated by openapi-gen.go. DO NOT EDIT. +// Code generated by openapi-gen. DO NOT EDIT. // This file was autogenerated by openapi-gen. Do not edit it manually! @@ -40,6 +40,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "k8s.io/kube-openapi/test/integration/testdata/dummytype.Bar": schema_test_integration_testdata_dummytype_Bar(ref), "k8s.io/kube-openapi/test/integration/testdata/dummytype.Baz": schema_test_integration_testdata_dummytype_Baz(ref), "k8s.io/kube-openapi/test/integration/testdata/dummytype.Foo": schema_test_integration_testdata_dummytype_Foo(ref), + "k8s.io/kube-openapi/test/integration/testdata/dummytype.StatusError": schema_test_integration_testdata_dummytype_StatusError(ref), "k8s.io/kube-openapi/test/integration/testdata/dummytype.Waldo": schema_test_integration_testdata_dummytype_Waldo(ref), "k8s.io/kube-openapi/test/integration/testdata/enumtype.FruitsBasket": schema_test_integration_testdata_enumtype_FruitsBasket(ref), "k8s.io/kube-openapi/test/integration/testdata/listtype.AtomicList": schema_test_integration_testdata_listtype_AtomicList(ref), @@ -251,6 +252,33 @@ func schema_test_integration_testdata_dummytype_Foo(ref common.ReferenceCallback } } +func schema_test_integration_testdata_dummytype_StatusError(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "Code": { + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "Message": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"Code", "Message"}, + }, + }, + } +} + func schema_test_integration_testdata_dummytype_Waldo(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/test/integration/testdata/dummytype/alpha.go b/test/integration/testdata/dummytype/alpha.go index f3e50b4a5..6b2c348d0 100644 --- a/test/integration/testdata/dummytype/alpha.go +++ b/test/integration/testdata/dummytype/alpha.go @@ -34,3 +34,9 @@ type Baz struct { Violation bool ViolationBehind bool } + +// +k8s:openapi-gen=true +type StatusError struct { + Code int + Message string +} diff --git a/test/integration/testdata/golden.v2.json b/test/integration/testdata/golden.v2.json index d23da1051..978fd9b1c 100644 --- a/test/integration/testdata/golden.v2.json +++ b/test/integration/testdata/golden.v2.json @@ -5,6 +5,28 @@ "version": "1.0" }, "paths": { + "/test/custom": { + "post": { + "produces": [ + "application/json" + ], + "schemes": [ + "https" + ], + "operationId": "create-custom.Bah", + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/custom.Bah" + } + }, + "404": { + "$ref": "#/responses/NotFound" + } + } + } + }, "/test/custom/bac": { "get": { "produces": [ @@ -13,7 +35,7 @@ "schemes": [ "https" ], - "operationId": "func12", + "operationId": "get-custom.Bac", "responses": { "200": { "description": "OK", @@ -35,7 +57,7 @@ "schemes": [ "https" ], - "operationId": "func13", + "operationId": "get-custom.Bah", "responses": { "200": { "description": "OK", @@ -57,7 +79,7 @@ "schemes": [ "https" ], - "operationId": "func11", + "operationId": "get-custom.Bak", "responses": { "200": { "description": "OK", @@ -79,7 +101,7 @@ "schemes": [ "https" ], - "operationId": "func10", + "operationId": "get-custom.Bal", "responses": { "200": { "description": "OK", @@ -93,18 +115,18 @@ } } }, - "/test/defaults/defaulted": { - "get": { + "/test/defaults": { + "post": { "produces": [ "application/json" ], "schemes": [ "https" ], - "operationId": "func19", + "operationId": "create-defaults.Defaulted", "responses": { - "200": { - "description": "OK", + "201": { + "description": "Created", "schema": { "$ref": "#/definitions/defaults.Defaulted" } @@ -115,7 +137,7 @@ } } }, - "/test/dummytype/bar": { + "/test/defaults/defaulted": { "get": { "produces": [ "application/json" @@ -123,12 +145,12 @@ "schemes": [ "https" ], - "operationId": "func2", + "operationId": "get-defaults.Defaulted", "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/dummytype.Bar" + "$ref": "#/definitions/defaults.Defaulted" } }, "404": { @@ -137,29 +159,35 @@ } } }, - "/test/dummytype/baz": { - "get": { + "/test/dummytype": { + "post": { "produces": [ "application/json" ], "schemes": [ "https" ], - "operationId": "func3", + "operationId": "create-dummytype.Waldo", "responses": { - "200": { - "description": "OK", + "201": { + "description": "Created", "schema": { - "$ref": "#/definitions/dummytype.Baz" + "$ref": "#/definitions/dummytype.Waldo" } }, "404": { "$ref": "#/responses/NotFound" + }, + "500": { + "description": "Internal Service Error", + "schema": { + "$ref": "#/definitions/dummytype.StatusError" + } } } } }, - "/test/dummytype/foo": { + "/test/dummytype/bar": { "get": { "produces": [ "application/json" @@ -167,16 +195,60 @@ "schemes": [ "https" ], - "operationId": "func1", + "operationId": "get-dummytype.Bar", "responses": { - "200": { - "description": "OK", + "404": { + "$ref": "#/responses/NotFound" + }, + "500": { + "description": "Internal Service Error", "schema": { - "$ref": "#/definitions/dummytype.Foo" + "$ref": "#/definitions/dummytype.StatusError" } + } + } + } + }, + "/test/dummytype/baz": { + "get": { + "produces": [ + "application/json" + ], + "schemes": [ + "https" + ], + "operationId": "get-dummytype.Baz", + "responses": { + "404": { + "$ref": "#/responses/NotFound" }, + "500": { + "description": "Internal Service Error", + "schema": { + "$ref": "#/definitions/dummytype.StatusError" + } + } + } + } + }, + "/test/dummytype/foo": { + "get": { + "produces": [ + "application/json" + ], + "schemes": [ + "https" + ], + "operationId": "get-dummytype.Foo", + "responses": { "404": { "$ref": "#/responses/NotFound" + }, + "500": { + "description": "Internal Service Error", + "schema": { + "$ref": "#/definitions/dummytype.StatusError" + } } } } @@ -189,12 +261,34 @@ "schemes": [ "https" ], - "operationId": "func4", + "operationId": "get-dummytype.Waldo", "responses": { - "200": { - "description": "OK", + "404": { + "$ref": "#/responses/NotFound" + }, + "500": { + "description": "Internal Service Error", "schema": { - "$ref": "#/definitions/dummytype.Waldo" + "$ref": "#/definitions/dummytype.StatusError" + } + } + } + } + }, + "/test/listtype": { + "post": { + "produces": [ + "application/json" + ], + "schemes": [ + "https" + ], + "operationId": "create-listtype.SetList", + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/listtype.SetList" } }, "404": { @@ -211,7 +305,7 @@ "schemes": [ "https" ], - "operationId": "func5", + "operationId": "get-listtype.AtomicList", "responses": { "200": { "description": "OK", @@ -233,7 +327,7 @@ "schemes": [ "https" ], - "operationId": "func6", + "operationId": "get-listtype.MapList", "responses": { "200": { "description": "OK", @@ -255,7 +349,7 @@ "schemes": [ "https" ], - "operationId": "func7", + "operationId": "get-listtype.SetList", "responses": { "200": { "description": "OK", @@ -269,6 +363,28 @@ } } }, + "/test/maptype": { + "post": { + "produces": [ + "application/json" + ], + "schemes": [ + "https" + ], + "operationId": "create-maptype.AtomicMap", + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/maptype.AtomicMap" + } + }, + "404": { + "$ref": "#/responses/NotFound" + } + } + } + }, "/test/maptype/atomicmap": { "get": { "produces": [ @@ -277,7 +393,7 @@ "schemes": [ "https" ], - "operationId": "func15", + "operationId": "get-maptype.AtomicMap", "responses": { "200": { "description": "OK", @@ -299,7 +415,7 @@ "schemes": [ "https" ], - "operationId": "func14", + "operationId": "get-maptype.GranularMap", "responses": { "200": { "description": "OK", @@ -313,6 +429,28 @@ } } }, + "/test/structtype": { + "post": { + "produces": [ + "application/json" + ], + "schemes": [ + "https" + ], + "operationId": "create-structtype.DeclaredAtomicStruct", + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/structtype.DeclaredAtomicStruct" + } + }, + "404": { + "$ref": "#/responses/NotFound" + } + } + } + }, "/test/structtype/atomicstruct": { "get": { "produces": [ @@ -321,7 +459,7 @@ "schemes": [ "https" ], - "operationId": "func17", + "operationId": "get-structtype.AtomicStruct", "responses": { "200": { "description": "OK", @@ -343,7 +481,7 @@ "schemes": [ "https" ], - "operationId": "func18", + "operationId": "get-structtype.DeclaredAtomicStruct", "responses": { "200": { "description": "OK", @@ -365,7 +503,7 @@ "schemes": [ "https" ], - "operationId": "func16", + "operationId": "get-structtype.GranularStruct", "responses": { "200": { "description": "OK", @@ -379,6 +517,28 @@ } } }, + "/test/uniontype": { + "post": { + "produces": [ + "application/json" + ], + "schemes": [ + "https" + ], + "operationId": "create-uniontype.InlinedUnion", + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/uniontype.InlinedUnion" + } + }, + "404": { + "$ref": "#/responses/NotFound" + } + } + } + }, "/test/uniontype/inlinedunion": { "get": { "produces": [ @@ -387,7 +547,7 @@ "schemes": [ "https" ], - "operationId": "func9", + "operationId": "get-uniontype.InlinedUnion", "responses": { "200": { "description": "OK", @@ -409,7 +569,7 @@ "schemes": [ "https" ], - "operationId": "func8", + "operationId": "get-uniontype.TopLevelUnion", "responses": { "200": { "description": "OK", @@ -560,6 +720,24 @@ } } }, + "dummytype.StatusError": { + "type": "object", + "required": [ + "Code", + "Message" + ], + "properties": { + "Code": { + "type": "integer", + "format": "int32", + "default": 0 + }, + "Message": { + "type": "string", + "default": "" + } + } + }, "dummytype.Waldo": { "type": "object", "required": [ diff --git a/test/integration/testdata/golden.v2.report b/test/integration/testdata/golden.v2.report index e47a350c4..e00254ef3 100644 --- a/test/integration/testdata/golden.v2.report +++ b/test/integration/testdata/golden.v2.report @@ -14,6 +14,8 @@ API rule violation: names_match,k8s.io/kube-openapi/test/integration/testdata/du API rule violation: names_match,k8s.io/kube-openapi/test/integration/testdata/dummytype,Baz,ViolationBehind API rule violation: names_match,k8s.io/kube-openapi/test/integration/testdata/dummytype,Foo,First API rule violation: names_match,k8s.io/kube-openapi/test/integration/testdata/dummytype,Foo,Second +API rule violation: names_match,k8s.io/kube-openapi/test/integration/testdata/dummytype,StatusError,Code +API rule violation: names_match,k8s.io/kube-openapi/test/integration/testdata/dummytype,StatusError,Message API rule violation: names_match,k8s.io/kube-openapi/test/integration/testdata/dummytype,Waldo,First API rule violation: names_match,k8s.io/kube-openapi/test/integration/testdata/dummytype,Waldo,Second API rule violation: names_match,k8s.io/kube-openapi/test/integration/testdata/listtype,AtomicList,Field diff --git a/test/integration/testdata/golden.v3.json b/test/integration/testdata/golden.v3.json index 0ecb51da9..0f95c22e6 100644 --- a/test/integration/testdata/golden.v3.json +++ b/test/integration/testdata/golden.v3.json @@ -5,9 +5,26 @@ "version": "1.0" }, "paths": { + "/test/custom": { + "post": { + "operationId": "create-custom.Bah", + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom.Bah" + } + } + } + } + } + } + }, "/test/custom/bac": { "get": { - "operationId": "func12", + "operationId": "get-custom.Bac", "responses": { "200": { "description": "OK", @@ -24,7 +41,7 @@ }, "/test/custom/bah": { "get": { - "operationId": "func13", + "operationId": "get-custom.Bah", "responses": { "200": { "description": "OK", @@ -41,7 +58,7 @@ }, "/test/custom/bak": { "get": { - "operationId": "func11", + "operationId": "get-custom.Bak", "responses": { "200": { "description": "OK", @@ -58,7 +75,7 @@ }, "/test/custom/bal": { "get": { - "operationId": "func10", + "operationId": "get-custom.Bal", "responses": { "200": { "description": "OK", @@ -73,9 +90,26 @@ } } }, + "/test/defaults": { + "post": { + "operationId": "create-defaults.Defaulted", + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/defaults.Defaulted" + } + } + } + } + } + } + }, "/test/defaults/defaulted": { "get": { - "operationId": "func19", + "operationId": "get-defaults.Defaulted", "responses": { "200": { "description": "OK", @@ -90,16 +124,43 @@ } } }, + "/test/dummytype": { + "post": { + "operationId": "create-dummytype.Waldo", + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dummytype.Waldo" + } + } + } + }, + "500": { + "description": "Internal Service Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dummytype.StatusError" + } + } + } + } + } + } + }, "/test/dummytype/bar": { "get": { - "operationId": "func2", + "operationId": "get-dummytype.Bar", "responses": { - "200": { - "description": "OK", + "500": { + "description": "Internal Service Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/dummytype.Bar" + "$ref": "#/components/schemas/dummytype.StatusError" } } } @@ -109,14 +170,14 @@ }, "/test/dummytype/baz": { "get": { - "operationId": "func3", + "operationId": "get-dummytype.Baz", "responses": { - "200": { - "description": "OK", + "500": { + "description": "Internal Service Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/dummytype.Baz" + "$ref": "#/components/schemas/dummytype.StatusError" } } } @@ -126,14 +187,14 @@ }, "/test/dummytype/foo": { "get": { - "operationId": "func1", + "operationId": "get-dummytype.Foo", "responses": { - "200": { - "description": "OK", + "500": { + "description": "Internal Service Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/dummytype.Foo" + "$ref": "#/components/schemas/dummytype.StatusError" } } } @@ -143,14 +204,31 @@ }, "/test/dummytype/waldo": { "get": { - "operationId": "func4", + "operationId": "get-dummytype.Waldo", "responses": { - "200": { - "description": "OK", + "500": { + "description": "Internal Service Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/dummytype.Waldo" + "$ref": "#/components/schemas/dummytype.StatusError" + } + } + } + } + } + } + }, + "/test/listtype": { + "post": { + "operationId": "create-listtype.SetList", + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/listtype.SetList" } } } @@ -160,7 +238,7 @@ }, "/test/listtype/atomiclist": { "get": { - "operationId": "func5", + "operationId": "get-listtype.AtomicList", "responses": { "200": { "description": "OK", @@ -177,7 +255,7 @@ }, "/test/listtype/maplist": { "get": { - "operationId": "func6", + "operationId": "get-listtype.MapList", "responses": { "200": { "description": "OK", @@ -194,7 +272,7 @@ }, "/test/listtype/setlist": { "get": { - "operationId": "func7", + "operationId": "get-listtype.SetList", "responses": { "200": { "description": "OK", @@ -209,9 +287,26 @@ } } }, + "/test/maptype": { + "post": { + "operationId": "create-maptype.AtomicMap", + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/maptype.AtomicMap" + } + } + } + } + } + } + }, "/test/maptype/atomicmap": { "get": { - "operationId": "func15", + "operationId": "get-maptype.AtomicMap", "responses": { "200": { "description": "OK", @@ -228,7 +323,7 @@ }, "/test/maptype/granularmap": { "get": { - "operationId": "func14", + "operationId": "get-maptype.GranularMap", "responses": { "200": { "description": "OK", @@ -243,9 +338,26 @@ } } }, + "/test/structtype": { + "post": { + "operationId": "create-structtype.DeclaredAtomicStruct", + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/structtype.DeclaredAtomicStruct" + } + } + } + } + } + } + }, "/test/structtype/atomicstruct": { "get": { - "operationId": "func17", + "operationId": "get-structtype.AtomicStruct", "responses": { "200": { "description": "OK", @@ -262,7 +374,7 @@ }, "/test/structtype/declaredatomicstruct": { "get": { - "operationId": "func18", + "operationId": "get-structtype.DeclaredAtomicStruct", "responses": { "200": { "description": "OK", @@ -279,7 +391,7 @@ }, "/test/structtype/granularstruct": { "get": { - "operationId": "func16", + "operationId": "get-structtype.GranularStruct", "responses": { "200": { "description": "OK", @@ -294,9 +406,26 @@ } } }, + "/test/uniontype": { + "post": { + "operationId": "create-uniontype.InlinedUnion", + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/uniontype.InlinedUnion" + } + } + } + } + } + } + }, "/test/uniontype/inlinedunion": { "get": { - "operationId": "func9", + "operationId": "get-uniontype.InlinedUnion", "responses": { "200": { "description": "OK", @@ -313,7 +442,7 @@ }, "/test/uniontype/toplevelunion": { "get": { - "operationId": "func8", + "operationId": "get-uniontype.TopLevelUnion", "responses": { "200": { "description": "OK", @@ -472,6 +601,24 @@ } } }, + "dummytype.StatusError": { + "type": "object", + "required": [ + "Code", + "Message" + ], + "properties": { + "Code": { + "type": "integer", + "format": "int32", + "default": 0 + }, + "Message": { + "type": "string", + "default": "" + } + } + }, "dummytype.Waldo": { "type": "object", "required": [ diff --git a/test/integration/testutil/testutil.go b/test/integration/testutil/testutil.go index 791fa9a0c..33cba4d14 100644 --- a/test/integration/testutil/testutil.go +++ b/test/integration/testutil/testutil.go @@ -18,12 +18,11 @@ package testutil import ( "fmt" - "strings" - "github.com/emicklei/go-restful" "k8s.io/kube-openapi/pkg/common" "k8s.io/kube-openapi/pkg/util" "k8s.io/kube-openapi/pkg/validation/spec" + "strings" ) // CreateOpenAPIBuilderConfig hard-codes some values in the API builder @@ -51,32 +50,38 @@ func CreateOpenAPIBuilderConfig() *common.Config { } } -// CreateWebServices hard-codes a simple WebService which only defines a GET path +// CreateWebServices hard-codes a simple WebService which only defines a GET and POST path // for testing. func CreateWebServices() []*restful.WebService { w := new(restful.WebService) - w.Route(buildRouteForType(w, "dummytype", "Foo")) - w.Route(buildRouteForType(w, "dummytype", "Bar")) - w.Route(buildRouteForType(w, "dummytype", "Baz")) - w.Route(buildRouteForType(w, "dummytype", "Waldo")) - w.Route(buildRouteForType(w, "listtype", "AtomicList")) - w.Route(buildRouteForType(w, "listtype", "MapList")) - w.Route(buildRouteForType(w, "listtype", "SetList")) - w.Route(buildRouteForType(w, "uniontype", "TopLevelUnion")) - w.Route(buildRouteForType(w, "uniontype", "InlinedUnion")) - w.Route(buildRouteForType(w, "custom", "Bal")) - w.Route(buildRouteForType(w, "custom", "Bak")) - w.Route(buildRouteForType(w, "custom", "Bac")) - w.Route(buildRouteForType(w, "custom", "Bah")) - w.Route(buildRouteForType(w, "maptype", "GranularMap")) - w.Route(buildRouteForType(w, "maptype", "AtomicMap")) - w.Route(buildRouteForType(w, "structtype", "GranularStruct")) - w.Route(buildRouteForType(w, "structtype", "AtomicStruct")) - w.Route(buildRouteForType(w, "structtype", "DeclaredAtomicStruct")) - w.Route(buildRouteForType(w, "defaults", "Defaulted")) + addRoutes(w, buildRouteForType(w, "dummytype", "Foo")...) + addRoutes(w, buildRouteForType(w, "dummytype", "Bar")...) + addRoutes(w, buildRouteForType(w, "dummytype", "Baz")...) + addRoutes(w, buildRouteForType(w, "dummytype", "Waldo")...) + addRoutes(w, buildRouteForType(w, "listtype", "AtomicList")...) + addRoutes(w, buildRouteForType(w, "listtype", "MapList")...) + addRoutes(w, buildRouteForType(w, "listtype", "SetList")...) + addRoutes(w, buildRouteForType(w, "uniontype", "TopLevelUnion")...) + addRoutes(w, buildRouteForType(w, "uniontype", "InlinedUnion")...) + addRoutes(w, buildRouteForType(w, "custom", "Bal")...) + addRoutes(w, buildRouteForType(w, "custom", "Bak")...) + addRoutes(w, buildRouteForType(w, "custom", "Bac")...) + addRoutes(w, buildRouteForType(w, "custom", "Bah")...) + addRoutes(w, buildRouteForType(w, "maptype", "GranularMap")...) + addRoutes(w, buildRouteForType(w, "maptype", "AtomicMap")...) + addRoutes(w, buildRouteForType(w, "structtype", "GranularStruct")...) + addRoutes(w, buildRouteForType(w, "structtype", "AtomicStruct")...) + addRoutes(w, buildRouteForType(w, "structtype", "DeclaredAtomicStruct")...) + addRoutes(w, buildRouteForType(w, "defaults", "Defaulted")...) return []*restful.WebService{w} } +func addRoutes(ws *restful.WebService, routes ...*restful.RouteBuilder) { + for _, r := range routes { + ws.Route(r) + } +} + // Implements OpenAPICanonicalTypeNamer var _ = util.OpenAPICanonicalTypeNamer(&typeNamer{}) @@ -89,13 +94,37 @@ func (t *typeNamer) OpenAPICanonicalTypeName() string { return fmt.Sprintf("k8s.io/kube-openapi/test/integration/testdata/%s.%s", t.pkg, t.name) } -func buildRouteForType(ws *restful.WebService, pkg, name string) *restful.RouteBuilder { +func buildRouteForType(ws *restful.WebService, pkg, name string) []*restful.RouteBuilder { namer := typeNamer{ pkg: pkg, name: name, } - return ws.GET(fmt.Sprintf("test/%s/%s", pkg, strings.ToLower(name))). + + var routes []*restful.RouteBuilder + + routes = append(routes, ws.GET(fmt.Sprintf("test/%s/%s", pkg, strings.ToLower(name))). + Operation(fmt.Sprintf("get-%s.%s", pkg, name)). Produces("application/json"). To(func(*restful.Request, *restful.Response) {}). - Writes(&namer) + Writes(&namer)) + + routes = append(routes, ws.POST(fmt.Sprintf("test/%s", pkg)). + Operation(fmt.Sprintf("create-%s.%s", pkg, name)). + Produces("application/json"). + To(func(*restful.Request, *restful.Response) {}). + Returns(201, "Created", &namer). + Writes(&namer)) + + if pkg == "dummytype" { + statusErrType := typeNamer{ + pkg: "dummytype", + name: "StatusError", + } + + for _, route := range routes { + route.Returns(500, "Internal Service Error", &statusErrType) + } + } + + return routes } From 0c8fa980bd5dc8065b4887240b1f58054d05c1ea Mon Sep 17 00:00:00 2001 From: austin ce Date: Tue, 11 Jan 2022 16:23:09 -0500 Subject: [PATCH 6/6] Testutil cleanup --- test/integration/testutil/testutil.go | 32 +++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/test/integration/testutil/testutil.go b/test/integration/testutil/testutil.go index 33cba4d14..ac23a9b1f 100644 --- a/test/integration/testutil/testutil.go +++ b/test/integration/testutil/testutil.go @@ -18,11 +18,12 @@ package testutil import ( "fmt" + "strings" + "github.com/emicklei/go-restful" "k8s.io/kube-openapi/pkg/common" "k8s.io/kube-openapi/pkg/util" "k8s.io/kube-openapi/pkg/validation/spec" - "strings" ) // CreateOpenAPIBuilderConfig hard-codes some values in the API builder @@ -50,7 +51,7 @@ func CreateOpenAPIBuilderConfig() *common.Config { } } -// CreateWebServices hard-codes a simple WebService which only defines a GET and POST path +// CreateWebServices hard-codes a simple WebService which only defines a GET and POST paths // for testing. func CreateWebServices() []*restful.WebService { w := new(restful.WebService) @@ -100,20 +101,19 @@ func buildRouteForType(ws *restful.WebService, pkg, name string) []*restful.Rout name: name, } - var routes []*restful.RouteBuilder - - routes = append(routes, ws.GET(fmt.Sprintf("test/%s/%s", pkg, strings.ToLower(name))). - Operation(fmt.Sprintf("get-%s.%s", pkg, name)). - Produces("application/json"). - To(func(*restful.Request, *restful.Response) {}). - Writes(&namer)) - - routes = append(routes, ws.POST(fmt.Sprintf("test/%s", pkg)). - Operation(fmt.Sprintf("create-%s.%s", pkg, name)). - Produces("application/json"). - To(func(*restful.Request, *restful.Response) {}). - Returns(201, "Created", &namer). - Writes(&namer)) + routes := []*restful.RouteBuilder{ + ws.GET(fmt.Sprintf("test/%s/%s", pkg, strings.ToLower(name))). + Operation(fmt.Sprintf("get-%s.%s", pkg, name)). + Produces("application/json"). + To(func(*restful.Request, *restful.Response) {}). + Writes(&namer), + ws.POST(fmt.Sprintf("test/%s", pkg)). + Operation(fmt.Sprintf("create-%s.%s", pkg, name)). + Produces("application/json"). + To(func(*restful.Request, *restful.Response) {}). + Returns(201, "Created", &namer). + Writes(&namer), + } if pkg == "dummytype" { statusErrType := typeNamer{