代码拉取完成,页面将自动刷新
/*
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
// Note: Any reference to swagger in this document is to swagger 1.2 spec.
import (
"fmt"
"net/http"
"net/url"
"reflect"
"strconv"
"strings"
"github.com/emicklei/go-restful"
"github.com/emicklei/go-restful/swagger"
"github.com/go-openapi/loads"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
"k8s.io/kubernetes/pkg/util/json"
)
const (
// By convention, the Swagger specification file is named swagger.json
OpenAPIServePath = "/swagger.json"
OpenAPIVersion = "2.0"
)
// Config is set of configuration for openAPI spec generation.
type Config struct {
// SwaggerConfig is set of configuration for go-restful swagger spec generation. Currently
// openAPI implementation depends on go-restful to generate models.
SwaggerConfig *swagger.Config
// Info is general information about the API.
Info *spec.Info
// DefaultResponse will be used if an operation does not have any responses listed. It
// will show up as ... "responses" : {"default" : $DefaultResponse} in swagger spec.
DefaultResponse *spec.Response
// List of webservice's path prefixes to ignore
IgnorePrefixes []string
}
type openAPI struct {
config *Config
swagger *spec.Swagger
protocolList []string
}
// RegisterOpenAPIService registers a handler to provides standard OpenAPI specification.
func RegisterOpenAPIService(config *Config, containers *restful.Container) (err error) {
var _ = loads.Spec
var _ = strfmt.ParseDuration
var _ = validate.FormatOf
o := openAPI{
config: config,
}
err = o.buildSwaggerSpec()
if err != nil {
return err
}
containers.ServeMux.HandleFunc(OpenAPIServePath, func(w http.ResponseWriter, r *http.Request) {
resp := restful.NewResponse(w)
if r.URL.Path != OpenAPIServePath {
resp.WriteErrorString(http.StatusNotFound, "Path not found!")
}
resp.WriteAsJson(o.swagger)
})
return nil
}
func (o *openAPI) buildSwaggerSpec() (err error) {
if o.swagger != nil {
return fmt.Errorf("Swagger spec is already built. Duplicate call to buildSwaggerSpec is not allowed.")
}
o.protocolList, err = o.buildProtocolList()
if err != nil {
return err
}
definitions, err := o.buildDefinitions()
if err != nil {
return err
}
paths, err := o.buildPaths()
if err != nil {
return err
}
o.swagger = &spec.Swagger{
SwaggerProps: spec.SwaggerProps{
Swagger: OpenAPIVersion,
Definitions: definitions,
Paths: &paths,
Info: o.config.Info,
},
}
return nil
}
// buildDefinitions construct OpenAPI definitions using go-restful's swagger 1.2 generated models.
func (o *openAPI) buildDefinitions() (definitions spec.Definitions, err error) {
definitions = spec.Definitions{}
for _, decl := range swagger.NewSwaggerBuilder(*o.config.SwaggerConfig).ProduceAllDeclarations() {
for _, swaggerModel := range decl.Models.List {
_, ok := definitions[swaggerModel.Name]
if ok {
// TODO(mbohlool): decide what to do with repeated models
// The best way is to make sure they have the same content and
// fail otherwise.
continue
}
definitions[swaggerModel.Name], err = buildModel(swaggerModel.Model)
if err != nil {
return definitions, err
}
}
}
return definitions, nil
}
func buildModel(swaggerModel swagger.Model) (ret spec.Schema, err error) {
ret = spec.Schema{
// SchemaProps.SubTypes is not used in go-restful, ignoring.
SchemaProps: spec.SchemaProps{
Description: swaggerModel.Description,
Required: swaggerModel.Required,
Properties: make(map[string]spec.Schema),
},
SwaggerSchemaProps: spec.SwaggerSchemaProps{
Discriminator: swaggerModel.Discriminator,
},
}
for _, swaggerProp := range swaggerModel.Properties.List {
if _, ok := ret.Properties[swaggerProp.Name]; ok {
return ret, fmt.Errorf("Duplicate property in swagger 1.2 spec: %v", swaggerProp.Name)
}
ret.Properties[swaggerProp.Name], err = buildProperty(swaggerProp)
if err != nil {
return ret, err
}
}
return ret, nil
}
// buildProperty converts a swagger 1.2 property to an open API property.
func buildProperty(swaggerProperty swagger.NamedModelProperty) (openAPIProperty spec.Schema, err error) {
if swaggerProperty.Property.Ref != nil {
return spec.Schema{
SchemaProps: spec.SchemaProps{
Ref: spec.MustCreateRef("#/definitions/" + *swaggerProperty.Property.Ref),
},
}, nil
}
openAPIProperty = spec.Schema{
SchemaProps: spec.SchemaProps{
Description: swaggerProperty.Property.Description,
Default: getDefaultValue(swaggerProperty.Property.DefaultValue),
Enum: make([]interface{}, len(swaggerProperty.Property.Enum)),
},
}
for i, e := range swaggerProperty.Property.Enum {
openAPIProperty.Enum[i] = e
}
openAPIProperty.Minimum, err = getFloat64OrNil(swaggerProperty.Property.Minimum)
if err != nil {
return spec.Schema{}, err
}
openAPIProperty.Maximum, err = getFloat64OrNil(swaggerProperty.Property.Maximum)
if err != nil {
return spec.Schema{}, err
}
if swaggerProperty.Property.UniqueItems != nil {
openAPIProperty.UniqueItems = *swaggerProperty.Property.UniqueItems
}
if swaggerProperty.Property.Items != nil {
if swaggerProperty.Property.Items.Ref != nil {
openAPIProperty.Items = &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Ref: spec.MustCreateRef("#/definitions/" + *swaggerProperty.Property.Items.Ref),
},
},
}
} else {
openAPIProperty.Items = &spec.SchemaOrArray{
Schema: &spec.Schema{},
}
openAPIProperty.Items.Schema.Type, openAPIProperty.Items.Schema.Format, err =
buildType(swaggerProperty.Property.Items.Type, swaggerProperty.Property.Items.Format)
if err != nil {
return spec.Schema{}, err
}
}
}
openAPIProperty.Type, openAPIProperty.Format, err =
buildType(swaggerProperty.Property.Type, swaggerProperty.Property.Format)
if err != nil {
return spec.Schema{}, err
}
return openAPIProperty, nil
}
// buildPaths builds OpenAPI paths using go-restful's web services.
func (o *openAPI) buildPaths() (spec.Paths, error) {
paths := spec.Paths{
Paths: make(map[string]spec.PathItem),
}
pathsToIgnore := createTrie(o.config.IgnorePrefixes)
duplicateOpId := make(map[string]bool)
// Find duplicate operation IDs.
for _, service := range o.config.SwaggerConfig.WebServices {
if pathsToIgnore.HasPrefix(service.RootPath()) {
continue
}
for _, route := range service.Routes() {
_, exists := duplicateOpId[route.Operation]
duplicateOpId[route.Operation] = exists
}
}
for _, w := range o.config.SwaggerConfig.WebServices {
rootPath := w.RootPath()
if pathsToIgnore.HasPrefix(rootPath) {
continue
}
commonParams, err := buildParameters(w.PathParameters())
if err != nil {
return paths, err
}
for path, routes := range groupRoutesByPath(w.Routes()) {
// go-swagger has special variable difinition {$NAME:*} that can only be
// used at the end of the path and it is not recognized by OpenAPI.
if strings.HasSuffix(path, ":*}") {
path = path[:len(path)-3] + "}"
}
inPathCommonParamsMap, err := findCommonParameters(routes)
if err != nil {
return paths, err
}
pathItem, exists := paths.Paths[path]
if exists {
return paths, fmt.Errorf("Duplicate webservice route has been found for path: %v", path)
}
pathItem = spec.PathItem{
PathItemProps: spec.PathItemProps{
Parameters: make([]spec.Parameter, 0),
},
}
// add web services's parameters as well as any parameters appears in all ops, as common parameters
for _, p := range commonParams {
pathItem.Parameters = append(pathItem.Parameters, p)
}
for _, p := range inPathCommonParamsMap {
pathItem.Parameters = append(pathItem.Parameters, p)
}
for _, route := range routes {
op, err := o.buildOperations(route, inPathCommonParamsMap)
if err != nil {
return paths, err
}
if duplicateOpId[op.ID] {
// Repeated Operation IDs are not allowed in OpenAPI spec but if
// an OperationID is empty, client generators will infer the ID
// from the path and method of operation.
op.ID = ""
}
switch strings.ToUpper(route.Method) {
case "GET":
pathItem.Get = op
case "POST":
pathItem.Post = op
case "HEAD":
pathItem.Head = op
case "PUT":
pathItem.Put = op
case "DELETE":
pathItem.Delete = op
case "OPTIONS":
pathItem.Options = op
case "PATCH":
pathItem.Patch = op
}
}
paths.Paths[path] = pathItem
}
}
return paths, nil
}
// buildProtocolList returns list of accepted protocols for this web service. If web service url has no protocol, it
// will default to http.
func (o *openAPI) buildProtocolList() ([]string, error) {
uri, err := url.Parse(o.config.SwaggerConfig.WebServicesUrl)
if err != nil {
return []string{}, err
}
if uri.Scheme != "" {
return []string{uri.Scheme}, nil
} else {
return []string{"http"}, nil
}
}
// buildOperations builds operations for each webservice path
func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map[interface{}]spec.Parameter) (*spec.Operation, error) {
ret := &spec.Operation{
OperationProps: spec.OperationProps{
Description: route.Doc,
Consumes: route.Consumes,
Produces: route.Produces,
ID: route.Operation,
Schemes: o.protocolList,
Responses: &spec.Responses{
ResponsesProps: spec.ResponsesProps{
StatusCodeResponses: make(map[int]spec.Response),
},
},
},
}
for _, resp := range route.ResponseErrors {
ret.Responses.StatusCodeResponses[resp.Code] = spec.Response{
ResponseProps: spec.ResponseProps{
Description: resp.Message,
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Ref: spec.MustCreateRef("#/definitions/" + reflect.TypeOf(resp.Model).String()),
},
},
},
}
}
if len(ret.Responses.StatusCodeResponses) == 0 {
ret.Responses.Default = o.config.DefaultResponse
}
ret.Parameters = make([]spec.Parameter, 0)
for _, param := range route.ParameterDocs {
_, isCommon := inPathCommonParamsMap[mapKeyFromParam(param)]
if !isCommon {
openAPIParam, err := buildParameter(param.Data())
if err != nil {
return ret, err
}
ret.Parameters = append(ret.Parameters, openAPIParam)
}
}
return ret, nil
}
func groupRoutesByPath(routes []restful.Route) (ret map[string][]restful.Route) {
ret = make(map[string][]restful.Route)
for _, r := range routes {
route, exists := ret[r.Path]
if !exists {
route = make([]restful.Route, 0, 1)
}
ret[r.Path] = append(route, r)
}
return ret
}
func mapKeyFromParam(param *restful.Parameter) interface{} {
return struct {
Name string
Kind int
}{
Name: param.Data().Name,
Kind: param.Data().Kind,
}
}
func findCommonParameters(routes []restful.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)
for _, route := range routes {
routeParamDuplicateMap := make(map[interface{}]bool)
s := ""
for _, param := range route.ParameterDocs {
m, _ := json.Marshal(param.Data())
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)
}
routeParamDuplicateMap[key] = true
paramOpsCountByName[key]++
paramNameKindToDataMap[key] = param.Data()
}
}
for key, count := range paramOpsCountByName {
if count == len(routes) {
openAPIParam, err := buildParameter(paramNameKindToDataMap[key])
if err != nil {
return commonParamsMap, err
}
commonParamsMap[key] = openAPIParam
}
}
return commonParamsMap, nil
}
func buildParameter(restParam restful.ParameterData) (ret spec.Parameter, err error) {
ret = spec.Parameter{
ParamProps: spec.ParamProps{
Name: restParam.Name,
Description: restParam.Description,
Required: restParam.Required,
},
}
switch restParam.Kind {
case restful.BodyParameterKind:
ret.In = "body"
ret.Schema = &spec.Schema{
SchemaProps: spec.SchemaProps{
Ref: spec.MustCreateRef("#/definitions/" + restParam.DataType),
},
}
return ret, nil
case restful.PathParameterKind:
ret.In = "path"
if !restParam.Required {
return ret, fmt.Errorf("Path parameters should be marked at required for parameter %v", restParam)
}
case restful.QueryParameterKind:
ret.In = "query"
case restful.HeaderParameterKind:
ret.In = "header"
case restful.FormParameterKind:
ret.In = "form"
default:
return ret, fmt.Errorf("Unknown restful operation kind : %v", restParam.Kind)
}
if !isSimpleDataType(restParam.DataType) {
return ret, fmt.Errorf("Restful DataType should be a simple type, but got : %v", restParam.DataType)
}
ret.Type = restParam.DataType
ret.Format = restParam.DataFormat
ret.UniqueItems = !restParam.AllowMultiple
// TODO(mbohlool): make sure the type of default value matches Type
if restParam.DefaultValue != "" {
ret.Default = restParam.DefaultValue
}
return ret, nil
}
func buildParameters(restParam []*restful.Parameter) (ret []spec.Parameter, err error) {
ret = make([]spec.Parameter, len(restParam))
for i, v := range restParam {
ret[i], err = buildParameter(v.Data())
if err != nil {
return ret, err
}
}
return ret, nil
}
func isSimpleDataType(typeName string) bool {
switch typeName {
// Note that "file" intentionally kept out of this list as it is not being used.
// "file" type has more requirements.
case "string", "number", "integer", "boolean", "array":
return true
}
return false
}
func getFloat64OrNil(str string) (*float64, error) {
if len(str) > 0 {
num, err := strconv.ParseFloat(str, 64)
return &num, err
}
return nil, nil
}
// TODO(mbohlool): Convert default value type to the type of parameter
func getDefaultValue(str swagger.Special) interface{} {
if len(str) > 0 {
return str
}
return nil
}
func buildType(swaggerType *string, swaggerFormat string) ([]string, string, error) {
if swaggerType == nil {
return []string{}, "", nil
}
switch *swaggerType {
case "integer", "number", "string", "boolean", "array", "object", "file":
return []string{*swaggerType}, swaggerFormat, nil
case "int":
return []string{"integer"}, "int32", nil
case "long":
return []string{"integer"}, "int64", nil
case "float", "double":
return []string{"number"}, *swaggerType, nil
case "byte", "date", "datetime", "date-time":
return []string{"string"}, *swaggerType, nil
default:
return []string{}, "", fmt.Errorf("Unrecognized swagger 1.2 type : %v, %v", swaggerType, swaggerFormat)
}
}
// A simple trie implementation with Add an HasPrefix methods only.
type trie struct {
children map[byte]*trie
}
func createTrie(list []string) trie {
ret := trie{
children: make(map[byte]*trie),
}
for _, v := range list {
ret.Add(v)
}
return ret
}
func (t *trie) Add(v string) {
root := t
for _, b := range []byte(v) {
child, exists := root.children[b]
if !exists {
child = new(trie)
child.children = make(map[byte]*trie)
root.children[b] = child
}
root = child
}
}
func (t *trie) HasPrefix(v string) bool {
root := t
for _, b := range []byte(v) {
child, exists := root.children[b]
if !exists {
return false
}
root = child
}
return true
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。