rebase: bump k8s.io/kubernetes from 1.26.2 to 1.27.2

Bumps [k8s.io/kubernetes](https://github.com/kubernetes/kubernetes) from 1.26.2 to 1.27.2.
- [Release notes](https://github.com/kubernetes/kubernetes/releases)
- [Commits](https://github.com/kubernetes/kubernetes/compare/v1.26.2...v1.27.2)

---
updated-dependencies:
- dependency-name: k8s.io/kubernetes
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2023-05-29 21:03:29 +00:00
committed by mergify[bot]
parent 0e79135419
commit 07b05616a0
1072 changed files with 208716 additions and 198880 deletions

20
vendor/k8s.io/kube-openapi/pkg/builder/doc.go generated vendored Normal file
View File

@ -0,0 +1,20 @@
/*
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 builder contains code to generate OpenAPI discovery spec (which
// initial version of it also known as Swagger 2.0).
// For more details: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md
package builder

468
vendor/k8s.io/kube-openapi/pkg/builder/openapi.go generated vendored Normal file
View File

@ -0,0 +1,468 @@
/*
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 builder
import (
"encoding/json"
"fmt"
"net/http"
"strings"
restful "github.com/emicklei/go-restful/v3"
"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"
)
const (
OpenAPIVersion = "2.0"
)
type openAPI struct {
config *common.Config
swagger *spec.Swagger
protocolList []string
definitions map[string]common.OpenAPIDefinition
}
// 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(routeContainers)
if err != nil {
return nil, err
}
return o.finalizeSwagger()
}
// BuildOpenAPIDefinitionsForResource builds a partial OpenAPI spec given a sample object and common.Config to customize it.
func BuildOpenAPIDefinitionsForResource(model interface{}, config *common.Config) (*spec.Definitions, error) {
o := newOpenAPI(config)
// We can discard the return value of toSchema because all we care about is the side effect of calling it.
// All the models created for this resource get added to o.swagger.Definitions
_, err := o.toSchema(util.GetCanonicalTypeName(model))
if err != nil {
return nil, err
}
swagger, err := o.finalizeSwagger()
if err != nil {
return nil, err
}
return &swagger.Definitions, nil
}
// BuildOpenAPIDefinitionsForResources returns the OpenAPI spec which includes the definitions for the
// passed type names.
func BuildOpenAPIDefinitionsForResources(config *common.Config, names ...string) (*spec.Swagger, error) {
o := newOpenAPI(config)
// We can discard the return value of toSchema because all we care about is the side effect of calling it.
// All the models created for this resource get added to o.swagger.Definitions
for _, name := range names {
_, err := o.toSchema(name)
if err != nil {
return nil, err
}
}
return o.finalizeSwagger()
}
// newOpenAPI sets up the openAPI object so we can build the spec.
func newOpenAPI(config *common.Config) openAPI {
o := openAPI{
config: config,
swagger: &spec.Swagger{
SwaggerProps: spec.SwaggerProps{
Swagger: OpenAPIVersion,
Definitions: spec.Definitions{},
Responses: config.ResponseDefinitions,
Paths: &spec.Paths{Paths: map[string]spec.PathItem{}},
Info: config.Info,
},
},
}
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
}
}
o.definitions = o.config.GetDefinitions(func(name string) spec.Ref {
defName, _ := o.config.GetDefinitionName(name)
return spec.MustCreateRef("#/definitions/" + common.EscapeJsonPointer(defName))
})
if o.config.CommonResponses == nil {
o.config.CommonResponses = map[int]spec.Response{}
}
return o
}
// finalizeSwagger is called after the spec is built and returns the final spec.
// NOTE: finalizeSwagger also make changes to the final spec, as specified in the config.
func (o *openAPI) finalizeSwagger() (*spec.Swagger, error) {
if o.config.SecurityDefinitions != nil {
o.swagger.SecurityDefinitions = *o.config.SecurityDefinitions
o.swagger.Security = o.config.DefaultSecurity
}
if o.config.PostProcessSpec != nil {
var err error
o.swagger, err = o.config.PostProcessSpec(o.swagger)
if err != nil {
return nil, err
}
}
return o.swagger, nil
}
func (o *openAPI) buildDefinitionRecursively(name string) error {
uniqueName, extensions := o.config.GetDefinitionName(name)
if _, ok := o.swagger.Definitions[uniqueName]; ok {
return nil
}
if item, ok := o.definitions[name]; ok {
schema := spec.Schema{
VendorExtensible: item.Schema.VendorExtensible,
SchemaProps: item.Schema.SchemaProps,
SwaggerSchemaProps: item.Schema.SwaggerSchemaProps,
}
if extensions != nil {
if schema.Extensions == nil {
schema.Extensions = spec.Extensions{}
}
for k, v := range extensions {
schema.Extensions[k] = v
}
}
if v, ok := item.Schema.Extensions[common.ExtensionV2Schema]; ok {
if v2Schema, isOpenAPISchema := v.(spec.Schema); isOpenAPISchema {
schema = v2Schema
}
}
o.swagger.Definitions[uniqueName] = schema
for _, v := range item.Dependencies {
if err := o.buildDefinitionRecursively(v); err != nil {
return err
}
}
} else {
return fmt.Errorf("cannot find model definition for %v. If you added a new type, you may need to add +k8s:openapi-gen=true to the package or type and run code-gen again", name)
}
return nil
}
// buildDefinitionForType build a definition for a given type and return a referable name to its definition.
// This is the main function that keep track of definitions used in this spec and is depend on code generated
// by k8s.io/kubernetes/cmd/libs/go2idl/openapi-gen.
func (o *openAPI) buildDefinitionForType(name string) (string, error) {
if err := o.buildDefinitionRecursively(name); err != nil {
return "", err
}
defName, _ := o.config.GetDefinitionName(name)
return "#/definitions/" + common.EscapeJsonPointer(defName), nil
}
// buildPaths builds OpenAPI paths using go-restful's web services.
func (o *openAPI) buildPaths(routeContainers []common.RouteContainer) error {
pathsToIgnore := util.NewTrie(o.config.IgnorePrefixes)
duplicateOpId := make(map[string]string)
for _, w := range routeContainers {
rootPath := w.RootPath()
if pathsToIgnore.HasPrefix(rootPath) {
continue
}
commonParams, err := o.buildParameters(w.PathParameters())
if err != nil {
return err
}
for path, routes := range groupRoutesByPath(w.Routes()) {
// go-swagger has special variable definition {$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] + "}"
}
if pathsToIgnore.HasPrefix(path) {
continue
}
// Aggregating common parameters make API spec (and generated clients) simpler
inPathCommonParamsMap, err := o.findCommonParameters(routes)
if err != nil {
return err
}
pathItem, exists := o.swagger.Paths.Paths[path]
if exists {
return 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
pathItem.Parameters = append(pathItem.Parameters, commonParams...)
for _, p := range inPathCommonParamsMap {
pathItem.Parameters = append(pathItem.Parameters, p)
}
sortParameters(pathItem.Parameters)
for _, route := range routes {
op, err := o.buildOperations(route, inPathCommonParamsMap)
sortParameters(op.Parameters)
if err != nil {
return err
}
dpath, exists := duplicateOpId[op.ID]
if exists {
return fmt.Errorf("duplicate Operation ID %v for path %v and %v", op.ID, dpath, path)
} else {
duplicateOpId[op.ID] = path
}
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
}
}
o.swagger.Paths.Paths[path] = pathItem
}
}
return nil
}
// buildOperations builds operations for each webservice path
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.Description(),
Consumes: route.Consumes(),
Produces: route.Produces(),
Schemes: o.config.ProtocolList,
Responses: &spec.Responses{
ResponsesProps: spec.ResponsesProps{
StatusCodeResponses: make(map[int]spec.Response),
},
},
},
}
for k, v := range route.Metadata() {
if strings.HasPrefix(k, common.ExtensionPrefix) {
if ret.Extensions == nil {
ret.Extensions = spec.Extensions{}
}
ret.Extensions.Add(k, v)
}
}
if ret.ID, ret.Tags, err = o.config.GetOperationIDAndTagsFromRoute(route); err != nil {
return ret, err
}
// Build responses
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.ResponsePayloadSample() != nil {
ret.Responses.StatusCodeResponses[http.StatusOK], err = o.buildResponse(route.ResponsePayloadSample(), "OK")
if err != nil {
return ret, err
}
}
for code, resp := range o.config.CommonResponses {
if _, exists := ret.Responses.StatusCodeResponses[code]; !exists {
ret.Responses.StatusCodeResponses[code] = resp
}
}
// If there is still no response, use default response provided.
if len(ret.Responses.StatusCodeResponses) == 0 {
ret.Responses.Default = o.config.DefaultResponse
}
// Build non-common Parameters
ret.Parameters = make([]spec.Parameter, 0)
for _, param := range route.Parameters() {
if _, isCommon := inPathCommonParamsMap[mapKeyFromParam(param)]; !isCommon {
openAPIParam, err := o.buildParameter(param, route.RequestPayloadSample())
if err != nil {
return ret, err
}
ret.Parameters = append(ret.Parameters, openAPIParam)
}
}
return ret, nil
}
func (o *openAPI) buildResponse(model interface{}, description string) (spec.Response, error) {
schema, err := o.toSchema(util.GetCanonicalTypeName(model))
if err != nil {
return spec.Response{}, err
}
return spec.Response{
ResponseProps: spec.ResponseProps{
Description: description,
Schema: schema,
},
}, nil
}
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{}]common.Parameter, 0)
for _, route := range routes {
routeParamDuplicateMap := make(map[interface{}]bool)
s := ""
params := route.Parameters()
for _, param := range params {
m, _ := json.Marshal(param)
s += string(m) + "\n"
key := mapKeyFromParam(param)
if routeParamDuplicateMap[key] {
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
}
}
for key, count := range paramOpsCountByName {
paramData := paramNameKindToDataMap[key]
if count == len(routes) && paramData.Kind() != common.BodyParameterKind {
openAPIParam, err := o.buildParameter(paramData, nil)
if err != nil {
return commonParamsMap, err
}
commonParamsMap[key] = openAPIParam
}
}
return commonParamsMap, nil
}
func (o *openAPI) toSchema(name string) (_ *spec.Schema, err error) {
if openAPIType, openAPIFormat := common.OpenAPITypeFormat(name); openAPIType != "" {
return &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{openAPIType},
Format: openAPIFormat,
},
}, nil
} else {
ref, err := o.buildDefinitionForType(name)
if err != nil {
return nil, err
}
return &spec.Schema{
SchemaProps: spec.SchemaProps{
Ref: spec.MustCreateRef(ref),
},
}, nil
}
}
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(),
},
}
switch restParam.Kind() {
case common.BodyParameterKind:
if bodySample != nil {
ret.In = "body"
ret.Schema, err = o.toSchema(util.GetCanonicalTypeName(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 common.PathParameterKind:
ret.In = "path"
if !restParam.Required() {
return ret, fmt.Errorf("path parameters should be marked at required for parameter %v", restParam)
}
case common.QueryParameterKind:
ret.In = "query"
case common.HeaderParameterKind:
ret.In = "header"
case common.FormParameterKind:
ret.In = "formData"
default:
return ret, fmt.Errorf("unknown restful operation kind : %v", restParam.Kind())
}
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())
}
ret.Type = openAPIType
ret.Format = openAPIFormat
ret.UniqueItems = !restParam.AllowMultiple()
return ret, nil
}
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, nil)
if err != nil {
return ret, err
}
}
return ret, nil
}

61
vendor/k8s.io/kube-openapi/pkg/builder/util.go generated vendored Normal file
View File

@ -0,0 +1,61 @@
/*
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 builder
import (
"sort"
"k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/validation/spec"
)
type parameters []spec.Parameter
func (s parameters) Len() int { return len(s) }
func (s parameters) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// byNameIn used in sorting parameters by Name and In fields.
type byNameIn struct {
parameters
}
func (s byNameIn) Less(i, j int) bool {
return s.parameters[i].Name < s.parameters[j].Name || (s.parameters[i].Name == s.parameters[j].Name && s.parameters[i].In < s.parameters[j].In)
}
// SortParameters sorts parameters by Name and In fields.
func sortParameters(p []spec.Parameter) {
sort.Sort(byNameIn{p})
}
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)
}
return pathToRoutes
}
func mapKeyFromParam(param common.Parameter) interface{} {
return struct {
Name string
Kind common.ParameterKind
}{
Name: param.Name(),
Kind: param.Kind(),
}
}

490
vendor/k8s.io/kube-openapi/pkg/builder3/openapi.go generated vendored Normal file
View File

@ -0,0 +1,490 @@
/*
Copyright 2021 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 builder3
import (
"encoding/json"
"fmt"
"net/http"
"strings"
restful "github.com/emicklei/go-restful/v3"
builderutil "k8s.io/kube-openapi/pkg/builder3/util"
"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"
)
const (
OpenAPIVersion = "3.0"
)
type openAPI struct {
config *common.OpenAPIV3Config
spec *spec3.OpenAPI
definitions map[string]common.OpenAPIDefinition
}
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)
}
return pathToRoutes
}
func (o *openAPI) buildResponse(model interface{}, description string, content []string) (*spec3.Response, error) {
response := &spec3.Response{
ResponseProps: spec3.ResponseProps{
Description: description,
Content: make(map[string]*spec3.MediaType),
},
}
s, err := o.toSchema(util.GetCanonicalTypeName(model))
if err != nil {
return nil, err
}
for _, contentType := range content {
response.ResponseProps.Content[contentType] = &spec3.MediaType{
MediaTypeProps: spec3.MediaTypeProps{
Schema: s,
},
}
}
return response, nil
}
func (o *openAPI) buildOperations(route common.Route, inPathCommonParamsMap map[interface{}]*spec3.Parameter) (*spec3.Operation, error) {
ret := &spec3.Operation{
OperationProps: spec3.OperationProps{
Description: route.Description(),
Responses: &spec3.Responses{
ResponsesProps: spec3.ResponsesProps{
StatusCodeResponses: make(map[int]*spec3.Response),
},
},
},
}
for k, v := range route.Metadata() {
if strings.HasPrefix(k, common.ExtensionPrefix) {
if ret.Extensions == nil {
ret.Extensions = spec.Extensions{}
}
ret.Extensions.Add(k, v)
}
}
var err error
if ret.OperationId, ret.Tags, err = o.config.GetOperationIDAndTagsFromRoute(route); err != nil {
return ret, err
}
// Build responses
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.ResponsePayloadSample() != nil {
ret.Responses.StatusCodeResponses[http.StatusOK], err = o.buildResponse(route.ResponsePayloadSample(), "OK", route.Produces())
if err != nil {
return ret, err
}
}
for code, resp := range o.config.CommonResponses {
if _, exists := ret.Responses.StatusCodeResponses[code]; !exists {
ret.Responses.StatusCodeResponses[code] = resp
}
}
if len(ret.Responses.StatusCodeResponses) == 0 {
ret.Responses.Default = o.config.DefaultResponse
}
params := route.Parameters()
for _, param := range params {
_, isCommon := inPathCommonParamsMap[mapKeyFromParam(param)]
if !isCommon && param.Kind() != common.BodyParameterKind {
openAPIParam, err := o.buildParameter(param)
if err != nil {
return ret, err
}
ret.Parameters = append(ret.Parameters, openAPIParam)
}
}
body, err := o.buildRequestBody(params, route.Consumes(), route.RequestPayloadSample())
if err != nil {
return nil, err
}
if body != nil {
ret.RequestBody = body
}
return ret, nil
}
func (o *openAPI) buildRequestBody(parameters []common.Parameter, consumes []string, bodySample interface{}) (*spec3.RequestBody, error) {
for _, param := range parameters {
if param.Kind() == common.BodyParameterKind && bodySample != nil {
schema, err := o.toSchema(util.GetCanonicalTypeName(bodySample))
if err != nil {
return nil, err
}
r := &spec3.RequestBody{
RequestBodyProps: spec3.RequestBodyProps{
Content: map[string]*spec3.MediaType{},
},
}
for _, consume := range consumes {
r.Content[consume] = &spec3.MediaType{
MediaTypeProps: spec3.MediaTypeProps{
Schema: schema,
},
}
}
return r, nil
}
}
return nil, nil
}
func newOpenAPI(config *common.Config) openAPI {
o := openAPI{
config: common.ConvertConfigToV3(config),
spec: &spec3.OpenAPI{
Version: "3.0.0",
Info: config.Info,
Paths: &spec3.Paths{
Paths: map[string]*spec3.Path{},
},
Components: &spec3.Components{
Schemas: map[string]*spec.Schema{},
},
},
}
if len(o.config.ResponseDefinitions) > 0 {
o.spec.Components.Responses = make(map[string]*spec3.Response)
}
for k, response := range o.config.ResponseDefinitions {
o.spec.Components.Responses[k] = response
}
if len(o.config.SecuritySchemes) > 0 {
o.spec.Components.SecuritySchemes = make(spec3.SecuritySchemes)
}
for k, securityScheme := range o.config.SecuritySchemes {
o.spec.Components.SecuritySchemes[k] = securityScheme
}
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
}
}
if o.config.Definitions != nil {
o.definitions = o.config.Definitions
} else {
o.definitions = o.config.GetDefinitions(func(name string) spec.Ref {
defName, _ := o.config.GetDefinitionName(name)
return spec.MustCreateRef("#/components/schemas/" + common.EscapeJsonPointer(defName))
})
}
return o
}
func (o *openAPI) buildOpenAPISpec(webServices []common.RouteContainer) error {
pathsToIgnore := util.NewTrie(o.config.IgnorePrefixes)
for _, w := range webServices {
rootPath := w.RootPath()
if pathsToIgnore.HasPrefix(rootPath) {
continue
}
commonParams, err := o.buildParameters(w.PathParameters())
if err != nil {
return err
}
for path, routes := range groupRoutesByPath(w.Routes()) {
// go-swagger has special variable definition {$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] + "}"
}
if pathsToIgnore.HasPrefix(path) {
continue
}
// Aggregating common parameters make API spec (and generated clients) simpler
inPathCommonParamsMap, err := o.findCommonParameters(routes)
if err != nil {
return err
}
pathItem, exists := o.spec.Paths.Paths[path]
if exists {
return fmt.Errorf("duplicate webservice route has been found for path: %v", path)
}
pathItem = &spec3.Path{
PathProps: spec3.PathProps{},
}
// add web services's parameters as well as any parameters appears in all ops, as common parameters
pathItem.Parameters = append(pathItem.Parameters, commonParams...)
for _, p := range inPathCommonParamsMap {
pathItem.Parameters = append(pathItem.Parameters, p)
}
sortParameters(pathItem.Parameters)
for _, route := range routes {
op, _ := o.buildOperations(route, inPathCommonParamsMap)
sortParameters(op.Parameters)
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
}
}
o.spec.Paths.Paths[path] = pathItem
}
}
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 {
return nil, err
}
return a.spec, nil
}
// BuildOpenAPIDefinitionsForResource builds a partial OpenAPI spec given a sample object and common.Config to customize it.
// BuildOpenAPIDefinitionsForResources returns the OpenAPI spec which includes the definitions for the
// passed type names.
func BuildOpenAPIDefinitionsForResources(config *common.Config, names ...string) (map[string]*spec.Schema, error) {
o := newOpenAPI(config)
// We can discard the return value of toSchema because all we care about is the side effect of calling it.
// All the models created for this resource get added to o.swagger.Definitions
for _, name := range names {
_, err := o.toSchema(name)
if err != nil {
return nil, err
}
}
return o.spec.Components.Schemas, nil
}
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{}]common.Parameter, 0)
for _, route := range routes {
routeParamDuplicateMap := make(map[interface{}]bool)
s := ""
params := route.Parameters()
for _, param := range params {
m, _ := json.Marshal(param)
s += string(m) + "\n"
key := mapKeyFromParam(param)
if routeParamDuplicateMap[key] {
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
}
}
for key, count := range paramOpsCountByName {
paramData := paramNameKindToDataMap[key]
if count == len(routes) && paramData.Kind() != common.BodyParameterKind {
openAPIParam, err := o.buildParameter(paramData)
if err != nil {
return commonParamsMap, err
}
commonParamsMap[key] = openAPIParam
}
}
return commonParamsMap, nil
}
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)
if err != nil {
return ret, err
}
}
return ret, nil
}
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(),
},
}
switch restParam.Kind() {
case common.BodyParameterKind:
return nil, nil
case common.PathParameterKind:
ret.In = "path"
if !restParam.Required() {
return ret, fmt.Errorf("path parameters should be marked as required for parameter %v", restParam)
}
case common.QueryParameterKind:
ret.In = "query"
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())
}
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())
}
ret.Schema = &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{openAPIType},
Format: openAPIFormat,
UniqueItems: !restParam.AllowMultiple(),
},
}
return ret, nil
}
func (o *openAPI) buildDefinitionRecursively(name string) error {
uniqueName, extensions := o.config.GetDefinitionName(name)
if _, ok := o.spec.Components.Schemas[uniqueName]; ok {
return nil
}
if item, ok := o.definitions[name]; ok {
schema := &spec.Schema{
VendorExtensible: item.Schema.VendorExtensible,
SchemaProps: item.Schema.SchemaProps,
SwaggerSchemaProps: item.Schema.SwaggerSchemaProps,
}
if extensions != nil {
if schema.Extensions == nil {
schema.Extensions = spec.Extensions{}
}
for k, v := range extensions {
schema.Extensions[k] = v
}
}
// delete the embedded v2 schema if exists, otherwise no-op
delete(schema.VendorExtensible.Extensions, common.ExtensionV2Schema)
schema = builderutil.WrapRefs(schema)
o.spec.Components.Schemas[uniqueName] = schema
for _, v := range item.Dependencies {
if err := o.buildDefinitionRecursively(v); err != nil {
return err
}
}
} else {
return fmt.Errorf("cannot find model definition for %v. If you added a new type, you may need to add +k8s:openapi-gen=true to the package or type and run code-gen again", name)
}
return nil
}
func (o *openAPI) buildDefinitionForType(name string) (string, error) {
if err := o.buildDefinitionRecursively(name); err != nil {
return "", err
}
defName, _ := o.config.GetDefinitionName(name)
return "#/components/schemas/" + common.EscapeJsonPointer(defName), nil
}
func (o *openAPI) toSchema(name string) (_ *spec.Schema, err error) {
if openAPIType, openAPIFormat := common.OpenAPITypeFormat(name); openAPIType != "" {
return &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{openAPIType},
Format: openAPIFormat,
},
}, nil
} else {
ref, err := o.buildDefinitionForType(name)
if err != nil {
return nil, err
}
return &spec.Schema{
SchemaProps: spec.SchemaProps{
Ref: spec.MustCreateRef(ref),
},
}, nil
}
}

52
vendor/k8s.io/kube-openapi/pkg/builder3/util.go generated vendored Normal file
View File

@ -0,0 +1,52 @@
/*
Copyright 2021 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 builder3
import (
"sort"
"k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/spec3"
)
func mapKeyFromParam(param common.Parameter) interface{} {
return struct {
Name string
Kind common.ParameterKind
}{
Name: param.Name(),
Kind: param.Kind(),
}
}
func (s parameters) Len() int { return len(s) }
func (s parameters) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
type parameters []*spec3.Parameter
type byNameIn struct {
parameters
}
func (s byNameIn) Less(i, j int) bool {
return s.parameters[i].Name < s.parameters[j].Name || (s.parameters[i].Name == s.parameters[j].Name && s.parameters[i].In < s.parameters[j].In)
}
// SortParameters sorts parameters by Name and In fields.
func sortParameters(p []*spec3.Parameter) {
sort.Sort(byNameIn{p})
}

264
vendor/k8s.io/kube-openapi/pkg/cached/cache.go generated vendored Normal file
View File

@ -0,0 +1,264 @@
/*
Copyright 2022 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 cache provides a cache mechanism based on etags to lazily
// build, and/or cache results from expensive operation such that those
// operations are not repeated unnecessarily. The operations can be
// created as a tree, and replaced dynamically as needed.
//
// # Dependencies and types of caches
//
// This package uses a source/transform/sink model of caches to build
// the dependency tree, and can be used as follows:
// - [NewSource]: A source cache that recomputes the content every time.
// - [NewStaticSource]: A source cache that always produces the
// same content, it is only called once.
// - [NewTransformer]: A cache that transforms data from one format to
// another. It's only refreshed when the source changes.
// - [NewMerger]: A cache that aggregates multiple caches into one.
// It's only refreshed when the source changes.
// - [Replaceable]: A cache adapter that can be atomically
// replaced with a new one, and saves the previous results in case an
// error pops-up.
//
// # Atomicity
//
// Most of the operations are not atomic/thread-safe, except for
// [Replaceable.Replace] which can be performed while the objects
// are being read.
//
// # Etags
//
// Etags in this library is a cache version identifier. It doesn't
// necessarily strictly match to the semantics of http `etags`, but are
// somewhat inspired from it and function with the same principles.
// Hashing the content is a good way to guarantee that your function is
// never going to be called spuriously. In Kubernetes world, this could
// be a `resourceVersion`, this can be an actual etag, a hash, a UUID
// (if the cache always changes), or even a made-up string when the
// content of the cache never changes.
package cached
import (
"fmt"
"sync/atomic"
)
// Result is the content returned from a call to a cache. It can either
// be created with [NewResultOK] if the call was a success, or
// [NewResultErr] if the call resulted in an error.
type Result[T any] struct {
Data T
Etag string
Err error
}
// NewResultOK creates a new [Result] for a successful operation.
func NewResultOK[T any](data T, etag string) Result[T] {
return Result[T]{
Data: data,
Etag: etag,
}
}
// NewResultErr creates a new [Result] when an error has happened.
func NewResultErr[T any](err error) Result[T] {
return Result[T]{
Err: err,
}
}
// Result can be treated as a [Data] if necessary.
func (r Result[T]) Get() Result[T] {
return r
}
// Data is a cache that performs an action whose result data will be
// cached. It also returns an "etag" identifier to version the cache, so
// that the caller can know if they have the most recent version of the
// cache (and can decide to cache some operation based on that).
//
// The [NewMerger] and [NewTransformer] automatically handle
// that for you by checking if the etag is updated before calling the
// merging or transforming function.
type Data[T any] interface {
// Returns the cached data, as well as an "etag" to identify the
// version of the cache, or an error if something happened.
Get() Result[T]
}
// T is the source type, V is the destination type.
type merger[K comparable, T, V any] struct {
mergeFn func(map[K]Result[T]) Result[V]
caches map[K]Data[T]
cacheResults map[K]Result[T]
result Result[V]
}
// NewMerger creates a new merge cache, a cache that merges the result
// of other caches. The function only gets called if any of the
// dependency has changed.
//
// If any of the dependency returned an error before, or any of the
// dependency returned an error this time, or if the mergeFn failed
// before, then the function is reran.
//
// The caches and results are mapped by K so that associated data can be
// retrieved. The map of dependencies can not be modified after
// creation, and a new merger should be created (and probably replaced
// using a [Replaceable]).
//
// Note that this assumes there is no "partial" merge, the merge
// function will remerge all the dependencies together everytime. Since
// the list of dependencies is constant, there is no way to save some
// partial merge information either.
func NewMerger[K comparable, T, V any](mergeFn func(results map[K]Result[T]) Result[V], caches map[K]Data[T]) Data[V] {
return &merger[K, T, V]{
mergeFn: mergeFn,
caches: caches,
}
}
func (c *merger[K, T, V]) prepareResults() map[K]Result[T] {
cacheResults := make(map[K]Result[T], len(c.caches))
for key, cache := range c.caches {
cacheResults[key] = cache.Get()
}
return cacheResults
}
// Rerun if:
// - The last run resulted in an error
// - Any of the dependency previously returned an error
// - Any of the dependency just returned an error
// - Any of the dependency's etag changed
func (c *merger[K, T, V]) needsRunning(results map[K]Result[T]) bool {
if c.cacheResults == nil {
return true
}
if c.result.Err != nil {
return true
}
if len(results) != len(c.cacheResults) {
panic(fmt.Errorf("invalid number of results: %v (expected %v)", len(results), len(c.cacheResults)))
}
for key, oldResult := range c.cacheResults {
newResult, ok := results[key]
if !ok {
panic(fmt.Errorf("unknown cache entry: %v", key))
}
if newResult.Etag != oldResult.Etag || newResult.Err != nil || oldResult.Err != nil {
return true
}
}
return false
}
func (c *merger[K, T, V]) Get() Result[V] {
cacheResults := c.prepareResults()
if c.needsRunning(cacheResults) {
c.cacheResults = cacheResults
c.result = c.mergeFn(c.cacheResults)
}
return c.result
}
type transformerCacheKeyType struct{}
// NewTransformer creates a new cache that transforms the result of
// another cache. The transformFn will only be called if the source
// cache has updated the output, otherwise, the cached result will be
// returned.
//
// If the dependency returned an error before, or it returns an error
// this time, or if the transformerFn failed before, the function is
// reran.
func NewTransformer[T, V any](transformerFn func(Result[T]) Result[V], source Data[T]) Data[V] {
return NewMerger(func(caches map[transformerCacheKeyType]Result[T]) Result[V] {
cache, ok := caches[transformerCacheKeyType{}]
if len(caches) != 1 || !ok {
panic(fmt.Errorf("invalid cache for transformer cache: %v", caches))
}
return transformerFn(cache)
}, map[transformerCacheKeyType]Data[T]{
{}: source,
})
}
// NewSource creates a new cache that generates some data. This
// will always be called since we don't know the origin of the data and
// if it needs to be updated or not.
func NewSource[T any](sourceFn func() Result[T]) Data[T] {
c := source[T](sourceFn)
return &c
}
type source[T any] func() Result[T]
func (c *source[T]) Get() Result[T] {
return (*c)()
}
// NewStaticSource creates a new cache that always generates the
// same data. This will only be called once (lazily).
func NewStaticSource[T any](staticFn func() Result[T]) Data[T] {
return &static[T]{
fn: staticFn,
}
}
type static[T any] struct {
fn func() Result[T]
result *Result[T]
}
func (c *static[T]) Get() Result[T] {
if c.result == nil {
result := c.fn()
c.result = &result
}
return *c.result
}
// Replaceable is a cache that carries the result even when the
// cache is replaced. The cache can be replaced atomically (without any
// lock held). This is the type that should typically be stored in
// structs.
type Replaceable[T any] struct {
cache atomic.Pointer[Data[T]]
result *Result[T]
}
// Get retrieves the data from the underlying source. [Replaceable]
// implements the [Data] interface itself. This is a pass-through
// that calls the most recent underlying cache. If the cache fails but
// previously had returned a success, that success will be returned
// instead. If the cache fails but we never returned a success, that
// failure is returned.
func (c *Replaceable[T]) Get() Result[T] {
result := (*c.cache.Load()).Get()
if result.Err != nil && c.result != nil && c.result.Err == nil {
return *c.result
}
c.result = &result
return *c.result
}
// Replace changes the cache in a thread-safe way.
func (c *Replaceable[T]) Replace(cache Data[T]) {
c.cache.Swap(&cache)
}

View File

@ -246,38 +246,42 @@ var schemaTypeFormatMap = map[string]typeInfo{
// the spec does not need to be simple type,format) or can even return a simple type,format (e.g. IntOrString). For simple
// type formats, the benefit of adding OpenAPIDefinitionGetter interface is to keep both type and property documentation.
// Example:
// type Sample struct {
// ...
// // port of the server
// port IntOrString
// ...
// }
//
// type Sample struct {
// ...
// // port of the server
// port IntOrString
// ...
// }
//
// // IntOrString documentation...
// type IntOrString { ... }
//
// Adding IntOrString to this function:
// "port" : {
// format: "string",
// type: "int-or-string",
// Description: "port of the server"
// }
//
// "port" : {
// format: "string",
// type: "int-or-string",
// Description: "port of the server"
// }
//
// Implement OpenAPIDefinitionGetter for IntOrString:
//
// "port" : {
// $Ref: "#/definitions/IntOrString"
// Description: "port of the server"
// }
// "port" : {
// $Ref: "#/definitions/IntOrString"
// Description: "port of the server"
// }
//
// ...
// definitions:
// {
// "IntOrString": {
// format: "string",
// type: "int-or-string",
// Description: "IntOrString documentation..." // new
// }
// }
//
// {
// "IntOrString": {
// format: "string",
// type: "int-or-string",
// Description: "IntOrString documentation..." // new
// }
// }
func OpenAPITypeFormat(typeName string) (string, string) {
mapped, ok := schemaTypeFormatMap[typeName]
if !ok {

View File

@ -0,0 +1,15 @@
package restfuladapter
import (
"github.com/emicklei/go-restful/v3"
"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
}

View File

@ -0,0 +1,54 @@
package restfuladapter
import (
"encoding/json"
"github.com/emicklei/go-restful/v3"
"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
}

View File

@ -0,0 +1,25 @@
package restfuladapter
import (
"github.com/emicklei/go-restful/v3"
"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
}

View File

@ -0,0 +1,68 @@
package restfuladapter
import (
"github.com/emicklei/go-restful/v3"
"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 {
localRes := res
responses = append(responses, &ResponseErrorAdapter{&localRes})
}
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
}

View File

@ -0,0 +1,34 @@
package restfuladapter
import (
"github.com/emicklei/go-restful/v3"
"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
}

View File

@ -0,0 +1,208 @@
/*
Copyright 2020 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 handler
import "k8s.io/kube-openapi/pkg/validation/spec"
// PruneDefaults remove all the defaults recursively from all the
// schemas in the definitions, and does not modify the definitions in
// place.
func PruneDefaults(definitions spec.Definitions) spec.Definitions {
definitionsCloned := false
for k, v := range definitions {
if s := PruneDefaultsSchema(&v); s != &v {
if !definitionsCloned {
definitionsCloned = true
orig := definitions
definitions = make(spec.Definitions, len(orig))
for k2, v2 := range orig {
definitions[k2] = v2
}
}
definitions[k] = *s
}
}
return definitions
}
// PruneDefaultsSchema remove all the defaults recursively from the
// schema in place.
func PruneDefaultsSchema(schema *spec.Schema) *spec.Schema {
if schema == nil {
return nil
}
orig := schema
clone := func() {
if orig == schema {
schema = &spec.Schema{}
*schema = *orig
}
}
if schema.Default != nil {
clone()
schema.Default = nil
}
definitionsCloned := false
for k, v := range schema.Definitions {
if s := PruneDefaultsSchema(&v); s != &v {
if !definitionsCloned {
definitionsCloned = true
clone()
schema.Definitions = make(spec.Definitions, len(orig.Definitions))
for k2, v2 := range orig.Definitions {
schema.Definitions[k2] = v2
}
}
schema.Definitions[k] = *s
}
}
propertiesCloned := false
for k, v := range schema.Properties {
if s := PruneDefaultsSchema(&v); s != &v {
if !propertiesCloned {
propertiesCloned = true
clone()
schema.Properties = make(map[string]spec.Schema, len(orig.Properties))
for k2, v2 := range orig.Properties {
schema.Properties[k2] = v2
}
}
schema.Properties[k] = *s
}
}
patternPropertiesCloned := false
for k, v := range schema.PatternProperties {
if s := PruneDefaultsSchema(&v); s != &v {
if !patternPropertiesCloned {
patternPropertiesCloned = true
clone()
schema.PatternProperties = make(map[string]spec.Schema, len(orig.PatternProperties))
for k2, v2 := range orig.PatternProperties {
schema.PatternProperties[k2] = v2
}
}
schema.PatternProperties[k] = *s
}
}
dependenciesCloned := false
for k, v := range schema.Dependencies {
if s := PruneDefaultsSchema(v.Schema); s != v.Schema {
if !dependenciesCloned {
dependenciesCloned = true
clone()
schema.Dependencies = make(spec.Dependencies, len(orig.Dependencies))
for k2, v2 := range orig.Dependencies {
schema.Dependencies[k2] = v2
}
}
v.Schema = s
schema.Dependencies[k] = v
}
}
allOfCloned := false
for i := range schema.AllOf {
if s := PruneDefaultsSchema(&schema.AllOf[i]); s != &schema.AllOf[i] {
if !allOfCloned {
allOfCloned = true
clone()
schema.AllOf = make([]spec.Schema, len(orig.AllOf))
copy(schema.AllOf, orig.AllOf)
}
schema.AllOf[i] = *s
}
}
anyOfCloned := false
for i := range schema.AnyOf {
if s := PruneDefaultsSchema(&schema.AnyOf[i]); s != &schema.AnyOf[i] {
if !anyOfCloned {
anyOfCloned = true
clone()
schema.AnyOf = make([]spec.Schema, len(orig.AnyOf))
copy(schema.AnyOf, orig.AnyOf)
}
schema.AnyOf[i] = *s
}
}
oneOfCloned := false
for i := range schema.OneOf {
if s := PruneDefaultsSchema(&schema.OneOf[i]); s != &schema.OneOf[i] {
if !oneOfCloned {
oneOfCloned = true
clone()
schema.OneOf = make([]spec.Schema, len(orig.OneOf))
copy(schema.OneOf, orig.OneOf)
}
schema.OneOf[i] = *s
}
}
if schema.Not != nil {
if s := PruneDefaultsSchema(schema.Not); s != schema.Not {
clone()
schema.Not = s
}
}
if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
if s := PruneDefaultsSchema(schema.AdditionalProperties.Schema); s != schema.AdditionalProperties.Schema {
clone()
schema.AdditionalProperties = &spec.SchemaOrBool{Schema: s, Allows: schema.AdditionalProperties.Allows}
}
}
if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil {
if s := PruneDefaultsSchema(schema.AdditionalItems.Schema); s != schema.AdditionalItems.Schema {
clone()
schema.AdditionalItems = &spec.SchemaOrBool{Schema: s, Allows: schema.AdditionalItems.Allows}
}
}
if schema.Items != nil {
if schema.Items.Schema != nil {
if s := PruneDefaultsSchema(schema.Items.Schema); s != schema.Items.Schema {
clone()
schema.Items = &spec.SchemaOrArray{Schema: s}
}
} else {
itemsCloned := false
for i := range schema.Items.Schemas {
if s := PruneDefaultsSchema(&schema.Items.Schemas[i]); s != &schema.Items.Schemas[i] {
if !itemsCloned {
clone()
schema.Items = &spec.SchemaOrArray{
Schemas: make([]spec.Schema, len(orig.Items.Schemas)),
}
itemsCloned = true
copy(schema.Items.Schemas, orig.Items.Schemas)
}
schema.Items.Schemas[i] = *s
}
}
}
}
return schema
}

207
vendor/k8s.io/kube-openapi/pkg/handler/handler.go generated vendored Normal file
View File

@ -0,0 +1,207 @@
/*
Copyright 2017 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 handler
import (
"bytes"
"crypto/sha512"
"fmt"
"net/http"
"strconv"
"sync"
"time"
"github.com/NYTimes/gziphandler"
"github.com/emicklei/go-restful/v3"
"github.com/golang/protobuf/proto"
openapi_v2 "github.com/google/gnostic/openapiv2"
"github.com/google/uuid"
"github.com/munnerz/goautoneg"
klog "k8s.io/klog/v2"
"k8s.io/kube-openapi/pkg/builder"
"k8s.io/kube-openapi/pkg/cached"
"k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/common/restfuladapter"
"k8s.io/kube-openapi/pkg/validation/spec"
)
const (
subTypeProtobufDeprecated = "com.github.proto-openapi.spec.v2@v1.0+protobuf"
subTypeProtobuf = "com.github.proto-openapi.spec.v2.v1.0+protobuf"
subTypeJSON = "json"
)
func computeETag(data []byte) string {
if data == nil {
return ""
}
return fmt.Sprintf("%X", sha512.Sum512(data))
}
type timedSpec struct {
spec []byte
lastModified time.Time
}
// OpenAPIService is the service responsible for serving OpenAPI spec. It has
// the ability to safely change the spec while serving it.
type OpenAPIService struct {
specCache cached.Replaceable[*spec.Swagger]
jsonCache cached.Data[timedSpec]
protoCache cached.Data[timedSpec]
}
// NewOpenAPIService builds an OpenAPIService starting with the given spec.
func NewOpenAPIService(swagger *spec.Swagger) *OpenAPIService {
return NewOpenAPIServiceLazy(cached.NewResultOK(swagger, uuid.New().String()))
}
// NewOpenAPIServiceLazy builds an OpenAPIService from lazy spec.
func NewOpenAPIServiceLazy(swagger cached.Data[*spec.Swagger]) *OpenAPIService {
o := &OpenAPIService{}
o.UpdateSpecLazy(swagger)
o.jsonCache = cached.NewTransformer[*spec.Swagger](func(result cached.Result[*spec.Swagger]) cached.Result[timedSpec] {
if result.Err != nil {
return cached.NewResultErr[timedSpec](result.Err)
}
json, err := result.Data.MarshalJSON()
if err != nil {
return cached.NewResultErr[timedSpec](err)
}
return cached.NewResultOK(timedSpec{spec: json, lastModified: time.Now()}, computeETag(json))
}, &o.specCache)
o.protoCache = cached.NewTransformer(func(result cached.Result[timedSpec]) cached.Result[timedSpec] {
if result.Err != nil {
return cached.NewResultErr[timedSpec](result.Err)
}
proto, err := ToProtoBinary(result.Data.spec)
if err != nil {
return cached.NewResultErr[timedSpec](err)
}
// We can re-use the same etag as json because of the Vary header.
return cached.NewResultOK(timedSpec{spec: proto, lastModified: result.Data.lastModified}, result.Etag)
}, o.jsonCache)
return o
}
func (o *OpenAPIService) UpdateSpec(swagger *spec.Swagger) error {
o.UpdateSpecLazy(cached.NewResultOK(swagger, uuid.New().String()))
return nil
}
func (o *OpenAPIService) UpdateSpecLazy(swagger cached.Data[*spec.Swagger]) {
o.specCache.Replace(swagger)
}
func ToProtoBinary(json []byte) ([]byte, error) {
document, err := openapi_v2.ParseDocument(json)
if err != nil {
return nil, err
}
return proto.Marshal(document)
}
// RegisterOpenAPIVersionedService registers a handler to provide access to provided swagger spec.
//
// Deprecated: use OpenAPIService.RegisterOpenAPIVersionedService instead.
func RegisterOpenAPIVersionedService(spec *spec.Swagger, servePath string, handler common.PathHandler) (*OpenAPIService, error) {
o := NewOpenAPIService(spec)
return o, o.RegisterOpenAPIVersionedService(servePath, handler)
}
// RegisterOpenAPIVersionedService registers a handler to provide access to provided swagger spec.
func (o *OpenAPIService) RegisterOpenAPIVersionedService(servePath string, handler common.PathHandler) error {
// Mutex protects the cache chain
var mutex sync.Mutex
accepted := []struct {
Type string
SubType string
ReturnedContentType string
GetDataAndEtag cached.Data[timedSpec]
}{
{"application", subTypeJSON, "application/" + subTypeJSON, o.jsonCache},
{"application", subTypeProtobufDeprecated, "application/" + subTypeProtobuf, o.protoCache},
{"application", subTypeProtobuf, "application/" + subTypeProtobuf, o.protoCache},
}
handler.Handle(servePath, gziphandler.GzipHandler(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
decipherableFormats := r.Header.Get("Accept")
if decipherableFormats == "" {
decipherableFormats = "*/*"
}
clauses := goautoneg.ParseAccept(decipherableFormats)
w.Header().Add("Vary", "Accept")
for _, clause := range clauses {
for _, accepts := range accepted {
if clause.Type != accepts.Type && clause.Type != "*" {
continue
}
if clause.SubType != accepts.SubType && clause.SubType != "*" {
continue
}
// serve the first matching media type in the sorted clause list
mutex.Lock()
result := accepts.GetDataAndEtag.Get()
mutex.Unlock()
if result.Err != nil {
klog.Errorf("Error in OpenAPI handler: %s", result.Err)
// only return a 503 if we have no older cache data to serve
if result.Data.spec == nil {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
}
// Set Content-Type header in the reponse
w.Header().Set("Content-Type", accepts.ReturnedContentType)
// ETag must be enclosed in double quotes: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
w.Header().Set("Etag", strconv.Quote(result.Etag))
// ServeContent will take care of caching using eTag.
http.ServeContent(w, r, servePath, result.Data.lastModified, bytes.NewReader(result.Data.spec))
return
}
}
// Return 406 for not acceptable format
w.WriteHeader(406)
return
}),
))
return nil
}
// 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) {
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
}
o := NewOpenAPIService(spec)
return o, o.RegisterOpenAPIVersionedService(servePath, handler)
}

View File

@ -21,11 +21,9 @@ import (
"crypto/sha512"
"encoding/json"
"fmt"
"mime"
"net/http"
"net/url"
"path"
"sort"
"strconv"
"strings"
"sync"
@ -33,23 +31,18 @@ import (
"github.com/golang/protobuf/proto"
openapi_v3 "github.com/google/gnostic/openapiv3"
"github.com/google/uuid"
"github.com/munnerz/goautoneg"
"k8s.io/klog/v2"
"k8s.io/kube-openapi/pkg/cached"
"k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/internal/handler"
"k8s.io/kube-openapi/pkg/spec3"
"k8s.io/kube-openapi/pkg/validation/spec"
)
const (
jsonExt = ".json"
mimeJson = "application/json"
// TODO(mehdy): change @68f4ded to a version tag when gnostic add version tags.
mimePb = "application/com.github.googleapis.gnostic.OpenAPIv3@68f4ded+protobuf"
mimePbGz = "application/x-gzip"
subTypeProtobuf = "com.github.proto-openapi.spec.v3@v1.0+protobuf"
subTypeJSON = "json"
subTypeProtobufDeprecated = "com.github.proto-openapi.spec.v3@v1.0+protobuf"
subTypeProtobuf = "com.github.proto-openapi.spec.v3.v1.0+protobuf"
subTypeJSON = "json"
)
// OpenAPIV3Discovery is the format of the Discovery document for OpenAPI V3
@ -65,29 +58,63 @@ type OpenAPIV3DiscoveryGroupVersion struct {
ServerRelativeURL string `json:"serverRelativeURL"`
}
func ToV3ProtoBinary(json []byte) ([]byte, error) {
document, err := openapi_v3.ParseDocument(json)
if err != nil {
return nil, err
}
return proto.Marshal(document)
}
type timedSpec struct {
spec []byte
lastModified time.Time
}
// This type is protected by the lock on OpenAPIService.
type openAPIV3Group struct {
specCache cached.Replaceable[*spec3.OpenAPI]
pbCache cached.Data[timedSpec]
jsonCache cached.Data[timedSpec]
}
func newOpenAPIV3Group() *openAPIV3Group {
o := &openAPIV3Group{}
o.jsonCache = cached.NewTransformer[*spec3.OpenAPI](func(result cached.Result[*spec3.OpenAPI]) cached.Result[timedSpec] {
if result.Err != nil {
return cached.NewResultErr[timedSpec](result.Err)
}
json, err := json.Marshal(result.Data)
if err != nil {
return cached.NewResultErr[timedSpec](err)
}
return cached.NewResultOK(timedSpec{spec: json, lastModified: time.Now()}, computeETag(json))
}, &o.specCache)
o.pbCache = cached.NewTransformer(func(result cached.Result[timedSpec]) cached.Result[timedSpec] {
if result.Err != nil {
return cached.NewResultErr[timedSpec](result.Err)
}
proto, err := ToV3ProtoBinary(result.Data.spec)
if err != nil {
return cached.NewResultErr[timedSpec](err)
}
return cached.NewResultOK(timedSpec{spec: proto, lastModified: result.Data.lastModified}, result.Etag)
}, o.jsonCache)
return o
}
func (o *openAPIV3Group) UpdateSpec(openapi cached.Data[*spec3.OpenAPI]) {
o.specCache.Replace(openapi)
}
// OpenAPIService is the service responsible for serving OpenAPI spec. It has
// the ability to safely change the spec while serving it.
type OpenAPIService struct {
// rwMutex protects All members of this service.
rwMutex sync.RWMutex
lastModified time.Time
v3Schema map[string]*OpenAPIV3Group
}
// Mutex protects the schema map.
mutex sync.Mutex
v3Schema map[string]*openAPIV3Group
type OpenAPIV3Group struct {
rwMutex sync.RWMutex
lastModified time.Time
pbCache handler.HandlerCache
jsonCache handler.HandlerCache
etagCache handler.HandlerCache
}
func init() {
mime.AddExtensionType(".json", mimeJson)
mime.AddExtensionType(".pb-v1", mimePb)
mime.AddExtensionType(".gz", mimePbGz)
discoveryCache cached.Replaceable[timedSpec]
}
func computeETag(data []byte) string {
@ -106,92 +133,90 @@ func constructServerRelativeURL(gvString, etag string) string {
}
// NewOpenAPIService builds an OpenAPIService starting with the given spec.
func NewOpenAPIService(spec *spec.Swagger) (*OpenAPIService, error) {
func NewOpenAPIService() *OpenAPIService {
o := &OpenAPIService{}
o.v3Schema = make(map[string]*OpenAPIV3Group)
return o, nil
o.v3Schema = make(map[string]*openAPIV3Group)
// We're not locked because we haven't shared the structure yet.
o.discoveryCache.Replace(o.buildDiscoveryCacheLocked())
return o
}
func (o *OpenAPIService) getGroupBytes() ([]byte, error) {
o.rwMutex.RLock()
defer o.rwMutex.RUnlock()
keys := make([]string, len(o.v3Schema))
i := 0
for k := range o.v3Schema {
keys[i] = k
i++
func (o *OpenAPIService) buildDiscoveryCacheLocked() cached.Data[timedSpec] {
caches := make(map[string]cached.Data[timedSpec], len(o.v3Schema))
for gvName, group := range o.v3Schema {
caches[gvName] = group.jsonCache
}
sort.Strings(keys)
discovery := &OpenAPIV3Discovery{Paths: make(map[string]OpenAPIV3DiscoveryGroupVersion)}
for gvString, groupVersion := range o.v3Schema {
etagBytes, err := groupVersion.etagCache.Get()
return cached.NewMerger(func(results map[string]cached.Result[timedSpec]) cached.Result[timedSpec] {
discovery := &OpenAPIV3Discovery{Paths: make(map[string]OpenAPIV3DiscoveryGroupVersion)}
for gvName, result := range results {
if result.Err != nil {
return cached.NewResultErr[timedSpec](result.Err)
}
discovery.Paths[gvName] = OpenAPIV3DiscoveryGroupVersion{
ServerRelativeURL: constructServerRelativeURL(gvName, result.Etag),
}
}
j, err := json.Marshal(discovery)
if err != nil {
return nil, err
return cached.NewResultErr[timedSpec](err)
}
discovery.Paths[gvString] = OpenAPIV3DiscoveryGroupVersion{
ServerRelativeURL: constructServerRelativeURL(gvString, string(etagBytes)),
}
}
j, err := json.Marshal(discovery)
if err != nil {
return nil, err
}
return j, nil
return cached.NewResultOK(timedSpec{spec: j, lastModified: time.Now()}, computeETag(j))
}, caches)
}
func (o *OpenAPIService) getSingleGroupBytes(getType string, group string) ([]byte, string, time.Time, error) {
o.rwMutex.RLock()
defer o.rwMutex.RUnlock()
o.mutex.Lock()
defer o.mutex.Unlock()
v, ok := o.v3Schema[group]
if !ok {
return nil, "", time.Now(), fmt.Errorf("Cannot find CRD group %s", group)
}
if getType == subTypeJSON {
specBytes, err := v.jsonCache.Get()
if err != nil {
return nil, "", v.lastModified, err
}
etagBytes, err := v.etagCache.Get()
return specBytes, string(etagBytes), v.lastModified, err
} else if getType == subTypeProtobuf {
specPb, err := v.pbCache.Get()
if err != nil {
return nil, "", v.lastModified, err
}
etagBytes, err := v.etagCache.Get()
return specPb, string(etagBytes), v.lastModified, err
result := cached.Result[timedSpec]{}
switch getType {
case subTypeJSON:
result = v.jsonCache.Get()
case subTypeProtobuf, subTypeProtobufDeprecated:
result = v.pbCache.Get()
default:
return nil, "", time.Now(), fmt.Errorf("Invalid accept clause %s", getType)
}
return nil, "", time.Now(), fmt.Errorf("Invalid accept clause %s", getType)
return result.Data.spec, result.Etag, result.Data.lastModified, result.Err
}
func (o *OpenAPIService) UpdateGroupVersion(group string, openapi *spec3.OpenAPI) (err error) {
o.rwMutex.Lock()
defer o.rwMutex.Unlock()
// UpdateGroupVersionLazy adds or updates an existing group with the new cached.
func (o *OpenAPIService) UpdateGroupVersionLazy(group string, openapi cached.Data[*spec3.OpenAPI]) {
o.mutex.Lock()
defer o.mutex.Unlock()
if _, ok := o.v3Schema[group]; !ok {
o.v3Schema[group] = &OpenAPIV3Group{}
o.v3Schema[group] = newOpenAPIV3Group()
// Since there is a new item, we need to re-build the cache map.
o.discoveryCache.Replace(o.buildDiscoveryCacheLocked())
}
return o.v3Schema[group].UpdateSpec(openapi)
o.v3Schema[group].UpdateSpec(openapi)
}
func (o *OpenAPIService) UpdateGroupVersion(group string, openapi *spec3.OpenAPI) {
o.UpdateGroupVersionLazy(group, cached.NewResultOK(openapi, uuid.New().String()))
}
func (o *OpenAPIService) DeleteGroupVersion(group string) {
o.rwMutex.Lock()
defer o.rwMutex.Unlock()
o.mutex.Lock()
defer o.mutex.Unlock()
delete(o.v3Schema, group)
}
func ToV3ProtoBinary(json []byte) ([]byte, error) {
document, err := openapi_v3.ParseDocument(json)
if err != nil {
return nil, err
}
return proto.Marshal(document)
// Rebuild the merge cache map since the items have changed.
o.discoveryCache.Replace(o.buildDiscoveryCacheLocked())
}
func (o *OpenAPIService) HandleDiscovery(w http.ResponseWriter, r *http.Request) {
data, _ := o.getGroupBytes()
http.ServeContent(w, r, "/openapi/v3", time.Now(), bytes.NewReader(data))
result := o.discoveryCache.Get()
if result.Err != nil {
klog.Errorf("Error serving discovery: %s", result.Err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Etag", strconv.Quote(result.Etag))
w.Header().Set("Content-Type", "application/json")
http.ServeContent(w, r, "/openapi/v3", result.Data.lastModified, bytes.NewReader(result.Data.spec))
}
func (o *OpenAPIService) HandleGroupVersion(w http.ResponseWriter, r *http.Request) {
@ -210,11 +235,13 @@ func (o *OpenAPIService) HandleGroupVersion(w http.ResponseWriter, r *http.Reque
}
accepted := []struct {
Type string
SubType string
Type string
SubType string
ReturnedContentType string
}{
{"application", subTypeJSON},
{"application", subTypeProtobuf},
{"application", subTypeJSON, "application/" + subTypeJSON},
{"application", subTypeProtobuf, "application/" + subTypeProtobuf},
{"application", subTypeProtobufDeprecated, "application/" + subTypeProtobuf},
}
for _, clause := range clauses {
@ -229,6 +256,9 @@ func (o *OpenAPIService) HandleGroupVersion(w http.ResponseWriter, r *http.Reque
if err != nil {
return
}
// Set Content-Type header in the reponse
w.Header().Set("Content-Type", accepts.ReturnedContentType)
// ETag must be enclosed in double quotes: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
w.Header().Set("Etag", strconv.Quote(etag))
@ -262,30 +292,3 @@ func (o *OpenAPIService) RegisterOpenAPIV3VersionedService(servePath string, han
handler.HandlePrefix(servePath+"/", http.HandlerFunc(o.HandleGroupVersion))
return nil
}
func (o *OpenAPIV3Group) UpdateSpec(openapi *spec3.OpenAPI) (err error) {
o.rwMutex.Lock()
defer o.rwMutex.Unlock()
o.jsonCache = o.jsonCache.New(func() ([]byte, error) {
return json.Marshal(openapi)
})
o.pbCache = o.pbCache.New(func() ([]byte, error) {
json, err := o.jsonCache.Get()
if err != nil {
return nil, err
}
return ToV3ProtoBinary(json)
})
// TODO: This forces a json marshal of corresponding group-versions.
// We should look to replace this with a faster hashing mechanism.
o.etagCache = o.etagCache.New(func() ([]byte, error) {
json, err := o.jsonCache.Get()
if err != nil {
return nil, err
}
return []byte(computeETag(json)), nil
})
o.lastModified = time.Now()
return nil
}

View File

@ -18,3 +18,7 @@ package internal
// Used by tests to selectively disable experimental JSON unmarshaler
var UseOptimizedJSONUnmarshaling bool = true
var UseOptimizedJSONUnmarshalingV3 bool = true
// Used by tests to selectively disable experimental JSON marshaler
var UseOptimizedJSONMarshaling bool = true

View File

@ -1,57 +0,0 @@
/*
Copyright 2021 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 handler
import (
"sync"
)
// HandlerCache represents a lazy cache for generating a byte array
// It is used to lazily marshal OpenAPI v2/v3 and lazily generate the ETag
type HandlerCache struct {
BuildCache func() ([]byte, error)
once sync.Once
bytes []byte
err error
}
// Get either returns the cached value or calls BuildCache() once before caching and returning
// its results. If BuildCache returns an error, the last valid value for the cache (from prior
// calls to New()) is used instead if possible.
func (c *HandlerCache) Get() ([]byte, error) {
c.once.Do(func() {
bytes, err := c.BuildCache()
// if there is an error updating the cache, there can be situations where
// c.bytes contains a valid value (carried over from the previous update)
// but c.err is also not nil; the cache user is expected to check for this
c.err = err
if c.err == nil {
// don't override previous spec if we had an error
c.bytes = bytes
}
})
return c.bytes, c.err
}
// New creates a new HandlerCache for situations where a cache refresh is needed.
// This function is not thread-safe and should not be called at the same time as Get().
func (c *HandlerCache) New(cacheBuilder func() ([]byte, error)) HandlerCache {
return HandlerCache{
bytes: c.bytes,
BuildCache: cacheBuilder,
}
}

View File

@ -0,0 +1,65 @@
/*
Copyright 2023 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 internal
import (
"github.com/go-openapi/jsonreference"
jsonv2 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
)
// DeterministicMarshal calls the jsonv2 library with the deterministic
// flag in order to have stable marshaling.
func DeterministicMarshal(in any) ([]byte, error) {
return jsonv2.MarshalOptions{Deterministic: true}.Marshal(jsonv2.EncodeOptions{}, in)
}
// JSONRefFromMap populates a json reference object if the map v contains a $ref key.
func JSONRefFromMap(jsonRef *jsonreference.Ref, v map[string]interface{}) error {
if v == nil {
return nil
}
if vv, ok := v["$ref"]; ok {
if str, ok := vv.(string); ok {
ref, err := jsonreference.New(str)
if err != nil {
return err
}
*jsonRef = ref
}
}
return nil
}
// SanitizeExtensions sanitizes the input map such that non extension
// keys (non x-*, X-*) keys are dropped from the map. Returns the new
// modified map, or nil if the map is now empty.
func SanitizeExtensions(e map[string]interface{}) map[string]interface{} {
for k := range e {
if !IsExtensionKey(k) {
delete(e, k)
}
}
if len(e) == 0 {
e = nil
}
return e
}
// IsExtensionKey returns true if the input string is of format x-* or X-*
func IsExtensionKey(k string) bool {
return len(k) > 1 && (k[0] == 'x' || k[0] == 'X') && k[1] == '-'
}

View File

@ -34,6 +34,13 @@ type MarshalOptions struct {
// unknown JSON object members.
DiscardUnknownMembers bool
// Deterministic specifies that the same input value will be serialized
// as the exact same output bytes. Different processes of
// the same program will serialize equal values to the same bytes,
// but different versions of the same program are not guaranteed
// to produce the exact same sequence of bytes.
Deterministic bool
// formatDepth is the depth at which we respect the format flag.
formatDepth int
// format is custom formatting for the value at the specified depth.

View File

@ -62,7 +62,7 @@ func unmarshalValueAny(uo UnmarshalOptions, dec *Decoder) (any, error) {
}
return dec.stringCache.make(val), nil
case '0':
fv, _ := parseFloat(val, 64) // ignore error since readValue gaurantees val is valid
fv, _ := parseFloat(val, 64) // ignore error since readValue guarantees val is valid
return fv, nil
default:
panic("BUG: invalid kind: " + k.String())
@ -99,13 +99,32 @@ func marshalObjectAny(mo MarshalOptions, enc *Encoder, obj map[string]any) error
if !enc.options.AllowInvalidUTF8 {
enc.tokens.last.disableNamespace()
}
for name, val := range obj {
if err := enc.WriteToken(String(name)); err != nil {
return err
if !mo.Deterministic || len(obj) <= 1 {
for name, val := range obj {
if err := enc.WriteToken(String(name)); err != nil {
return err
}
if err := marshalValueAny(mo, enc, val); err != nil {
return err
}
}
if err := marshalValueAny(mo, enc, val); err != nil {
return err
} else {
names := getStrings(len(obj))
var i int
for name := range obj {
(*names)[i] = name
i++
}
names.Sort()
for _, name := range *names {
if err := enc.WriteToken(String(name)); err != nil {
return err
}
if err := marshalValueAny(mo, enc, obj[name]); err != nil {
return err
}
}
putStrings(names)
}
if err := enc.WriteToken(ObjectEnd); err != nil {
return err

View File

@ -5,6 +5,7 @@
package json
import (
"bytes"
"encoding/base32"
"encoding/base64"
"encoding/hex"
@ -12,6 +13,7 @@ import (
"fmt"
"math"
"reflect"
"sort"
"strconv"
"sync"
)
@ -228,13 +230,7 @@ func makeBytesArshaler(t reflect.Type, fncs *arshaler) *arshaler {
}
}
val := enc.UnusedBuffer()
var b []byte
if va.Kind() == reflect.Array {
// TODO(https://go.dev/issue/47066): Avoid reflect.Value.Slice.
b = va.Slice(0, va.Len()).Bytes()
} else {
b = va.Bytes()
}
b := va.Bytes()
n := len(`"`) + encodedLen(len(b)) + len(`"`)
if cap(val) < n {
val = make([]byte, n)
@ -248,19 +244,19 @@ func makeBytesArshaler(t reflect.Type, fncs *arshaler) *arshaler {
}
unmarshalDefault := fncs.unmarshal
fncs.unmarshal = func(uo UnmarshalOptions, dec *Decoder, va addressableValue) error {
decode, decodedLen := decodeBase64, decodedLenBase64
decode, decodedLen, encodedLen := decodeBase64, decodedLenBase64, encodedLenBase64
if uo.format != "" && uo.formatDepth == dec.tokens.depth() {
switch uo.format {
case "base64":
decode, decodedLen = decodeBase64, decodedLenBase64
decode, decodedLen, encodedLen = decodeBase64, decodedLenBase64, encodedLenBase64
case "base64url":
decode, decodedLen = decodeBase64URL, decodedLenBase64URL
decode, decodedLen, encodedLen = decodeBase64URL, decodedLenBase64URL, encodedLenBase64URL
case "base32":
decode, decodedLen = decodeBase32, decodedLenBase32
decode, decodedLen, encodedLen = decodeBase32, decodedLenBase32, encodedLenBase32
case "base32hex":
decode, decodedLen = decodeBase32Hex, decodedLenBase32Hex
decode, decodedLen, encodedLen = decodeBase32Hex, decodedLenBase32Hex, encodedLenBase32Hex
case "base16", "hex":
decode, decodedLen = decodeBase16, decodedLenBase16
decode, decodedLen, encodedLen = decodeBase16, decodedLenBase16, encodedLenBase16
case "array":
uo.format = ""
return unmarshalDefault(uo, dec, va)
@ -290,23 +286,28 @@ func makeBytesArshaler(t reflect.Type, fncs *arshaler) *arshaler {
n--
}
n = decodedLen(n)
var b []byte
b := va.Bytes()
if va.Kind() == reflect.Array {
// TODO(https://go.dev/issue/47066): Avoid reflect.Value.Slice.
b = va.Slice(0, va.Len()).Bytes()
if n != len(b) {
err := fmt.Errorf("decoded base64 length of %d mismatches array length of %d", n, len(b))
return &SemanticError{action: "unmarshal", JSONKind: k, GoType: t, Err: err}
}
} else {
b = va.Bytes()
if b == nil || cap(b) < n {
b = make([]byte, n)
} else {
b = b[:n]
}
}
if _, err := decode(b, val); err != nil {
n2, err := decode(b, val)
if err == nil && len(val) != encodedLen(n2) {
// TODO(https://go.dev/issue/53845): RFC 4648, section 3.3,
// specifies that non-alphabet characters must be rejected.
// Unfortunately, the "base32" and "base64" packages allow
// '\r' and '\n' characters by default.
err = errors.New("illegal data at input byte " + strconv.Itoa(bytes.IndexAny(val, "\r\n")))
}
if err != nil {
return &SemanticError{action: "unmarshal", JSONKind: k, GoType: t, Err: err}
}
if va.Kind() == reflect.Slice {
@ -412,7 +413,7 @@ func makeUintArshaler(t reflect.Type) *arshaler {
return nil
}
x := math.Float64frombits(uint64(va.Uint()))
x := math.Float64frombits(va.Uint())
return enc.writeNumber(x, rawUintNumber, mo.StringifyNumbers)
}
fncs.unmarshal = func(uo UnmarshalOptions, dec *Decoder, va addressableValue) error {
@ -450,7 +451,7 @@ func makeUintArshaler(t reflect.Type) *arshaler {
err := fmt.Errorf("cannot parse %q as unsigned integer: %w", val, strconv.ErrRange)
return &SemanticError{action: "unmarshal", JSONKind: k, GoType: t, Err: err}
}
va.SetUint(uint64(n))
va.SetUint(n)
return nil
}
return &SemanticError{action: "unmarshal", JSONKind: k, GoType: t}
@ -549,23 +550,9 @@ func makeFloatArshaler(t reflect.Type) *arshaler {
return &fncs
}
var mapIterPool = sync.Pool{
New: func() any { return new(reflect.MapIter) },
}
func getMapIter(mv reflect.Value) *reflect.MapIter {
iter := mapIterPool.Get().(*reflect.MapIter)
iter.Reset(mv)
return iter
}
func putMapIter(iter *reflect.MapIter) {
iter.Reset(reflect.Value{}) // allow underlying map to be garbage collected
mapIterPool.Put(iter)
}
func makeMapArshaler(t reflect.Type) *arshaler {
// NOTE: The logic below disables namespaces for tracking duplicate names
// when handling map keys with a unique represention.
// when handling map keys with a unique representation.
// NOTE: Values retrieved from a map are not addressable,
// so we shallow copy the values to make them addressable and
@ -641,24 +628,76 @@ func makeMapArshaler(t reflect.Type) *arshaler {
enc.tokens.last.disableNamespace()
}
// NOTE: Map entries are serialized in a non-deterministic order.
// Users that need stable output should call RawValue.Canonicalize.
// TODO(go1.19): Remove use of a sync.Pool with reflect.MapIter.
// Calling reflect.Value.MapRange no longer allocates.
// See https://go.dev/cl/400675.
iter := getMapIter(va.Value)
defer putMapIter(iter)
for iter.Next() {
k.SetIterKey(iter)
if err := marshalKey(mko, enc, k); err != nil {
// TODO: If err is errMissingName, then wrap it as a
// SemanticError since this key type cannot be serialized
// as a JSON string.
return err
switch {
case !mo.Deterministic || n <= 1:
for iter := va.Value.MapRange(); iter.Next(); {
k.SetIterKey(iter)
if err := marshalKey(mko, enc, k); err != nil {
// TODO: If err is errMissingName, then wrap it as a
// SemanticError since this key type cannot be serialized
// as a JSON string.
return err
}
v.SetIterValue(iter)
if err := marshalVal(mo, enc, v); err != nil {
return err
}
}
v.SetIterValue(iter)
if err := marshalVal(mo, enc, v); err != nil {
return err
case !nonDefaultKey && t.Key().Kind() == reflect.String:
names := getStrings(n)
for i, iter := 0, va.Value.MapRange(); i < n && iter.Next(); i++ {
k.SetIterKey(iter)
(*names)[i] = k.String()
}
names.Sort()
for _, name := range *names {
if err := enc.WriteToken(String(name)); err != nil {
return err
}
// TODO(https://go.dev/issue/57061): Use v.SetMapIndexOf.
k.SetString(name)
v.Set(va.MapIndex(k.Value))
if err := marshalVal(mo, enc, v); err != nil {
return err
}
}
putStrings(names)
default:
type member struct {
name string // unquoted name
key addressableValue
}
members := make([]member, n)
keys := reflect.MakeSlice(reflect.SliceOf(t.Key()), n, n)
for i, iter := 0, va.Value.MapRange(); i < n && iter.Next(); i++ {
// Marshal the member name.
k := addressableValue{keys.Index(i)} // indexed slice element is always addressable
k.SetIterKey(iter)
if err := marshalKey(mko, enc, k); err != nil {
// TODO: If err is errMissingName, then wrap it as a
// SemanticError since this key type cannot be serialized
// as a JSON string.
return err
}
name := enc.unwriteOnlyObjectMemberName()
members[i] = member{name, k}
}
// TODO: If AllowDuplicateNames is enabled, then sort according
// to reflect.Value as well if the names are equal.
// See internal/fmtsort.
// TODO(https://go.dev/issue/47619): Use slices.SortFunc instead.
sort.Slice(members, func(i, j int) bool {
return lessUTF16(members[i].name, members[j].name)
})
for _, member := range members {
if err := enc.WriteToken(String(member.name)); err != nil {
return err
}
// TODO(https://go.dev/issue/57061): Use v.SetMapIndexOf.
v.Set(va.MapIndex(member.key.Value))
if err := marshalVal(mo, enc, v); err != nil {
return err
}
}
}
}
@ -856,7 +895,7 @@ func makeStructArshaler(t reflect.Type) *arshaler {
// 2. The object namespace is guaranteed to be disabled.
// 3. The object name is guaranteed to be valid and pre-escaped.
// 4. There is no need to flush the buffer (for unwrite purposes).
// 5. There is no possibility of an error occuring.
// 5. There is no possibility of an error occurring.
if optimizeCommon {
// Append any delimiters or optional whitespace.
if enc.tokens.last.length() > 0 {
@ -996,7 +1035,7 @@ func makeStructArshaler(t reflect.Type) *arshaler {
if fields.inlinedFallback == nil {
// Skip unknown value since we have no place to store it.
if err := dec.skipValue(); err != nil {
if err := dec.SkipValue(); err != nil {
return err
}
} else {

View File

@ -5,6 +5,7 @@
package json
import (
"bytes"
"errors"
"reflect"
)
@ -89,35 +90,61 @@ func marshalInlinedFallbackAll(mo MarshalOptions, enc *Encoder, va addressableVa
}
return nil
} else {
if v.Len() == 0 {
m := v // must be a map[string]V
n := m.Len()
if n == 0 {
return nil
}
m := v
mk := newAddressableValue(stringType)
mv := newAddressableValue(m.Type().Elem())
for iter := m.MapRange(); iter.Next(); {
b, err := appendString(enc.UnusedBuffer(), iter.Key().String(), !enc.options.AllowInvalidUTF8, nil)
marshalKey := func(mk addressableValue) error {
b, err := appendString(enc.UnusedBuffer(), mk.String(), !enc.options.AllowInvalidUTF8, nil)
if err != nil {
return err
}
if insertUnquotedName != nil {
isVerbatim := consumeSimpleString(b) == len(b)
isVerbatim := bytes.IndexByte(b, '\\') < 0
name := unescapeStringMayCopy(b, isVerbatim)
if !insertUnquotedName(name) {
return &SyntacticError{str: "duplicate name " + string(b) + " in object"}
}
}
if err := enc.WriteValue(b); err != nil {
return err
return enc.WriteValue(b)
}
marshalVal := f.fncs.marshal
if mo.Marshalers != nil {
marshalVal, _ = mo.Marshalers.lookup(marshalVal, mv.Type())
}
if !mo.Deterministic || n <= 1 {
for iter := m.MapRange(); iter.Next(); {
mk.SetIterKey(iter)
if err := marshalKey(mk); err != nil {
return err
}
mv.Set(iter.Value())
if err := marshalVal(mo, enc, mv); err != nil {
return err
}
}
mv.Set(iter.Value())
marshal := f.fncs.marshal
if mo.Marshalers != nil {
marshal, _ = mo.Marshalers.lookup(marshal, mv.Type())
} else {
names := getStrings(n)
for i, iter := 0, m.Value.MapRange(); i < n && iter.Next(); i++ {
mk.SetIterKey(iter)
(*names)[i] = mk.String()
}
if err := marshal(mo, enc, mv); err != nil {
return err
names.Sort()
for _, name := range *names {
mk.SetString(name)
if err := marshalKey(mk); err != nil {
return err
}
// TODO(https://go.dev/issue/57061): Use mv.SetMapIndexOf.
mv.Set(m.MapIndex(mk.Value))
if err := marshalVal(mo, enc, mv); err != nil {
return err
}
}
putStrings(names)
}
return nil
}
@ -162,7 +189,7 @@ func unmarshalInlinedFallbackNext(uo UnmarshalOptions, dec *Decoder, va addressa
} else {
name := string(unquotedName) // TODO: Intern this?
m := v
m := v // must be a map[string]V
if m.IsNil() {
m.Set(reflect.MakeMap(m.Type()))
}

View File

@ -21,8 +21,8 @@ var (
)
// MarshalerV1 is implemented by types that can marshal themselves.
// It is recommended that types implement MarshalerV2 unless
// the implementation is trying to avoid a hard dependency on this package.
// It is recommended that types implement MarshalerV2 unless the implementation
// is trying to avoid a hard dependency on the "jsontext" package.
//
// It is recommended that implementations return a buffer that is safe
// for the caller to retain and potentially mutate.

View File

@ -5,6 +5,7 @@
package json
import (
"errors"
"fmt"
"reflect"
"strings"
@ -85,25 +86,39 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
fncs.nonDefault = true
fncs.marshal = func(mo MarshalOptions, enc *Encoder, va addressableValue) error {
format := time.RFC3339Nano
isRFC3339 := true
if mo.format != "" && mo.formatDepth == enc.tokens.depth() {
var err error
format, err = checkTimeFormat(mo.format)
format, isRFC3339, err = checkTimeFormat(mo.format)
if err != nil {
return &SemanticError{action: "marshal", GoType: t, Err: err}
}
}
tt := va.Interface().(time.Time)
if y := tt.Year(); y < 0 || y >= 10000 {
// RFC 3339 is clear that years are 4 digits exactly.
// See https://go.dev/issue/4556#c15 for more discussion.
err := fmt.Errorf("year %d outside of range [0,9999]", y)
return &SemanticError{action: "marshal", GoType: t, Err: err}
}
b := enc.UnusedBuffer()
b = append(b, '"')
b = tt.AppendFormat(b, format)
b = append(b, '"')
if isRFC3339 {
// Not all Go timestamps can be represented as valid RFC 3339.
// Explicitly check for these edge cases.
// See https://go.dev/issue/4556 and https://go.dev/issue/54580.
var err error
switch b := b[len(`"`) : len(b)-len(`"`)]; {
case b[len("9999")] != '-': // year must be exactly 4 digits wide
err = errors.New("year outside of range [0,9999]")
case b[len(b)-1] != 'Z':
c := b[len(b)-len("Z07:00")]
if ('0' <= c && c <= '9') || parseDec2(b[len(b)-len("07:00"):]) >= 24 {
err = errors.New("timezone hour outside of range [0,23]")
}
}
if err != nil {
return &SemanticError{action: "marshal", GoType: t, Err: err}
}
return enc.WriteValue(b) // RFC 3339 never needs JSON escaping
}
// The format may contain special characters that need escaping.
// Verify that the result is a valid JSON string (common case),
// otherwise escape the string correctly (slower case).
@ -113,10 +128,11 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
return enc.WriteValue(b)
}
fncs.unmarshal = func(uo UnmarshalOptions, dec *Decoder, va addressableValue) error {
format := time.RFC3339Nano
format := time.RFC3339
isRFC3339 := true
if uo.format != "" && uo.formatDepth == dec.tokens.depth() {
var err error
format, err = checkTimeFormat(uo.format)
format, isRFC3339, err = checkTimeFormat(uo.format)
if err != nil {
return &SemanticError{action: "unmarshal", GoType: t, Err: err}
}
@ -136,6 +152,29 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
case '"':
val = unescapeStringMayCopy(val, flags.isVerbatim())
tt2, err := time.Parse(format, string(val))
if isRFC3339 && err == nil {
// TODO(https://go.dev/issue/54580): RFC 3339 specifies
// the exact grammar of a valid timestamp. However,
// the parsing functionality in "time" is too loose and
// incorrectly accepts invalid timestamps as valid.
// Remove these manual checks when "time" checks it for us.
newParseError := func(layout, value, layoutElem, valueElem, message string) error {
return &time.ParseError{Layout: layout, Value: value, LayoutElem: layoutElem, ValueElem: valueElem, Message: message}
}
switch {
case val[len("2006-01-02T")+1] == ':': // hour must be two digits
err = newParseError(format, string(val), "15", string(val[len("2006-01-02T"):][:1]), "")
case val[len("2006-01-02T15:04:05")] == ',': // sub-second separator must be a period
err = newParseError(format, string(val), ".", ",", "")
case val[len(val)-1] != 'Z':
switch {
case parseDec2(val[len(val)-len("07:00"):]) >= 24: // timezone hour must be in range
err = newParseError(format, string(val), "Z07:00", string(val[len(val)-len("Z07:00"):]), ": timezone hour out of range")
case parseDec2(val[len(val)-len("00"):]) >= 60: // timezone minute must be in range
err = newParseError(format, string(val), "Z07:00", string(val[len(val)-len("Z07:00"):]), ": timezone minute out of range")
}
}
}
if err != nil {
return &SemanticError{action: "unmarshal", JSONKind: k, GoType: t, Err: err}
}
@ -149,48 +188,54 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
return fncs
}
func checkTimeFormat(format string) (string, error) {
func checkTimeFormat(format string) (string, bool, error) {
// We assume that an exported constant in the time package will
// always start with an uppercase ASCII letter.
if len(format) > 0 && 'A' <= format[0] && format[0] <= 'Z' {
switch format {
case "ANSIC":
return time.ANSIC, nil
return time.ANSIC, false, nil
case "UnixDate":
return time.UnixDate, nil
return time.UnixDate, false, nil
case "RubyDate":
return time.RubyDate, nil
return time.RubyDate, false, nil
case "RFC822":
return time.RFC822, nil
return time.RFC822, false, nil
case "RFC822Z":
return time.RFC822Z, nil
return time.RFC822Z, false, nil
case "RFC850":
return time.RFC850, nil
return time.RFC850, false, nil
case "RFC1123":
return time.RFC1123, nil
return time.RFC1123, false, nil
case "RFC1123Z":
return time.RFC1123Z, nil
return time.RFC1123Z, false, nil
case "RFC3339":
return time.RFC3339, nil
return time.RFC3339, true, nil
case "RFC3339Nano":
return time.RFC3339Nano, nil
return time.RFC3339Nano, true, nil
case "Kitchen":
return time.Kitchen, nil
return time.Kitchen, false, nil
case "Stamp":
return time.Stamp, nil
return time.Stamp, false, nil
case "StampMilli":
return time.StampMilli, nil
return time.StampMilli, false, nil
case "StampMicro":
return time.StampMicro, nil
return time.StampMicro, false, nil
case "StampNano":
return time.StampNano, nil
return time.StampNano, false, nil
default:
// Reject any format that is an exported Go identifier in case
// new format constants are added to the time package.
if strings.TrimFunc(format, isLetterOrDigit) == "" {
return "", fmt.Errorf("undefined format layout: %v", format)
return "", false, fmt.Errorf("undefined format layout: %v", format)
}
}
}
return format, nil
return format, false, nil
}
// parseDec2 parses b as an unsigned, base-10, 2-digit number.
// It panics if len(b) < 2. The result is undefined if digits are not base-10.
func parseDec2(b []byte) byte {
return 10*(b[0]-'0') + (b[1] - '0')
}

View File

@ -347,9 +347,9 @@ func (d *Decoder) PeekKind() Kind {
return next
}
// skipValue is semantically equivalent to calling ReadValue and discarding
// SkipValue is semantically equivalent to calling ReadValue and discarding
// the result except that memory is not wasted trying to hold the entire result.
func (d *Decoder) skipValue() error {
func (d *Decoder) SkipValue() error {
switch d.PeekKind() {
case '{', '[':
// For JSON objects and arrays, keep skipping all tokens
@ -374,7 +374,7 @@ func (d *Decoder) skipValue() error {
}
// ReadToken reads the next Token, advancing the read offset.
// The returned token is only valid until the next Peek or Read call.
// The returned token is only valid until the next Peek, Read, or Skip call.
// It returns io.EOF if there are no more tokens.
func (d *Decoder) ReadToken() (Token, error) {
// Determine the next kind.
@ -585,7 +585,7 @@ func (f valueFlags) isCanonical() bool { return f&stringNonCanonical == 0 }
// ReadValue returns the next raw JSON value, advancing the read offset.
// The value is stripped of any leading or trailing whitespace.
// The returned value is only valid until the next Peek or Read call and
// The returned value is only valid until the next Peek, Read, or Skip call and
// may not be mutated while the Decoder remains in use.
// If the decoder is currently at the end token for an object or array,
// then it reports a SyntacticError and the internal state remains unchanged.
@ -1013,7 +1013,7 @@ func (d *Decoder) InputOffset() int64 {
// UnreadBuffer returns the data remaining in the unread buffer,
// which may contain zero or more bytes.
// The returned buffer must not be mutated while Decoder continues to be used.
// The buffer contents are valid until the next Peek or Read call.
// The buffer contents are valid until the next Peek, Read, or Skip call.
func (d *Decoder) UnreadBuffer() []byte {
return d.unreadBuffer()
}
@ -1213,7 +1213,7 @@ func consumeStringResumable(flags *valueFlags, b []byte, resumeOffset int, valid
return n, &SyntacticError{str: "invalid escape sequence " + strconv.Quote(string(b[n:n+6])) + " within string"}
}
// Only certain control characters can use the \uFFFF notation
// for canonical formating (per RFC 8785, section 3.2.2.2.).
// for canonical formatting (per RFC 8785, section 3.2.2.2.).
switch v1 {
// \uFFFF notation not permitted for these characters.
case '\b', '\f', '\n', '\r', '\t':

View File

@ -8,8 +8,7 @@
// primitive data types such as booleans, strings, and numbers,
// in addition to structured data types such as objects and arrays.
//
//
// Terminology
// # Terminology
//
// This package uses the terms "encode" and "decode" for syntactic functionality
// that is concerned with processing JSON based on its grammar, and
@ -32,8 +31,7 @@
//
// See RFC 8259 for more information.
//
//
// Specifications
// # Specifications
//
// Relevant specifications include RFC 4627, RFC 7159, RFC 7493, RFC 8259,
// and RFC 8785. Each RFC is generally a stricter subset of another RFC.
@ -60,8 +58,7 @@
// In particular, it makes specific choices about behavior that RFC 8259
// leaves as undefined in order to ensure greater interoperability.
//
//
// JSON Representation of Go structs
// # JSON Representation of Go structs
//
// A Go struct is naturally represented as a JSON object,
// where each Go struct field corresponds with a JSON object member.

View File

@ -347,6 +347,30 @@ func (e *Encoder) unwriteEmptyObjectMember(prevName *string) bool {
return true
}
// unwriteOnlyObjectMemberName unwrites the only object member name
// and returns the unquoted name.
func (e *Encoder) unwriteOnlyObjectMemberName() string {
if last := e.tokens.last; !last.isObject() || last.length() != 1 {
panic("BUG: must be called on an object after writing first name")
}
// Unwrite the name and whitespace.
b := trimSuffixString(e.buf)
isVerbatim := bytes.IndexByte(e.buf[len(b):], '\\') < 0
name := string(unescapeStringMayCopy(e.buf[len(b):], isVerbatim))
e.buf = trimSuffixWhitespace(b)
// Undo state changes.
e.tokens.last.decrement()
if !e.options.AllowDuplicateNames {
if e.tokens.last.isActiveNamespace() {
e.namespaces.last().removeLast()
}
e.names.clearLast()
}
return name
}
func trimSuffixWhitespace(b []byte) []byte {
// NOTE: The arguments and logic are kept simple to keep this inlineable.
n := len(b) - 1

View File

@ -8,6 +8,7 @@ import (
"bytes"
"io"
"math/bits"
"sort"
"sync"
)
@ -148,3 +149,34 @@ func putStreamingDecoder(d *Decoder) {
streamingDecoderPool.Put(d)
}
}
var stringsPools = &sync.Pool{New: func() any { return new(stringSlice) }}
type stringSlice []string
// getStrings returns a non-nil pointer to a slice with length n.
func getStrings(n int) *stringSlice {
s := stringsPools.Get().(*stringSlice)
if cap(*s) < n {
*s = make([]string, n)
}
*s = (*s)[:n]
return s
}
func putStrings(s *stringSlice) {
if cap(*s) > 1<<10 {
*s = nil // avoid pinning arbitrarily large amounts of memory
}
stringsPools.Put(s)
}
// Sort sorts the string slice according to RFC 8785, section 3.2.3.
func (ss *stringSlice) Sort() {
// TODO(https://go.dev/issue/47619): Use slices.SortFunc instead.
sort.Sort(ss)
}
func (ss *stringSlice) Len() int { return len(*ss) }
func (ss *stringSlice) Less(i, j int) bool { return lessUTF16((*ss)[i], (*ss)[j]) }
func (ss *stringSlice) Swap(i, j int) { (*ss)[i], (*ss)[j] = (*ss)[j], (*ss)[i] }

View File

@ -721,7 +721,7 @@ func (s *uintSet) has(i uint) bool {
return s.lo.has(i)
} else {
i -= 64
iHi, iLo := int(i/64), uint(i%64)
iHi, iLo := int(i/64), i%64
return iHi < len(s.hi) && s.hi[iHi].has(iLo)
}
}
@ -735,7 +735,7 @@ func (s *uintSet) insert(i uint) bool {
return !has
} else {
i -= 64
iHi, iLo := int(i/64), uint(i%64)
iHi, iLo := int(i/64), i%64
if iHi >= len(s.hi) {
s.hi = append(s.hi, make([]uintSet64, iHi+1-len(s.hi))...)
s.hi = s.hi[:cap(s.hi)]

View File

@ -112,7 +112,7 @@ func Bool(b bool) Token {
return False
}
// String construct a Token representing a JSON string.
// String constructs a Token representing a JSON string.
// The provided string should contain valid UTF-8, otherwise invalid characters
// may be mangled as the Unicode replacement character.
func String(s string) Token {
@ -225,7 +225,7 @@ func (t Token) appendString(dst []byte, validateUTF8, preserveRaw bool, escapeRu
}
// String returns the unescaped string value for a JSON string.
// For other JSON kinds, this returns the raw JSON represention.
// For other JSON kinds, this returns the raw JSON representation.
func (t Token) String() string {
// This is inlinable to take advantage of "function outlining".
// This avoids an allocation for the string(b) conversion
@ -373,10 +373,10 @@ func (t Token) Int() int64 {
case 'i':
return int64(t.num)
case 'u':
if uint64(t.num) > maxInt64 {
if t.num > maxInt64 {
return maxInt64
}
return int64(uint64(t.num))
return int64(t.num)
}
}
@ -425,7 +425,7 @@ func (t Token) Uint() uint64 {
// Handle exact integer value.
switch t.str[0] {
case 'u':
return uint64(t.num)
return t.num
case 'i':
if int64(t.num) < minUint64 {
return minUint64

View File

@ -263,7 +263,7 @@ func reorderObjects(d *Decoder, scratch *[]byte) {
afterValue := d.InputOffset()
if isSorted && len(*members) > 0 {
isSorted = lessUTF16(prevName, name)
isSorted = lessUTF16(prevName, []byte(name))
}
*members = append(*members, memberName{name, beforeName, afterValue})
prevName = name
@ -317,7 +317,7 @@ func reorderObjects(d *Decoder, scratch *[]byte) {
// to the UTF-16 codepoints of the UTF-8 encoded input strings.
// This implements the ordering specified in RFC 8785, section 3.2.3.
// The inputs must be valid UTF-8, otherwise this may panic.
func lessUTF16(x, y []byte) bool {
func lessUTF16[Bytes []byte | string](x, y Bytes) bool {
// NOTE: This is an optimized, allocation-free implementation
// of lessUTF16Simple in fuzz_test.go. FuzzLessUTF16 verifies that the
// two implementations agree on the result of comparing any two strings.
@ -326,8 +326,13 @@ func lessUTF16(x, y []byte) bool {
return ('\u0000' <= r && r <= '\uD7FF') || ('\uE000' <= r && r <= '\uFFFF')
}
var invalidUTF8 bool
x0, y0 := x, y
for {
if len(x) == 0 || len(y) == 0 {
if len(x) == len(y) && invalidUTF8 {
return string(x0) < string(y0)
}
return len(x) < len(y)
}
@ -341,35 +346,36 @@ func lessUTF16(x, y []byte) bool {
}
// Decode next pair of runes as UTF-8.
rx, nx := utf8.DecodeRune(x)
ry, ny := utf8.DecodeRune(y)
// TODO(https://go.dev/issue/56948): Use a generic implementation
// of utf8.DecodeRune, or rely on a compiler optimization to statically
// hide the cost of a type switch (https://go.dev/issue/57072).
var rx, ry rune
var nx, ny int
switch any(x).(type) {
case string:
rx, nx = utf8.DecodeRuneInString(string(x))
ry, ny = utf8.DecodeRuneInString(string(y))
case []byte:
rx, nx = utf8.DecodeRune([]byte(x))
ry, ny = utf8.DecodeRune([]byte(y))
}
selfx := isUTF16Self(rx)
selfy := isUTF16Self(ry)
switch {
// Both runes encode as either a single or surrogate pair
// of UTF-16 codepoints.
case isUTF16Self(rx) == isUTF16Self(ry):
if rx != ry {
return rx < ry
}
// The x rune is a single UTF-16 codepoint, while
// the y rune is a surrogate pair of UTF-16 codepoints.
case isUTF16Self(rx):
ry, _ := utf16.EncodeRune(ry)
if rx != ry {
return rx < ry
}
panic("BUG: invalid UTF-8") // implies rx is an unpaired surrogate half
case selfx && !selfy:
ry, _ = utf16.EncodeRune(ry)
// The y rune is a single UTF-16 codepoint, while
// the x rune is a surrogate pair of UTF-16 codepoints.
case isUTF16Self(ry):
rx, _ := utf16.EncodeRune(rx)
if rx != ry {
return rx < ry
}
panic("BUG: invalid UTF-8") // implies ry is an unpaired surrogate half
case selfy && !selfx:
rx, _ = utf16.EncodeRune(rx)
}
if rx != ry {
return rx < ry
}
invalidUTF8 = invalidUTF8 || (rx == utf8.RuneError && nx == 1) || (ry == utf8.RuneError && ny == 1)
x, y = x[nx:], y[ny:]
}
}

260
vendor/k8s.io/kube-openapi/pkg/schemaconv/openapi.go generated vendored Normal file
View File

@ -0,0 +1,260 @@
/*
Copyright 2022 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 schemaconv
import (
"errors"
"path"
"strings"
"k8s.io/kube-openapi/pkg/validation/spec"
"sigs.k8s.io/structured-merge-diff/v4/schema"
)
// ToSchemaFromOpenAPI converts a directory of OpenAPI schemas to an smd Schema.
// - models: a map from definition name to OpenAPI V3 structural schema for each definition.
// Key in map is used to resolve references in the schema.
// - preserveUnknownFields: flag indicating whether unknown fields in all schemas should be preserved.
// - returns: nil and an error if there is a parse error, or if schema does not satisfy a
// required structural schema invariant for conversion. If no error, returns
// a new smd schema.
//
// Schema should be validated as structural before using with this function, or
// there may be information lost.
func ToSchemaFromOpenAPI(models map[string]*spec.Schema, preserveUnknownFields bool) (*schema.Schema, error) {
c := convert{
preserveUnknownFields: preserveUnknownFields,
output: &schema.Schema{},
}
for name, spec := range models {
// Skip/Ignore top-level references
if len(spec.Ref.String()) > 0 {
continue
}
var a schema.Atom
// Hard-coded schemas for now as proto_models implementation functions.
// https://github.com/kubernetes/kube-openapi/issues/364
if name == quantityResource {
a = schema.Atom{
Scalar: untypedDef.Atom.Scalar,
}
} else if name == rawExtensionResource {
a = untypedDef.Atom
} else {
c2 := c.push(name, &a)
c2.visitSpec(spec)
c.pop(c2)
}
c.insertTypeDef(name, a)
}
if len(c.errorMessages) > 0 {
return nil, errors.New(strings.Join(c.errorMessages, "\n"))
}
c.addCommonTypes()
return c.output, nil
}
func (c *convert) visitSpec(m *spec.Schema) {
// Check if this schema opts its descendants into preserve-unknown-fields
if p, ok := m.Extensions["x-kubernetes-preserve-unknown-fields"]; ok && p == true {
c.preserveUnknownFields = true
}
a := c.top()
*a = c.parseSchema(m)
}
func (c *convert) parseSchema(m *spec.Schema) schema.Atom {
// k8s-generated OpenAPI specs have historically used only one value for
// type and starting with OpenAPIV3 it is only allowed to be
// a single string.
typ := ""
if len(m.Type) > 0 {
typ = m.Type[0]
}
// Structural Schemas produced by kubernetes follow very specific rules which
// we can use to infer the SMD type:
switch typ {
case "":
// According to Swagger docs:
// https://swagger.io/docs/specification/data-models/data-types/#any
//
// If no type is specified, it is equivalent to accepting any type.
return schema.Atom{
Scalar: ptr(schema.Scalar("untyped")),
List: c.parseList(m),
Map: c.parseObject(m),
}
case "object":
return schema.Atom{
Map: c.parseObject(m),
}
case "array":
return schema.Atom{
List: c.parseList(m),
}
case "integer", "boolean", "number", "string":
return convertPrimitive(typ, m.Format)
default:
c.reportError("unrecognized type: '%v'", typ)
return schema.Atom{
Scalar: ptr(schema.Scalar("untyped")),
}
}
}
func (c *convert) makeOpenAPIRef(specSchema *spec.Schema) schema.TypeRef {
refString := specSchema.Ref.String()
// Special-case handling for $ref stored inside a single-element allOf
if len(refString) == 0 && len(specSchema.AllOf) == 1 && len(specSchema.AllOf[0].Ref.String()) > 0 {
refString = specSchema.AllOf[0].Ref.String()
}
if _, n := path.Split(refString); len(n) > 0 {
//!TODO: Refactor the field ElementRelationship override
// we can generate the types with overrides ahead of time rather than
// requiring the hacky runtime support
// (could just create a normalized key struct containing all customizations
// to deduplicate)
mapRelationship, err := getMapElementRelationship(specSchema.Extensions)
if err != nil {
c.reportError(err.Error())
}
if len(mapRelationship) > 0 {
return schema.TypeRef{
NamedType: &n,
ElementRelationship: &mapRelationship,
}
}
return schema.TypeRef{
NamedType: &n,
}
}
var inlined schema.Atom
// compute the type inline
c2 := c.push("inlined in "+c.currentName, &inlined)
c2.preserveUnknownFields = c.preserveUnknownFields
c2.visitSpec(specSchema)
c.pop(c2)
return schema.TypeRef{
Inlined: inlined,
}
}
func (c *convert) parseObject(s *spec.Schema) *schema.Map {
var fields []schema.StructField
for name, member := range s.Properties {
fields = append(fields, schema.StructField{
Name: name,
Type: c.makeOpenAPIRef(&member),
Default: member.Default,
})
}
// AdditionalProperties informs the schema of any "unknown" keys
// Unknown keys are enforced by the ElementType field.
elementType := func() schema.TypeRef {
if s.AdditionalProperties == nil {
// According to openAPI spec, an object without properties and without
// additionalProperties is assumed to be a free-form object.
if c.preserveUnknownFields || len(s.Properties) == 0 {
return schema.TypeRef{
NamedType: &deducedName,
}
}
// If properties are specified, do not implicitly allow unknown
// fields
return schema.TypeRef{}
} else if s.AdditionalProperties.Schema != nil {
// Unknown fields use the referred schema
return c.makeOpenAPIRef(s.AdditionalProperties.Schema)
} else if s.AdditionalProperties.Allows {
// A boolean instead of a schema was provided. Deduce the
// type from the value provided at runtime.
return schema.TypeRef{
NamedType: &deducedName,
}
} else {
// Additional Properties are explicitly disallowed by the user.
// Ensure element type is empty.
return schema.TypeRef{}
}
}()
relationship, err := getMapElementRelationship(s.Extensions)
if err != nil {
c.reportError(err.Error())
}
return &schema.Map{
Fields: fields,
ElementRelationship: relationship,
ElementType: elementType,
}
}
func (c *convert) parseList(s *spec.Schema) *schema.List {
relationship, mapKeys, err := getListElementRelationship(s.Extensions)
if err != nil {
c.reportError(err.Error())
}
elementType := func() schema.TypeRef {
if s.Items != nil {
if s.Items.Schema == nil || s.Items.Len() != 1 {
c.reportError("structural schema arrays must have exactly one member subtype")
return schema.TypeRef{
NamedType: &deducedName,
}
}
subSchema := s.Items.Schema
if subSchema == nil {
subSchema = &s.Items.Schemas[0]
}
return c.makeOpenAPIRef(subSchema)
} else if len(s.Type) > 0 && len(s.Type[0]) > 0 {
c.reportError("`items` must be specified on arrays")
}
// A list with no items specified is treated as "untyped".
return schema.TypeRef{
NamedType: &untypedName,
}
}()
return &schema.List{
ElementRelationship: relationship,
Keys: mapKeys,
ElementType: elementType,
}
}

View File

@ -0,0 +1,178 @@
/*
Copyright 2022 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 schemaconv
import (
"errors"
"path"
"strings"
"k8s.io/kube-openapi/pkg/util/proto"
"sigs.k8s.io/structured-merge-diff/v4/schema"
)
// ToSchema converts openapi definitions into a schema suitable for structured
// merge (i.e. kubectl apply v2).
func ToSchema(models proto.Models) (*schema.Schema, error) {
return ToSchemaWithPreserveUnknownFields(models, false)
}
// ToSchemaWithPreserveUnknownFields converts openapi definitions into a schema suitable for structured
// merge (i.e. kubectl apply v2), it will preserve unknown fields if specified.
func ToSchemaWithPreserveUnknownFields(models proto.Models, preserveUnknownFields bool) (*schema.Schema, error) {
c := convert{
preserveUnknownFields: preserveUnknownFields,
output: &schema.Schema{},
}
for _, name := range models.ListModels() {
model := models.LookupModel(name)
var a schema.Atom
c2 := c.push(name, &a)
model.Accept(c2)
c.pop(c2)
c.insertTypeDef(name, a)
}
if len(c.errorMessages) > 0 {
return nil, errors.New(strings.Join(c.errorMessages, "\n"))
}
c.addCommonTypes()
return c.output, nil
}
func (c *convert) makeRef(model proto.Schema, preserveUnknownFields bool) schema.TypeRef {
var tr schema.TypeRef
if r, ok := model.(*proto.Ref); ok {
if r.Reference() == "io.k8s.apimachinery.pkg.runtime.RawExtension" {
return schema.TypeRef{
NamedType: &untypedName,
}
}
// reference a named type
_, n := path.Split(r.Reference())
tr.NamedType = &n
mapRelationship, err := getMapElementRelationship(model.GetExtensions())
if err != nil {
c.reportError(err.Error())
}
// empty string means unset.
if len(mapRelationship) > 0 {
tr.ElementRelationship = &mapRelationship
}
} else {
// compute the type inline
c2 := c.push("inlined in "+c.currentName, &tr.Inlined)
c2.preserveUnknownFields = preserveUnknownFields
model.Accept(c2)
c.pop(c2)
if tr == (schema.TypeRef{}) {
// emit warning?
tr.NamedType = &untypedName
}
}
return tr
}
func (c *convert) VisitKind(k *proto.Kind) {
preserveUnknownFields := c.preserveUnknownFields
if p, ok := k.GetExtensions()["x-kubernetes-preserve-unknown-fields"]; ok && p == true {
preserveUnknownFields = true
}
a := c.top()
a.Map = &schema.Map{}
for _, name := range k.FieldOrder {
member := k.Fields[name]
tr := c.makeRef(member, preserveUnknownFields)
a.Map.Fields = append(a.Map.Fields, schema.StructField{
Name: name,
Type: tr,
Default: member.GetDefault(),
})
}
unions, err := makeUnions(k.GetExtensions())
if err != nil {
c.reportError(err.Error())
return
}
// TODO: We should check that the fields and discriminator
// specified in the union are actual fields in the struct.
a.Map.Unions = unions
if preserveUnknownFields {
a.Map.ElementType = schema.TypeRef{
NamedType: &deducedName,
}
}
a.Map.ElementRelationship, err = getMapElementRelationship(k.GetExtensions())
if err != nil {
c.reportError(err.Error())
}
}
func (c *convert) VisitArray(a *proto.Array) {
relationship, mapKeys, err := getListElementRelationship(a.GetExtensions())
if err != nil {
c.reportError(err.Error())
}
atom := c.top()
atom.List = &schema.List{
ElementType: c.makeRef(a.SubType, c.preserveUnknownFields),
ElementRelationship: relationship,
Keys: mapKeys,
}
}
func (c *convert) VisitMap(m *proto.Map) {
relationship, err := getMapElementRelationship(m.GetExtensions())
if err != nil {
c.reportError(err.Error())
}
a := c.top()
a.Map = &schema.Map{
ElementType: c.makeRef(m.SubType, c.preserveUnknownFields),
ElementRelationship: relationship,
}
}
func (c *convert) VisitPrimitive(p *proto.Primitive) {
a := c.top()
if c.currentName == quantityResource {
a.Scalar = ptr(schema.Scalar("untyped"))
} else {
*a = convertPrimitive(p.Type, p.Format)
}
}
func (c *convert) VisitArbitrary(a *proto.Arbitrary) {
*c.top() = deducedDef.Atom
}
func (c *convert) VisitReference(proto.Reference) {
// Do nothing, we handle references specially
}

View File

@ -17,43 +17,18 @@ limitations under the License.
package schemaconv
import (
"errors"
"fmt"
"path"
"sort"
"strings"
"k8s.io/kube-openapi/pkg/util/proto"
"sigs.k8s.io/structured-merge-diff/v4/schema"
)
const (
quantityResource = "io.k8s.apimachinery.pkg.api.resource.Quantity"
quantityResource = "io.k8s.apimachinery.pkg.api.resource.Quantity"
rawExtensionResource = "io.k8s.apimachinery.pkg.runtime.RawExtension"
)
// ToSchema converts openapi definitions into a schema suitable for structured
// merge (i.e. kubectl apply v2).
func ToSchema(models proto.Models) (*schema.Schema, error) {
return ToSchemaWithPreserveUnknownFields(models, false)
}
// ToSchemaWithPreserveUnknownFields converts openapi definitions into a schema suitable for structured
// merge (i.e. kubectl apply v2), it will preserve unknown fields if specified.
func ToSchemaWithPreserveUnknownFields(models proto.Models, preserveUnknownFields bool) (*schema.Schema, error) {
c := convert{
input: models,
preserveUnknownFields: preserveUnknownFields,
output: &schema.Schema{},
}
if err := c.convertAll(); err != nil {
return nil, err
}
c.addCommonTypes()
return c.output, nil
}
type convert struct {
input proto.Models
preserveUnknownFields bool
output *schema.Schema
@ -64,7 +39,6 @@ type convert struct {
func (c *convert) push(name string, a *schema.Atom) *convert {
return &convert{
input: c.input,
preserveUnknownFields: c.preserveUnknownFields,
output: c.output,
currentName: name,
@ -78,30 +52,17 @@ func (c *convert) pop(c2 *convert) {
c.errorMessages = append(c.errorMessages, c2.errorMessages...)
}
func (c *convert) convertAll() error {
for _, name := range c.input.ListModels() {
model := c.input.LookupModel(name)
c.insertTypeDef(name, model)
}
if len(c.errorMessages) > 0 {
return errors.New(strings.Join(c.errorMessages, "\n"))
}
return nil
}
func (c *convert) reportError(format string, args ...interface{}) {
c.errorMessages = append(c.errorMessages,
c.currentName+": "+fmt.Sprintf(format, args...),
)
}
func (c *convert) insertTypeDef(name string, model proto.Schema) {
func (c *convert) insertTypeDef(name string, atom schema.Atom) {
def := schema.TypeDef{
Name: name,
Atom: atom,
}
c2 := c.push(name, &def.Atom)
model.Accept(c2)
c.pop(c2)
if def.Atom == (schema.Atom{}) {
// This could happen if there were a top-level reference.
return
@ -156,46 +117,6 @@ var deducedDef schema.TypeDef = schema.TypeDef{
},
}
func (c *convert) makeRef(model proto.Schema, preserveUnknownFields bool) schema.TypeRef {
var tr schema.TypeRef
if r, ok := model.(*proto.Ref); ok {
if r.Reference() == "io.k8s.apimachinery.pkg.runtime.RawExtension" {
return schema.TypeRef{
NamedType: &untypedName,
}
}
// reference a named type
_, n := path.Split(r.Reference())
tr.NamedType = &n
ext := model.GetExtensions()
if val, ok := ext["x-kubernetes-map-type"]; ok {
switch val {
case "atomic":
relationship := schema.Atomic
tr.ElementRelationship = &relationship
case "granular":
relationship := schema.Separable
tr.ElementRelationship = &relationship
default:
c.reportError("unknown map type %v", val)
}
}
} else {
// compute the type inline
c2 := c.push("inlined in "+c.currentName, &tr.Inlined)
c2.preserveUnknownFields = preserveUnknownFields
model.Accept(c2)
c.pop(c2)
if tr == (schema.TypeRef{}) {
// emit warning?
tr.NamedType = &untypedName
}
}
return tr
}
func makeUnions(extensions map[string]interface{}) ([]schema.Union, error) {
schemaUnions := []schema.Union{}
if iunions, ok := extensions["x-kubernetes-unions"]; ok {
@ -299,52 +220,6 @@ func makeUnion(extensions map[string]interface{}) (schema.Union, error) {
return union, nil
}
func (c *convert) VisitKind(k *proto.Kind) {
preserveUnknownFields := c.preserveUnknownFields
if p, ok := k.GetExtensions()["x-kubernetes-preserve-unknown-fields"]; ok && p == true {
preserveUnknownFields = true
}
a := c.top()
a.Map = &schema.Map{}
for _, name := range k.FieldOrder {
member := k.Fields[name]
tr := c.makeRef(member, preserveUnknownFields)
a.Map.Fields = append(a.Map.Fields, schema.StructField{
Name: name,
Type: tr,
Default: member.GetDefault(),
})
}
unions, err := makeUnions(k.GetExtensions())
if err != nil {
c.reportError(err.Error())
return
}
// TODO: We should check that the fields and discriminator
// specified in the union are actual fields in the struct.
a.Map.Unions = unions
if preserveUnknownFields {
a.Map.ElementType = schema.TypeRef{
NamedType: &deducedName,
}
}
ext := k.GetExtensions()
if val, ok := ext["x-kubernetes-map-type"]; ok {
switch val {
case "atomic":
a.Map.ElementRelationship = schema.Atomic
case "granular":
a.Map.ElementRelationship = schema.Separable
default:
c.reportError("unknown map type %v", val)
}
}
}
func toStringSlice(o interface{}) (out []string, ok bool) {
switch t := o.(type) {
case []interface{}:
@ -355,117 +230,108 @@ func toStringSlice(o interface{}) (out []string, ok bool) {
}
}
return out, true
case []string:
return t, true
}
return nil, false
}
func (c *convert) VisitArray(a *proto.Array) {
atom := c.top()
atom.List = &schema.List{
ElementRelationship: schema.Atomic,
}
l := atom.List
l.ElementType = c.makeRef(a.SubType, c.preserveUnknownFields)
ext := a.GetExtensions()
if val, ok := ext["x-kubernetes-list-type"]; ok {
if val == "atomic" {
l.ElementRelationship = schema.Atomic
} else if val == "set" {
l.ElementRelationship = schema.Associative
} else if val == "map" {
l.ElementRelationship = schema.Associative
if keys, ok := ext["x-kubernetes-list-map-keys"]; ok {
if keyNames, ok := toStringSlice(keys); ok {
l.Keys = keyNames
} else {
c.reportError("uninterpreted map keys: %#v", keys)
}
} else {
c.reportError("missing map keys")
}
} else {
c.reportError("unknown list type %v", val)
l.ElementRelationship = schema.Atomic
}
} else if val, ok := ext["x-kubernetes-patch-strategy"]; ok {
if val == "merge" || val == "merge,retainKeys" {
l.ElementRelationship = schema.Associative
if key, ok := ext["x-kubernetes-patch-merge-key"]; ok {
if keyName, ok := key.(string); ok {
l.Keys = []string{keyName}
} else {
c.reportError("uninterpreted merge key: %#v", key)
}
} else {
// It's not an error for this to be absent, it
// means it's a set.
}
} else if val == "retainKeys" {
} else {
c.reportError("unknown patch strategy %v", val)
l.ElementRelationship = schema.Atomic
}
}
}
func (c *convert) VisitMap(m *proto.Map) {
a := c.top()
a.Map = &schema.Map{}
a.Map.ElementType = c.makeRef(m.SubType, c.preserveUnknownFields)
ext := m.GetExtensions()
if val, ok := ext["x-kubernetes-map-type"]; ok {
switch val {
case "atomic":
a.Map.ElementRelationship = schema.Atomic
case "granular":
a.Map.ElementRelationship = schema.Separable
default:
c.reportError("unknown map type %v", val)
}
}
}
func ptr(s schema.Scalar) *schema.Scalar { return &s }
func (c *convert) VisitPrimitive(p *proto.Primitive) {
a := c.top()
if c.currentName == quantityResource {
a.Scalar = ptr(schema.Scalar("untyped"))
} else {
switch p.Type {
case proto.Integer:
a.Scalar = ptr(schema.Numeric)
case proto.Number:
a.Scalar = ptr(schema.Numeric)
case proto.String:
switch p.Format {
case "":
a.Scalar = ptr(schema.String)
case "byte":
// byte really means []byte and is encoded as a string.
a.Scalar = ptr(schema.String)
case "int-or-string":
a.Scalar = ptr(schema.Scalar("untyped"))
case "date-time":
a.Scalar = ptr(schema.Scalar("untyped"))
default:
a.Scalar = ptr(schema.Scalar("untyped"))
}
case proto.Boolean:
a.Scalar = ptr(schema.Boolean)
// Basic conversion functions to convert OpenAPI schema definitions to
// SMD Schema atoms
func convertPrimitive(typ string, format string) (a schema.Atom) {
switch typ {
case "integer":
a.Scalar = ptr(schema.Numeric)
case "number":
a.Scalar = ptr(schema.Numeric)
case "string":
switch format {
case "":
a.Scalar = ptr(schema.String)
case "byte":
// byte really means []byte and is encoded as a string.
a.Scalar = ptr(schema.String)
case "int-or-string":
a.Scalar = ptr(schema.Scalar("untyped"))
case "date-time":
a.Scalar = ptr(schema.Scalar("untyped"))
default:
a.Scalar = ptr(schema.Scalar("untyped"))
}
case "boolean":
a.Scalar = ptr(schema.Boolean)
default:
a.Scalar = ptr(schema.Scalar("untyped"))
}
return a
}
func getListElementRelationship(ext map[string]any) (schema.ElementRelationship, []string, error) {
if val, ok := ext["x-kubernetes-list-type"]; ok {
switch val {
case "atomic":
return schema.Atomic, nil, nil
case "set":
return schema.Associative, nil, nil
case "map":
keys, ok := ext["x-kubernetes-list-map-keys"]
if !ok {
return schema.Associative, nil, fmt.Errorf("missing map keys")
}
keyNames, ok := toStringSlice(keys)
if !ok {
return schema.Associative, nil, fmt.Errorf("uninterpreted map keys: %#v", keys)
}
return schema.Associative, keyNames, nil
default:
return schema.Atomic, nil, fmt.Errorf("unknown list type %v", val)
}
} else if val, ok := ext["x-kubernetes-patch-strategy"]; ok {
switch val {
case "merge", "merge,retainKeys":
if key, ok := ext["x-kubernetes-patch-merge-key"]; ok {
keyName, ok := key.(string)
if !ok {
return schema.Associative, nil, fmt.Errorf("uninterpreted merge key: %#v", key)
}
return schema.Associative, []string{keyName}, nil
}
// It's not an error for x-kubernetes-patch-merge-key to be absent,
// it means it's a set
return schema.Associative, nil, nil
case "retainKeys":
return schema.Atomic, nil, nil
default:
return schema.Atomic, nil, fmt.Errorf("unknown patch strategy %v", val)
}
}
// Treat as atomic by default
return schema.Atomic, nil, nil
}
// Returns map element relationship if specified, or empty string if unspecified
func getMapElementRelationship(ext map[string]any) (schema.ElementRelationship, error) {
val, ok := ext["x-kubernetes-map-type"]
if !ok {
// unset Map element relationship
return "", nil
}
switch val {
case "atomic":
return schema.Atomic, nil
case "granular":
return schema.Separable, nil
default:
return "", fmt.Errorf("unknown map type %v", val)
}
}
func (c *convert) VisitArbitrary(a *proto.Arbitrary) {
*c.top() = deducedDef.Atom
}
func (c *convert) VisitReference(proto.Reference) {
// Do nothing, we handle references specially
}

View File

@ -18,7 +18,10 @@ package spec3
import (
"encoding/json"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/internal"
jsonv2 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
"k8s.io/kube-openapi/pkg/validation/spec"
)
@ -41,6 +44,9 @@ func (e *Encoding) MarshalJSON() ([]byte, error) {
}
func (e *Encoding) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, e)
}
if err := json.Unmarshal(data, &e.EncodingProps); err != nil {
return err
}
@ -50,6 +56,20 @@ func (e *Encoding) UnmarshalJSON(data []byte) error {
return nil
}
func (e *Encoding) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error {
var x struct {
spec.Extensions
EncodingProps
}
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
e.Extensions = internal.SanitizeExtensions(x.Extensions)
e.EncodingProps = x.EncodingProps
return nil
}
type EncodingProps struct {
// Content Type for encoding a specific property
ContentType string `json:"contentType,omitempty"`
@ -58,7 +78,7 @@ type EncodingProps struct {
// Describes how a specific property value will be serialized depending on its type
Style string `json:"style,omitempty"`
// When this is true, property values of type array or object generate separate parameters for each value of the array, or key-value-pair of the map. For other types of properties this property has no effect
Explode string `json:"explode,omitempty"`
Explode bool `json:"explode,omitempty"`
// AllowReserved determines whether the parameter value SHOULD allow reserved characters, as defined by RFC3986
AllowReserved bool `json:"allowReserved,omitempty"`
}

View File

@ -19,8 +19,11 @@ package spec3
import (
"encoding/json"
"k8s.io/kube-openapi/pkg/validation/spec"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/internal"
jsonv2 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
"k8s.io/kube-openapi/pkg/validation/spec"
)
// Example https://swagger.io/specification/#example-object
@ -49,6 +52,9 @@ func (e *Example) MarshalJSON() ([]byte, error) {
}
func (e *Example) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, e)
}
if err := json.Unmarshal(data, &e.Refable); err != nil {
return err
}
@ -61,6 +67,23 @@ func (e *Example) UnmarshalJSON(data []byte) error {
return nil
}
func (e *Example) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error {
var x struct {
spec.Extensions
ExampleProps
}
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
if err := internal.JSONRefFromMap(&e.Ref.Ref, x.Extensions); err != nil {
return err
}
e.Extensions = internal.SanitizeExtensions(x.Extensions)
e.ExampleProps = x.ExampleProps
return nil
}
type ExampleProps struct {
// Summary holds a short description of the example
Summary string `json:"summary,omitempty"`

View File

@ -18,8 +18,11 @@ package spec3
import (
"encoding/json"
"k8s.io/kube-openapi/pkg/validation/spec"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/internal"
jsonv2 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
"k8s.io/kube-openapi/pkg/validation/spec"
)
type ExternalDocumentation struct {
@ -48,6 +51,9 @@ func (e *ExternalDocumentation) MarshalJSON() ([]byte, error) {
}
func (e *ExternalDocumentation) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, e)
}
if err := json.Unmarshal(data, &e.ExternalDocumentationProps); err != nil {
return err
}
@ -56,3 +62,16 @@ func (e *ExternalDocumentation) UnmarshalJSON(data []byte) error {
}
return nil
}
func (e *ExternalDocumentation) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error {
var x struct {
spec.Extensions
ExternalDocumentationProps
}
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
e.Extensions = internal.SanitizeExtensions(x.Extensions)
e.ExternalDocumentationProps = x.ExternalDocumentationProps
return nil
}

254
vendor/k8s.io/kube-openapi/pkg/spec3/fuzz.go generated vendored Normal file
View File

@ -0,0 +1,254 @@
package spec3
import (
"math/rand"
"strings"
fuzz "github.com/google/gofuzz"
"k8s.io/kube-openapi/pkg/validation/spec"
)
// refChance is the chance that a particular component will use a $ref
// instead of fuzzed. Expressed as a fraction 1/n, currently there is
// a 1/3 chance that a ref will be used.
const refChance = 3
const alphaNumChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func randAlphanumString() string {
arr := make([]string, rand.Intn(10)+5)
for i := 0; i < len(arr); i++ {
arr[i] = string(alphaNumChars[rand.Intn(len(alphaNumChars))])
}
return strings.Join(arr, "")
}
var OpenAPIV3FuzzFuncs []interface{} = []interface{}{
func(s *string, c fuzz.Continue) {
// All OpenAPI V3 map keys must follow the corresponding
// regex. Note that this restricts the range for all other
// string values as well.
str := randAlphanumString()
*s = str
},
func(o *OpenAPI, c fuzz.Continue) {
c.FuzzNoCustom(o)
o.Version = "3.0.0"
},
func(r *interface{}, c fuzz.Continue) {
switch c.Intn(3) {
case 0:
*r = nil
case 1:
n := c.RandString() + "x"
*r = n
case 2:
n := c.Float64()
*r = n
}
},
func(v **spec.Info, c fuzz.Continue) {
// Info is never nil
*v = &spec.Info{}
c.FuzzNoCustom(*v)
(*v).Title = c.RandString() + "x"
},
func(v *Paths, c fuzz.Continue) {
c.Fuzz(&v.VendorExtensible)
num := c.Intn(5)
if num > 0 {
v.Paths = make(map[string]*Path)
}
for i := 0; i < num; i++ {
val := Path{}
c.Fuzz(&val)
v.Paths["/"+c.RandString()] = &val
}
},
func(v *SecurityScheme, c fuzz.Continue) {
if c.Intn(refChance) == 0 {
c.Fuzz(&v.Refable)
return
}
switch c.Intn(4) {
case 0:
v.Type = "apiKey"
v.Name = c.RandString() + "x"
switch c.Intn(3) {
case 0:
v.In = "query"
case 1:
v.In = "header"
case 2:
v.In = "cookie"
}
case 1:
v.Type = "http"
case 2:
v.Type = "oauth2"
v.Flows = make(map[string]*OAuthFlow)
flow := OAuthFlow{}
flow.AuthorizationUrl = c.RandString() + "x"
v.Flows["implicit"] = &flow
flow.Scopes = make(map[string]string)
flow.Scopes["foo"] = "bar"
case 3:
v.Type = "openIdConnect"
v.OpenIdConnectUrl = "https://" + c.RandString()
}
v.Scheme = "basic"
},
func(v *spec.Ref, c fuzz.Continue) {
switch c.Intn(7) {
case 0:
*v = spec.MustCreateRef("#/components/schemas/" + randAlphanumString())
case 1:
*v = spec.MustCreateRef("#/components/responses/" + randAlphanumString())
case 2:
*v = spec.MustCreateRef("#/components/headers/" + randAlphanumString())
case 3:
*v = spec.MustCreateRef("#/components/securitySchemes/" + randAlphanumString())
case 5:
*v = spec.MustCreateRef("#/components/parameters/" + randAlphanumString())
case 6:
*v = spec.MustCreateRef("#/components/requestBodies/" + randAlphanumString())
}
},
func(v *Parameter, c fuzz.Continue) {
if c.Intn(refChance) == 0 {
c.Fuzz(&v.Refable)
return
}
c.Fuzz(&v.ParameterProps)
c.Fuzz(&v.VendorExtensible)
switch c.Intn(3) {
case 0:
// Header param
v.In = "query"
case 1:
v.In = "header"
case 2:
v.In = "cookie"
}
},
func(v *RequestBody, c fuzz.Continue) {
if c.Intn(refChance) == 0 {
c.Fuzz(&v.Refable)
return
}
c.Fuzz(&v.RequestBodyProps)
c.Fuzz(&v.VendorExtensible)
},
func(v *Header, c fuzz.Continue) {
if c.Intn(refChance) == 0 {
c.Fuzz(&v.Refable)
return
}
c.Fuzz(&v.HeaderProps)
c.Fuzz(&v.VendorExtensible)
},
func(v *ResponsesProps, c fuzz.Continue) {
c.Fuzz(&v.Default)
n := c.Intn(5)
for i := 0; i < n; i++ {
r2 := Response{}
c.Fuzz(&r2)
// HTTP Status code in 100-599 Range
code := c.Intn(500) + 100
v.StatusCodeResponses = make(map[int]*Response)
v.StatusCodeResponses[code] = &r2
}
},
func(v *Response, c fuzz.Continue) {
if c.Intn(refChance) == 0 {
c.Fuzz(&v.Refable)
return
}
c.Fuzz(&v.ResponseProps)
c.Fuzz(&v.VendorExtensible)
},
func(v *spec.Extensions, c fuzz.Continue) {
numChildren := c.Intn(5)
for i := 0; i < numChildren; i++ {
if *v == nil {
*v = spec.Extensions{}
}
(*v)["x-"+c.RandString()] = c.RandString()
}
},
func(v *spec.ExternalDocumentation, c fuzz.Continue) {
c.Fuzz(&v.Description)
v.URL = "https://" + randAlphanumString()
},
func(v *spec.SchemaURL, c fuzz.Continue) {
*v = spec.SchemaURL("https://" + randAlphanumString())
},
func(v *spec.SchemaOrBool, c fuzz.Continue) {
*v = spec.SchemaOrBool{}
if c.RandBool() {
v.Allows = c.RandBool()
} else {
v.Schema = &spec.Schema{}
v.Allows = true
c.Fuzz(&v.Schema)
}
},
func(v *spec.SchemaOrArray, c fuzz.Continue) {
*v = spec.SchemaOrArray{}
if c.RandBool() {
schema := spec.Schema{}
c.Fuzz(&schema)
v.Schema = &schema
} else {
v.Schemas = []spec.Schema{}
numChildren := c.Intn(5)
for i := 0; i < numChildren; i++ {
schema := spec.Schema{}
c.Fuzz(&schema)
v.Schemas = append(v.Schemas, schema)
}
}
},
func(v *spec.SchemaOrStringArray, c fuzz.Continue) {
if c.RandBool() {
*v = spec.SchemaOrStringArray{}
if c.RandBool() {
c.Fuzz(&v.Property)
} else {
c.Fuzz(&v.Schema)
}
}
},
func(v *spec.Schema, c fuzz.Continue) {
if c.Intn(refChance) == 0 {
c.Fuzz(&v.Ref)
return
}
if c.RandBool() {
// file schema
c.Fuzz(&v.Default)
c.Fuzz(&v.Description)
c.Fuzz(&v.Example)
c.Fuzz(&v.ExternalDocs)
c.Fuzz(&v.Format)
c.Fuzz(&v.ReadOnly)
c.Fuzz(&v.Required)
c.Fuzz(&v.Title)
v.Type = spec.StringOrArray{"file"}
} else {
// normal schema
c.Fuzz(&v.SchemaProps)
c.Fuzz(&v.SwaggerSchemaProps)
c.Fuzz(&v.VendorExtensible)
c.Fuzz(&v.ExtraProps)
}
},
}

View File

@ -20,6 +20,8 @@ import (
"encoding/json"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/internal"
jsonv2 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
"k8s.io/kube-openapi/pkg/validation/spec"
)
@ -50,6 +52,9 @@ func (h *Header) MarshalJSON() ([]byte, error) {
}
func (h *Header) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, h)
}
if err := json.Unmarshal(data, &h.Refable); err != nil {
return err
}
@ -63,6 +68,22 @@ func (h *Header) UnmarshalJSON(data []byte) error {
return nil
}
func (h *Header) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error {
var x struct {
spec.Extensions
HeaderProps
}
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
if err := internal.JSONRefFromMap(&h.Ref.Ref, x.Extensions); err != nil {
return err
}
h.Extensions = internal.SanitizeExtensions(x.Extensions)
h.HeaderProps = x.HeaderProps
return nil
}
// HeaderProps a struct that describes a header object
type HeaderProps struct {
// Description holds a brief description of the parameter

View File

@ -18,7 +18,10 @@ package spec3
import (
"encoding/json"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/internal"
jsonv2 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
"k8s.io/kube-openapi/pkg/validation/spec"
)
@ -44,6 +47,9 @@ func (m *MediaType) MarshalJSON() ([]byte, error) {
}
func (m *MediaType) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, m)
}
if err := json.Unmarshal(data, &m.MediaTypeProps); err != nil {
return err
}
@ -53,10 +59,24 @@ func (m *MediaType) UnmarshalJSON(data []byte) error {
return nil
}
func (m *MediaType) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error {
var x struct {
spec.Extensions
MediaTypeProps
}
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
m.Extensions = internal.SanitizeExtensions(x.Extensions)
m.MediaTypeProps = x.MediaTypeProps
return nil
}
// MediaTypeProps a struct that allows you to specify content format, more at https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#mediaTypeObject
type MediaTypeProps struct {
// Schema holds the schema defining the type used for the media type
Schema *spec.Schema `json:"schema,omitempty"`
Schema *spec.Schema `json:"schema,omitempty"`
// Example of the media type
Example interface{} `json:"example,omitempty"`
// Examples of the media type. Each example object should match the media type and specific schema if present

View File

@ -19,8 +19,10 @@ package spec3
import (
"encoding/json"
"k8s.io/kube-openapi/pkg/validation/spec"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/internal"
jsonv2 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
"k8s.io/kube-openapi/pkg/validation/spec"
)
// Operation describes a single API operation on a path, more at https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#operationObject
@ -46,12 +48,28 @@ func (o *Operation) MarshalJSON() ([]byte, error) {
// UnmarshalJSON hydrates this items instance with the data from JSON
func (o *Operation) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, o)
}
if err := json.Unmarshal(data, &o.OperationProps); err != nil {
return err
}
return json.Unmarshal(data, &o.VendorExtensible)
}
func (o *Operation) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error {
var x struct {
spec.Extensions
OperationProps
}
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
o.Extensions = internal.SanitizeExtensions(x.Extensions)
o.OperationProps = x.OperationProps
return nil
}
// OperationProps describes a single API operation on a path, more at https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#operationObject
type OperationProps struct {
// Tags holds a list of tags for API documentation control
@ -73,7 +91,7 @@ type OperationProps struct {
// Deprecated declares this operation to be deprecated
Deprecated bool `json:"deprecated,omitempty"`
// SecurityRequirement holds a declaration of which security mechanisms can be used for this operation
SecurityRequirement []*SecurityRequirement `json:"security,omitempty"`
SecurityRequirement []map[string][]string `json:"security,omitempty"`
// Servers contains an alternative server array to service this operation
Servers []*Server `json:"servers,omitempty"`
}

View File

@ -20,6 +20,8 @@ import (
"encoding/json"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/internal"
jsonv2 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
"k8s.io/kube-openapi/pkg/validation/spec"
)
@ -50,6 +52,10 @@ func (p *Parameter) MarshalJSON() ([]byte, error) {
}
func (p *Parameter) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, p)
}
if err := json.Unmarshal(data, &p.Refable); err != nil {
return err
}
@ -63,6 +69,22 @@ func (p *Parameter) UnmarshalJSON(data []byte) error {
return nil
}
func (p *Parameter) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error {
var x struct {
spec.Extensions
ParameterProps
}
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
if err := internal.JSONRefFromMap(&p.Ref.Ref, x.Extensions); err != nil {
return err
}
p.Extensions = internal.SanitizeExtensions(x.Extensions)
p.ParameterProps = x.ParameterProps
return nil
}
// ParameterProps a struct that describes a single operation parameter, more at https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#parameterObject
type ParameterProps struct {
// Name holds the name of the parameter

View File

@ -18,10 +18,13 @@ package spec3
import (
"encoding/json"
"fmt"
"strings"
"k8s.io/kube-openapi/pkg/validation/spec"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/internal"
jsonv2 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
"k8s.io/kube-openapi/pkg/validation/spec"
)
// Paths describes the available paths and operations for the API, more at https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#pathsObject
@ -45,6 +48,9 @@ func (p *Paths) MarshalJSON() ([]byte, error) {
// UnmarshalJSON hydrates this items instance with the data from JSON
func (p *Paths) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, p)
}
var res map[string]json.RawMessage
if err := json.Unmarshal(data, &res); err != nil {
return err
@ -74,6 +80,59 @@ func (p *Paths) UnmarshalJSON(data []byte) error {
return nil
}
func (p *Paths) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error {
tok, err := dec.ReadToken()
if err != nil {
return err
}
switch k := tok.Kind(); k {
case 'n':
*p = Paths{}
return nil
case '{':
for {
tok, err := dec.ReadToken()
if err != nil {
return err
}
if tok.Kind() == '}' {
return nil
}
switch k := tok.String(); {
case internal.IsExtensionKey(k):
var ext any
if err := opts.UnmarshalNext(dec, &ext); err != nil {
return err
}
if p.Extensions == nil {
p.Extensions = make(map[string]any)
}
p.Extensions[k] = ext
case len(k) > 0 && k[0] == '/':
pi := Path{}
if err := opts.UnmarshalNext(dec, &pi); err != nil {
return err
}
if p.Paths == nil {
p.Paths = make(map[string]*Path)
}
p.Paths[k] = &pi
default:
_, err := dec.ReadValue() // skip value
if err != nil {
return err
}
}
}
default:
return fmt.Errorf("unknown JSON kind: %v", k)
}
}
// Path describes the operations available on a single path, more at https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#pathItemObject
//
// Note that this struct is actually a thin wrapper around PathProps to make it referable and extensible
@ -101,6 +160,9 @@ func (p *Path) MarshalJSON() ([]byte, error) {
}
func (p *Path) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, p)
}
if err := json.Unmarshal(data, &p.Refable); err != nil {
return err
}
@ -113,6 +175,24 @@ func (p *Path) UnmarshalJSON(data []byte) error {
return nil
}
func (p *Path) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error {
var x struct {
spec.Extensions
PathProps
}
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
if err := internal.JSONRefFromMap(&p.Ref.Ref, x.Extensions); err != nil {
return err
}
p.Extensions = internal.SanitizeExtensions(x.Extensions)
p.PathProps = x.PathProps
return nil
}
// PathProps describes the operations available on a single path, more at https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#pathItemObject
type PathProps struct {
// Summary holds a summary for all operations in this path

View File

@ -19,8 +19,10 @@ package spec3
import (
"encoding/json"
"k8s.io/kube-openapi/pkg/validation/spec"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/internal"
jsonv2 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
"k8s.io/kube-openapi/pkg/validation/spec"
)
// RequestBody describes a single request body, more at https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#requestBodyObject
@ -50,6 +52,9 @@ func (r *RequestBody) MarshalJSON() ([]byte, error) {
}
func (r *RequestBody) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, r)
}
if err := json.Unmarshal(data, &r.Refable); err != nil {
return err
}
@ -71,3 +76,19 @@ type RequestBodyProps struct {
// Required determines if the request body is required in the request
Required bool `json:"required,omitempty"`
}
func (r *RequestBody) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error {
var x struct {
spec.Extensions
RequestBodyProps
}
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
if err := internal.JSONRefFromMap(&r.Ref.Ref, x.Extensions); err != nil {
return err
}
r.Extensions = internal.SanitizeExtensions(x.Extensions)
r.RequestBodyProps = x.RequestBodyProps
return nil
}

View File

@ -18,10 +18,13 @@ package spec3
import (
"encoding/json"
"fmt"
"strconv"
"k8s.io/kube-openapi/pkg/validation/spec"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/internal"
jsonv2 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
"k8s.io/kube-openapi/pkg/validation/spec"
)
// Responses holds the list of possible responses as they are returned from executing this operation
@ -46,13 +49,15 @@ func (r *Responses) MarshalJSON() ([]byte, error) {
}
func (r *Responses) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, r)
}
if err := json.Unmarshal(data, &r.ResponsesProps); err != nil {
return err
}
if err := json.Unmarshal(data, &r.VendorExtensible); err != nil {
return err
}
return nil
}
@ -78,25 +83,91 @@ func (r ResponsesProps) MarshalJSON() ([]byte, error) {
// UnmarshalJSON unmarshals responses from JSON
func (r *ResponsesProps) UnmarshalJSON(data []byte) error {
var res map[string]*Response
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, r)
}
var res map[string]json.RawMessage
if err := json.Unmarshal(data, &res); err != nil {
return nil
return err
}
if v, ok := res["default"]; ok {
r.Default = v
value := Response{}
if err := json.Unmarshal(v, &value); err != nil {
return err
}
r.Default = &value
delete(res, "default")
}
for k, v := range res {
// Take all integral keys
if nk, err := strconv.Atoi(k); err == nil {
if r.StatusCodeResponses == nil {
r.StatusCodeResponses = map[int]*Response{}
}
r.StatusCodeResponses[nk] = v
value := Response{}
if err := json.Unmarshal(v, &value); err != nil {
return err
}
r.StatusCodeResponses[nk] = &value
}
}
return nil
}
func (r *Responses) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) (err error) {
tok, err := dec.ReadToken()
if err != nil {
return err
}
switch k := tok.Kind(); k {
case 'n':
*r = Responses{}
return nil
case '{':
for {
tok, err := dec.ReadToken()
if err != nil {
return err
}
if tok.Kind() == '}' {
return nil
}
switch k := tok.String(); {
case internal.IsExtensionKey(k):
var ext any
if err := opts.UnmarshalNext(dec, &ext); err != nil {
return err
}
if r.Extensions == nil {
r.Extensions = make(map[string]any)
}
r.Extensions[k] = ext
case k == "default":
resp := Response{}
if err := opts.UnmarshalNext(dec, &resp); err != nil {
return err
}
r.ResponsesProps.Default = &resp
default:
if nk, err := strconv.Atoi(k); err == nil {
resp := Response{}
if err := opts.UnmarshalNext(dec, &resp); err != nil {
return err
}
if r.StatusCodeResponses == nil {
r.StatusCodeResponses = map[int]*Response{}
}
r.StatusCodeResponses[nk] = &resp
}
}
}
default:
return fmt.Errorf("unknown JSON kind: %v", k)
}
}
// Response describes a single response from an API Operation, more at https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#responseObject
//
// Note that this struct is actually a thin wrapper around ResponseProps to make it referable and extensible
@ -124,6 +195,9 @@ func (r *Response) MarshalJSON() ([]byte, error) {
}
func (r *Response) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, r)
}
if err := json.Unmarshal(data, &r.Refable); err != nil {
return err
}
@ -133,7 +207,22 @@ func (r *Response) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &r.VendorExtensible); err != nil {
return err
}
return nil
}
func (r *Response) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error {
var x struct {
spec.Extensions
ResponseProps
}
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
if err := internal.JSONRefFromMap(&r.Ref.Ref, x.Extensions); err != nil {
return err
}
r.Extensions = internal.SanitizeExtensions(x.Extensions)
r.ResponseProps = x.ResponseProps
return nil
}
@ -149,7 +238,6 @@ type ResponseProps struct {
Links map[string]*Link `json:"links,omitempty"`
}
// Link represents a possible design-time link for a response, more at https://swagger.io/specification/#link-object
type Link struct {
spec.Refable
@ -175,6 +263,9 @@ func (r *Link) MarshalJSON() ([]byte, error) {
}
func (r *Link) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, r)
}
if err := json.Unmarshal(data, &r.Refable); err != nil {
return err
}
@ -188,6 +279,22 @@ func (r *Link) UnmarshalJSON(data []byte) error {
return nil
}
func (l *Link) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error {
var x struct {
spec.Extensions
LinkProps
}
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
if err := internal.JSONRefFromMap(&l.Ref.Ref, x.Extensions); err != nil {
return err
}
l.Extensions = internal.SanitizeExtensions(x.Extensions)
l.LinkProps = x.LinkProps
return nil
}
// LinkProps describes a single response from an API Operation, more at https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#responseObject
type LinkProps struct {
// OperationId is the name of an existing, resolvable OAS operation

View File

@ -1,56 +0,0 @@
/*
Copyright 2021 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 spec3
import (
"encoding/json"
"k8s.io/kube-openapi/pkg/validation/spec"
"github.com/go-openapi/swag"
)
// SecurityRequirementProps describes the required security schemes to execute an operation, more at https://swagger.io/specification/#security-requirement-object
//
// Note that this struct is actually a thin wrapper around SecurityRequirementProps to make it referable and extensible
type SecurityRequirement struct {
SecurityRequirementProps
spec.VendorExtensible
}
// MarshalJSON is a custom marshal function that knows how to encode SecurityRequirement as JSON
func (s *SecurityRequirement) MarshalJSON() ([]byte, error) {
b1, err := json.Marshal(s.SecurityRequirementProps)
if err != nil {
return nil, err
}
b2, err := json.Marshal(s.VendorExtensible)
if err != nil {
return nil, err
}
return swag.ConcatJSON(b1, b2), nil
}
// UnmarshalJSON hydrates this items instance with the data from JSON
func (s *SecurityRequirement) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &s.SecurityRequirementProps); err != nil {
return err
}
return json.Unmarshal(data, &s.VendorExtensible)
}
// SecurityRequirementProps describes the required security schemes to execute an operation, more at https://swagger.io/specification/#security-requirement-object
type SecurityRequirementProps map[string][]string

View File

@ -19,8 +19,8 @@ package spec3
import (
"encoding/json"
"k8s.io/kube-openapi/pkg/validation/spec"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/validation/spec"
)
// SecurityScheme defines reusable Security Scheme Object, more at https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#securitySchemeObject

View File

@ -18,9 +18,11 @@ package spec3
import (
"encoding/json"
"k8s.io/kube-openapi/pkg/validation/spec"
"github.com/go-openapi/swag"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/internal"
jsonv2 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
"k8s.io/kube-openapi/pkg/validation/spec"
)
type Server struct {
@ -51,6 +53,10 @@ func (s *Server) MarshalJSON() ([]byte, error) {
}
func (s *Server) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, s)
}
if err := json.Unmarshal(data, &s.ServerProps); err != nil {
return err
}
@ -60,6 +66,20 @@ func (s *Server) UnmarshalJSON(data []byte) error {
return nil
}
func (s *Server) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error {
var x struct {
spec.Extensions
ServerProps
}
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
s.Extensions = internal.SanitizeExtensions(x.Extensions)
s.ServerProps = x.ServerProps
return nil
}
type ServerVariable struct {
ServerVariableProps
spec.VendorExtensible
@ -88,6 +108,9 @@ func (s *ServerVariable) MarshalJSON() ([]byte, error) {
}
func (s *ServerVariable) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, s)
}
if err := json.Unmarshal(data, &s.ServerVariableProps); err != nil {
return err
}
@ -96,3 +119,17 @@ func (s *ServerVariable) UnmarshalJSON(data []byte) error {
}
return nil
}
func (s *ServerVariable) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error {
var x struct {
spec.Extensions
ServerVariableProps
}
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
s.Extensions = internal.SanitizeExtensions(x.Extensions)
s.ServerVariableProps = x.ServerVariableProps
return nil
}

View File

@ -17,6 +17,10 @@ limitations under the License.
package spec3
import (
"encoding/json"
"k8s.io/kube-openapi/pkg/internal"
jsonv2 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
"k8s.io/kube-openapi/pkg/validation/spec"
)
@ -35,3 +39,12 @@ type OpenAPI struct {
// ExternalDocs holds additional external documentation
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty"`
}
func (o *OpenAPI) UnmarshalJSON(data []byte) error {
type OpenAPIWithNoFunctions OpenAPI
p := (*OpenAPIWithNoFunctions)(o)
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, &p)
}
return json.Unmarshal(data, &p)
}

View File

@ -120,7 +120,7 @@ func (d *Definitions) ParseSchemaV3(s *openapi_v3.Schema, path *Path) (Schema, e
switch s.GetType() {
case object:
for _, extension := range s.GetSpecificationExtension() {
if extension.Name == "x-kuberentes-group-version-kind" {
if extension.Name == "x-kubernetes-group-version-kind" {
// Objects with x-kubernetes-group-version-kind are always top
// level types.
return d.parseV3Kind(s, path)
@ -285,7 +285,7 @@ func parseV3Interface(def *yaml.Node) (interface{}, error) {
func (d *Definitions) parseV3BaseSchema(s *openapi_v3.Schema, path *Path) (*BaseSchema, error) {
if s == nil {
return nil, fmt.Errorf("cannot initializae BaseSchema from nil")
return nil, fmt.Errorf("cannot initialize BaseSchema from nil")
}
def, err := parseV3Interface(s.GetDefault().ToRawInfo())

79
vendor/k8s.io/kube-openapi/pkg/util/trie.go generated vendored Normal file
View File

@ -0,0 +1,79 @@
/*
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 util
// A simple trie implementation with Add and HasPrefix methods only.
type Trie struct {
children map[byte]*Trie
wordTail bool
word string
}
// NewTrie creates a Trie and add all strings in the provided list to it.
func NewTrie(list []string) Trie {
ret := Trie{
children: make(map[byte]*Trie),
wordTail: false,
}
for _, v := range list {
ret.Add(v)
}
return ret
}
// Add adds a word to this trie
func (t *Trie) Add(v string) {
root := t
for _, b := range []byte(v) {
child, exists := root.children[b]
if !exists {
child = &Trie{
children: make(map[byte]*Trie),
wordTail: false,
}
root.children[b] = child
}
root = child
}
root.wordTail = true
root.word = v
}
// HasPrefix returns true of v has any of the prefixes stored in this trie.
func (t *Trie) HasPrefix(v string) bool {
_, has := t.GetPrefix(v)
return has
}
// GetPrefix is like HasPrefix but return the prefix in case of match or empty string otherwise.
func (t *Trie) GetPrefix(v string) (string, bool) {
root := t
if root.wordTail {
return root.word, true
}
for _, b := range []byte(v) {
child, exists := root.children[b]
if !exists {
return "", false
}
if child.wordTail {
return child.word, true
}
root = child
}
return "", false
}

115
vendor/k8s.io/kube-openapi/pkg/util/util.go generated vendored Normal file
View File

@ -0,0 +1,115 @@
/*
Copyright 2017 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 util
import (
"reflect"
"strings"
)
// [DEPRECATED] ToCanonicalName converts Golang package/type canonical name into REST friendly OpenAPI name.
// This method is deprecated because it has a misleading name. Please use ToRESTFriendlyName
// instead
//
// NOTE: actually the "canonical name" in this method should be named "REST friendly OpenAPI name",
// which is different from "canonical name" defined in GetCanonicalTypeName. The "canonical name" defined
// in GetCanonicalTypeName means Go type names with full package path.
//
// Examples of REST friendly OpenAPI name:
//
// Input: k8s.io/api/core/v1.Pod
// Output: io.k8s.api.core.v1.Pod
//
// Input: k8s.io/api/core/v1
// Output: io.k8s.api.core.v1
//
// Input: csi.storage.k8s.io/v1alpha1.CSINodeInfo
// Output: io.k8s.storage.csi.v1alpha1.CSINodeInfo
func ToCanonicalName(name string) string {
return ToRESTFriendlyName(name)
}
// ToRESTFriendlyName converts Golang package/type canonical name into REST friendly OpenAPI name.
//
// Examples of REST friendly OpenAPI name:
//
// Input: k8s.io/api/core/v1.Pod
// Output: io.k8s.api.core.v1.Pod
//
// Input: k8s.io/api/core/v1
// Output: io.k8s.api.core.v1
//
// Input: csi.storage.k8s.io/v1alpha1.CSINodeInfo
// Output: io.k8s.storage.csi.v1alpha1.CSINodeInfo
func ToRESTFriendlyName(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, ".")
}
// OpenAPICanonicalTypeNamer is an interface for models without Go type to seed model name.
//
// OpenAPI canonical names are Go type names with full package path, for uniquely indentifying
// a model / Go type. If a Go type is vendored from another package, only the path after "/vendor/"
// should be used. For custom resource definition (CRD), the canonical name is expected to be
//
// group/version.kind
//
// Examples of canonical name:
//
// Go type: k8s.io/kubernetes/pkg/apis/core.Pod
// CRD: csi.storage.k8s.io/v1alpha1.CSINodeInfo
//
// Example for vendored Go type:
//
// Original full path: k8s.io/kubernetes/vendor/k8s.io/api/core/v1.Pod
// Canonical name: k8s.io/api/core/v1.Pod
//
// Original full path: vendor/k8s.io/api/core/v1.Pod
// Canonical name: k8s.io/api/core/v1.Pod
type OpenAPICanonicalTypeNamer interface {
OpenAPICanonicalTypeName() string
}
// GetCanonicalTypeName will find the canonical type name of a sample object, removing
// the "vendor" part of the path
func GetCanonicalTypeName(model interface{}) string {
if namer, ok := model.(OpenAPICanonicalTypeNamer); ok {
return namer.OpenAPICanonicalTypeName()
}
t := reflect.TypeOf(model)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.PkgPath() == "" {
return t.Name()
}
path := t.PkgPath()
if strings.Contains(path, "/vendor/") {
path = path[strings.Index(path, "/vendor/")+len("/vendor/"):]
} else if strings.HasPrefix(path, "vendor/") {
path = strings.TrimPrefix(path, "vendor/")
}
return path + "." + t.Name()
}

View File

@ -43,6 +43,9 @@ type Header struct {
// MarshalJSON marshal this to JSON
func (h Header) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(h)
}
b1, err := json.Marshal(h.CommonValidations)
if err != nil {
return nil, err
@ -62,6 +65,20 @@ func (h Header) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2, b3, b4), nil
}
func (h Header) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
CommonValidations commonValidationsOmitZero `json:",inline"`
SimpleSchema simpleSchemaOmitZero `json:",inline"`
Extensions
HeaderProps
}
x.CommonValidations = commonValidationsOmitZero(h.CommonValidations)
x.SimpleSchema = simpleSchemaOmitZero(h.SimpleSchema)
x.Extensions = internal.SanitizeExtensions(h.Extensions)
x.HeaderProps = h.HeaderProps
return opts.MarshalNext(enc, x)
}
// UnmarshalJSON unmarshals this header from JSON
func (h *Header) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshaling {
@ -94,12 +111,8 @@ func (h *Header) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Dec
h.CommonValidations = x.CommonValidations
h.SimpleSchema = x.SimpleSchema
h.Extensions = x.Extensions
h.Extensions = internal.SanitizeExtensions(x.Extensions)
h.HeaderProps = x.HeaderProps
h.Extensions.sanitize()
if len(h.Extensions) == 0 {
h.Extensions = nil
}
return nil
}

View File

@ -89,17 +89,9 @@ func (e Extensions) GetObject(key string, out interface{}) error {
return nil
}
func (e Extensions) sanitize() {
for k := range e {
if !isExtensionKey(k) {
delete(e, k)
}
}
}
func (e Extensions) sanitizeWithExtra() (extra map[string]any) {
for k, v := range e {
if !isExtensionKey(k) {
if !internal.IsExtensionKey(k) {
if extra == nil {
extra = make(map[string]any)
}
@ -110,10 +102,6 @@ func (e Extensions) sanitizeWithExtra() (extra map[string]any) {
return extra
}
func isExtensionKey(k string) bool {
return len(k) > 1 && (k[0] == 'x' || k[0] == 'X') && k[1] == '-'
}
// VendorExtensible composition block.
type VendorExtensible struct {
Extensions Extensions
@ -181,6 +169,9 @@ type Info struct {
// MarshalJSON marshal this to JSON
func (i Info) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(i)
}
b1, err := json.Marshal(i.InfoProps)
if err != nil {
return nil, err
@ -192,6 +183,16 @@ func (i Info) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}
func (i Info) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Extensions
InfoProps
}
x.Extensions = i.Extensions
x.InfoProps = i.InfoProps
return opts.MarshalNext(enc, x)
}
// UnmarshalJSON marshal this from JSON
func (i *Info) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshaling {
@ -212,11 +213,7 @@ func (i *Info) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decod
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
x.Extensions.sanitize()
if len(x.Extensions) == 0 {
x.Extensions = nil
}
i.VendorExtensible.Extensions = x.Extensions
i.Extensions = internal.SanitizeExtensions(x.Extensions)
i.InfoProps = x.InfoProps
return nil
}

View File

@ -37,6 +37,18 @@ type SimpleSchema struct {
Example interface{} `json:"example,omitempty"`
}
// Marshaling structure only, always edit along with corresponding
// struct (or compilation will fail).
type simpleSchemaOmitZero struct {
Type string `json:"type,omitempty"`
Nullable bool `json:"nullable,omitzero"`
Format string `json:"format,omitempty"`
Items *Items `json:"items,omitzero"`
CollectionFormat string `json:"collectionFormat,omitempty"`
Default interface{} `json:"default,omitempty"`
Example interface{} `json:"example,omitempty"`
}
// CommonValidations describe common JSON-schema validations
type CommonValidations struct {
Maximum *float64 `json:"maximum,omitempty"`
@ -53,6 +65,23 @@ type CommonValidations struct {
Enum []interface{} `json:"enum,omitempty"`
}
// Marshaling structure only, always edit along with corresponding
// struct (or compilation will fail).
type commonValidationsOmitZero struct {
Maximum *float64 `json:"maximum,omitempty"`
ExclusiveMaximum bool `json:"exclusiveMaximum,omitzero"`
Minimum *float64 `json:"minimum,omitempty"`
ExclusiveMinimum bool `json:"exclusiveMinimum,omitzero"`
MaxLength *int64 `json:"maxLength,omitempty"`
MinLength *int64 `json:"minLength,omitempty"`
Pattern string `json:"pattern,omitempty"`
MaxItems *int64 `json:"maxItems,omitempty"`
MinItems *int64 `json:"minItems,omitempty"`
UniqueItems bool `json:"uniqueItems,omitzero"`
MultipleOf *float64 `json:"multipleOf,omitempty"`
Enum []interface{} `json:"enum,omitempty"`
}
// Items a limited subset of JSON-Schema's items object.
// It is used by parameter definitions that are not located in "body".
//
@ -105,18 +134,18 @@ func (i *Items) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Deco
if err := i.Refable.Ref.fromMap(x.Extensions); err != nil {
return err
}
x.Extensions.sanitize()
if len(x.Extensions) == 0 {
x.Extensions = nil
}
i.CommonValidations = x.CommonValidations
i.SimpleSchema = x.SimpleSchema
i.VendorExtensible.Extensions = x.Extensions
i.Extensions = internal.SanitizeExtensions(x.Extensions)
return nil
}
// MarshalJSON converts this items object to JSON
func (i Items) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(i)
}
b1, err := json.Marshal(i.CommonValidations)
if err != nil {
return nil, err
@ -135,3 +164,17 @@ func (i Items) MarshalJSON() ([]byte, error) {
}
return swag.ConcatJSON(b4, b3, b1, b2), nil
}
func (i Items) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
CommonValidations commonValidationsOmitZero `json:",inline"`
SimpleSchema simpleSchemaOmitZero `json:",inline"`
Ref string `json:"$ref,omitempty"`
Extensions
}
x.CommonValidations = commonValidationsOmitZero(i.CommonValidations)
x.SimpleSchema = simpleSchemaOmitZero(i.SimpleSchema)
x.Ref = i.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(i.Extensions)
return opts.MarshalNext(enc, x)
}

View File

@ -42,6 +42,23 @@ type OperationProps struct {
Responses *Responses `json:"responses,omitempty"`
}
// Marshaling structure only, always edit along with corresponding
// struct (or compilation will fail).
type operationPropsOmitZero struct {
Description string `json:"description,omitempty"`
Consumes []string `json:"consumes,omitempty"`
Produces []string `json:"produces,omitempty"`
Schemes []string `json:"schemes,omitempty"`
Tags []string `json:"tags,omitempty"`
Summary string `json:"summary,omitempty"`
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitzero"`
ID string `json:"operationId,omitempty"`
Deprecated bool `json:"deprecated,omitempty,omitzero"`
Security []map[string][]string `json:"security,omitempty"`
Parameters []Parameter `json:"parameters,omitempty"`
Responses *Responses `json:"responses,omitzero"`
}
// MarshalJSON takes care of serializing operation properties to JSON
//
// We use a custom marhaller here to handle a special cases related to
@ -96,17 +113,16 @@ func (o *Operation) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
x.Extensions.sanitize()
if len(x.Extensions) == 0 {
x.Extensions = nil
}
o.VendorExtensible.Extensions = x.Extensions
o.Extensions = internal.SanitizeExtensions(x.Extensions)
o.OperationProps = OperationProps(x.OperationPropsNoMethods)
return nil
}
// MarshalJSON converts this items object to JSON
func (o Operation) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(o)
}
b1, err := json.Marshal(o.OperationProps)
if err != nil {
return nil, err
@ -118,3 +134,13 @@ func (o Operation) MarshalJSON() ([]byte, error) {
concated := swag.ConcatJSON(b1, b2)
return concated, nil
}
func (o Operation) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Extensions
OperationProps operationPropsOmitZero `json:",inline"`
}
x.Extensions = internal.SanitizeExtensions(o.Extensions)
x.OperationProps = operationPropsOmitZero(o.OperationProps)
return opts.MarshalNext(enc, x)
}

View File

@ -36,6 +36,17 @@ type ParamProps struct {
AllowEmptyValue bool `json:"allowEmptyValue,omitempty"`
}
// Marshaling structure only, always edit along with corresponding
// struct (or compilation will fail).
type paramPropsOmitZero struct {
Description string `json:"description,omitempty"`
Name string `json:"name,omitempty"`
In string `json:"in,omitempty"`
Required bool `json:"required,omitzero"`
Schema *Schema `json:"schema,omitzero"`
AllowEmptyValue bool `json:"allowEmptyValue,omitzero"`
}
// Parameter a unique parameter is defined by a combination of a [name](#parameterName) and [location](#parameterIn).
//
// There are five possible parameter types.
@ -109,19 +120,18 @@ func (p *Parameter) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.
if err := p.Refable.Ref.fromMap(x.Extensions); err != nil {
return err
}
x.Extensions.sanitize()
if len(x.Extensions) == 0 {
x.Extensions = nil
}
p.CommonValidations = x.CommonValidations
p.SimpleSchema = x.SimpleSchema
p.VendorExtensible.Extensions = x.Extensions
p.Extensions = internal.SanitizeExtensions(x.Extensions)
p.ParamProps = x.ParamProps
return nil
}
// MarshalJSON converts this items object to JSON
func (p Parameter) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(p)
}
b1, err := json.Marshal(p.CommonValidations)
if err != nil {
return nil, err
@ -144,3 +154,19 @@ func (p Parameter) MarshalJSON() ([]byte, error) {
}
return swag.ConcatJSON(b3, b1, b2, b4, b5), nil
}
func (p Parameter) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
CommonValidations commonValidationsOmitZero `json:",inline"`
SimpleSchema simpleSchemaOmitZero `json:",inline"`
ParamProps paramPropsOmitZero `json:",inline"`
Ref string `json:"$ref,omitempty"`
Extensions
}
x.CommonValidations = commonValidationsOmitZero(p.CommonValidations)
x.SimpleSchema = simpleSchemaOmitZero(p.SimpleSchema)
x.Extensions = internal.SanitizeExtensions(p.Extensions)
x.ParamProps = paramPropsOmitZero(p.ParamProps)
x.Ref = p.Refable.Ref.String()
return opts.MarshalNext(enc, x)
}

View File

@ -70,24 +70,20 @@ func (p *PathItem) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.D
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
p.Extensions = x.Extensions
p.PathItemProps = x.PathItemProps
if err := p.Refable.Ref.fromMap(p.Extensions); err != nil {
if err := p.Refable.Ref.fromMap(x.Extensions); err != nil {
return err
}
p.Extensions.sanitize()
if len(p.Extensions) == 0 {
p.Extensions = nil
}
p.Extensions = internal.SanitizeExtensions(x.Extensions)
p.PathItemProps = x.PathItemProps
return nil
}
// MarshalJSON converts this items object to JSON
func (p PathItem) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(p)
}
b3, err := json.Marshal(p.Refable)
if err != nil {
return nil, err
@ -103,3 +99,15 @@ func (p PathItem) MarshalJSON() ([]byte, error) {
concated := swag.ConcatJSON(b3, b4, b5)
return concated, nil
}
func (p PathItem) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Ref string `json:"$ref,omitempty"`
Extensions
PathItemProps
}
x.Ref = p.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(p.Extensions)
x.PathItemProps = p.PathItemProps
return opts.MarshalNext(enc, x)
}

View File

@ -92,7 +92,7 @@ func (p *Paths) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Deco
}
switch k := tok.String(); {
case isExtensionKey(k):
case internal.IsExtensionKey(k):
ext = nil
if err := opts.UnmarshalNext(dec, &ext); err != nil {
return err
@ -114,7 +114,9 @@ func (p *Paths) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Deco
p.Paths[k] = pi
default:
_, err := dec.ReadValue() // skip value
return err
if err != nil {
return err
}
}
}
default:
@ -124,6 +126,9 @@ func (p *Paths) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Deco
// MarshalJSON converts this items object to JSON
func (p Paths) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(p)
}
b1, err := json.Marshal(p.VendorExtensible)
if err != nil {
return nil, err
@ -142,3 +147,18 @@ func (p Paths) MarshalJSON() ([]byte, error) {
concated := swag.ConcatJSON(b1, b2)
return concated, nil
}
func (p Paths) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
m := make(map[string]any, len(p.Extensions)+len(p.Paths))
for k, v := range p.Extensions {
if internal.IsExtensionKey(k) {
m[k] = v
}
}
for k, v := range p.Paths {
if strings.HasPrefix(k, "/") {
m[k] = v
}
}
return opts.MarshalNext(enc, m)
}

View File

@ -21,6 +21,8 @@ import (
"path/filepath"
"github.com/go-openapi/jsonreference"
"k8s.io/kube-openapi/pkg/internal"
)
// Refable is a struct for things that accept a $ref property
@ -149,19 +151,5 @@ func (r *Ref) UnmarshalJSON(d []byte) error {
}
func (r *Ref) fromMap(v map[string]interface{}) error {
if v == nil {
return nil
}
if vv, ok := v["$ref"]; ok {
if str, ok := vv.(string); ok {
ref, err := jsonreference.New(str)
if err != nil {
return err
}
*r = Ref{Ref: ref}
}
}
return nil
return internal.JSONRefFromMap(&r.Ref, v)
}

View File

@ -30,6 +30,15 @@ type ResponseProps struct {
Examples map[string]interface{} `json:"examples,omitempty"`
}
// Marshaling structure only, always edit along with corresponding
// struct (or compilation will fail).
type responsePropsOmitZero struct {
Description string `json:"description,omitempty"`
Schema *Schema `json:"schema,omitzero"`
Headers map[string]Header `json:"headers,omitempty"`
Examples map[string]interface{} `json:"examples,omitempty"`
}
// Response describes a single response from an API Operation.
//
// For more information: http://goo.gl/8us55a#responseObject
@ -68,23 +77,20 @@ func (r *Response) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.D
return err
}
r.Extensions = x.Extensions
r.ResponseProps = x.ResponseProps
if err := r.Refable.Ref.fromMap(r.Extensions); err != nil {
if err := r.Refable.Ref.fromMap(x.Extensions); err != nil {
return err
}
r.Extensions.sanitize()
if len(r.Extensions) == 0 {
r.Extensions = nil
}
r.Extensions = internal.SanitizeExtensions(x.Extensions)
r.ResponseProps = x.ResponseProps
return nil
}
// MarshalJSON converts this items object to JSON
func (r Response) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(r)
}
b1, err := json.Marshal(r.ResponseProps)
if err != nil {
return nil, err
@ -100,6 +106,18 @@ func (r Response) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2, b3), nil
}
func (r Response) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Ref string `json:"$ref,omitempty"`
Extensions
ResponseProps responsePropsOmitZero `json:",inline"`
}
x.Ref = r.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(r.Extensions)
x.ResponseProps = responsePropsOmitZero(r.ResponseProps)
return opts.MarshalNext(enc, x)
}
// NewResponse creates a new response instance
func NewResponse() *Response {
return new(Response)

View File

@ -63,6 +63,9 @@ func (r *Responses) UnmarshalJSON(data []byte) error {
// MarshalJSON converts this items object to JSON
func (r Responses) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(r)
}
b1, err := json.Marshal(r.ResponsesProps)
if err != nil {
return nil, err
@ -75,6 +78,25 @@ func (r Responses) MarshalJSON() ([]byte, error) {
return concated, nil
}
func (r Responses) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
type ArbitraryKeys map[string]interface{}
var x struct {
ArbitraryKeys
Default *Response `json:"default,omitempty"`
}
x.ArbitraryKeys = make(map[string]any, len(r.Extensions)+len(r.StatusCodeResponses))
for k, v := range r.Extensions {
if internal.IsExtensionKey(k) {
x.ArbitraryKeys[k] = v
}
}
for k, v := range r.StatusCodeResponses {
x.ArbitraryKeys[strconv.Itoa(k)] = v
}
x.Default = r.Default
return opts.MarshalNext(enc, x)
}
// ResponsesProps describes all responses for an operation.
// It tells what is the default response and maps all responses with a
// HTTP status code.
@ -148,7 +170,7 @@ func (r *Responses) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.
return nil
}
switch k := tok.String(); {
case isExtensionKey(k):
case internal.IsExtensionKey(k):
ext = nil
if err := opts.UnmarshalNext(dec, &ext); err != nil {
return err

View File

@ -196,6 +196,46 @@ type SchemaProps struct {
Definitions Definitions `json:"definitions,omitempty"`
}
// Marshaling structure only, always edit along with corresponding
// struct (or compilation will fail).
type schemaPropsOmitZero struct {
ID string `json:"id,omitempty"`
Ref Ref `json:"-"`
Schema SchemaURL `json:"-"`
Description string `json:"description,omitempty"`
Type StringOrArray `json:"type,omitzero"`
Nullable bool `json:"nullable,omitzero"`
Format string `json:"format,omitempty"`
Title string `json:"title,omitempty"`
Default interface{} `json:"default,omitzero"`
Maximum *float64 `json:"maximum,omitempty"`
ExclusiveMaximum bool `json:"exclusiveMaximum,omitzero"`
Minimum *float64 `json:"minimum,omitempty"`
ExclusiveMinimum bool `json:"exclusiveMinimum,omitzero"`
MaxLength *int64 `json:"maxLength,omitempty"`
MinLength *int64 `json:"minLength,omitempty"`
Pattern string `json:"pattern,omitempty"`
MaxItems *int64 `json:"maxItems,omitempty"`
MinItems *int64 `json:"minItems,omitempty"`
UniqueItems bool `json:"uniqueItems,omitzero"`
MultipleOf *float64 `json:"multipleOf,omitempty"`
Enum []interface{} `json:"enum,omitempty"`
MaxProperties *int64 `json:"maxProperties,omitempty"`
MinProperties *int64 `json:"minProperties,omitempty"`
Required []string `json:"required,omitempty"`
Items *SchemaOrArray `json:"items,omitzero"`
AllOf []Schema `json:"allOf,omitempty"`
OneOf []Schema `json:"oneOf,omitempty"`
AnyOf []Schema `json:"anyOf,omitempty"`
Not *Schema `json:"not,omitzero"`
Properties map[string]Schema `json:"properties,omitempty"`
AdditionalProperties *SchemaOrBool `json:"additionalProperties,omitzero"`
PatternProperties map[string]Schema `json:"patternProperties,omitempty"`
Dependencies Dependencies `json:"dependencies,omitempty"`
AdditionalItems *SchemaOrBool `json:"additionalItems,omitzero"`
Definitions Definitions `json:"definitions,omitempty"`
}
// SwaggerSchemaProps are additional properties supported by swagger schemas, but not JSON-schema (draft 4)
type SwaggerSchemaProps struct {
Discriminator string `json:"discriminator,omitempty"`
@ -204,6 +244,15 @@ type SwaggerSchemaProps struct {
Example interface{} `json:"example,omitempty"`
}
// Marshaling structure only, always edit along with corresponding
// struct (or compilation will fail).
type swaggerSchemaPropsOmitZero struct {
Discriminator string `json:"discriminator,omitempty"`
ReadOnly bool `json:"readOnly,omitzero"`
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitzero"`
Example interface{} `json:"example,omitempty"`
}
// Schema the schema object allows the definition of input and output data types.
// These types can be objects, but also primitives and arrays.
// This object is based on the [JSON Schema Specification Draft 4](http://json-schema.org/)
@ -434,6 +483,9 @@ func (s *Schema) WithExternalDocs(description, url string) *Schema {
// MarshalJSON marshal this to JSON
func (s Schema) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(s)
}
b1, err := json.Marshal(s.SchemaProps)
if err != nil {
return nil, fmt.Errorf("schema props %v", err)
@ -465,6 +517,31 @@ func (s Schema) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2, b3, b4, b5, b6), nil
}
func (s Schema) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
type ArbitraryKeys map[string]interface{}
var x struct {
ArbitraryKeys
SchemaProps schemaPropsOmitZero `json:",inline"`
SwaggerSchemaProps swaggerSchemaPropsOmitZero `json:",inline"`
Schema string `json:"$schema,omitempty"`
Ref string `json:"$ref,omitempty"`
}
x.ArbitraryKeys = make(map[string]any, len(s.Extensions)+len(s.ExtraProps))
for k, v := range s.Extensions {
if internal.IsExtensionKey(k) {
x.ArbitraryKeys[k] = v
}
}
for k, v := range s.ExtraProps {
x.ArbitraryKeys[k] = v
}
x.SchemaProps = schemaPropsOmitZero(s.SchemaProps)
x.SwaggerSchemaProps = swaggerSchemaPropsOmitZero(s.SwaggerSchemaProps)
x.Ref = s.Ref.String()
x.Schema = string(s.Schema)
return opts.MarshalNext(enc, x)
}
// UnmarshalJSON marshal this from JSON
func (s *Schema) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshaling {
@ -547,7 +624,7 @@ func (s *Schema) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Dec
}
s.ExtraProps = x.Extensions.sanitizeWithExtra()
s.VendorExtensible.Extensions = x.Extensions
s.Extensions = internal.SanitizeExtensions(x.Extensions)
s.SchemaProps = x.SchemaProps
s.SwaggerSchemaProps = x.SwaggerSchemaProps
return nil

View File

@ -18,6 +18,7 @@ import (
"encoding/json"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/internal"
jsonv2 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
)
@ -45,6 +46,9 @@ type SecurityScheme struct {
// MarshalJSON marshal this to JSON
func (s SecurityScheme) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(s)
}
b1, err := json.Marshal(s.SecuritySchemeProps)
if err != nil {
return nil, err
@ -56,6 +60,16 @@ func (s SecurityScheme) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}
func (s SecurityScheme) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Extensions
SecuritySchemeProps
}
x.Extensions = internal.SanitizeExtensions(s.Extensions)
x.SecuritySchemeProps = s.SecuritySchemeProps
return opts.MarshalNext(enc, x)
}
// UnmarshalJSON marshal this from JSON
func (s *SecurityScheme) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &s.SecuritySchemeProps); err != nil {
@ -72,11 +86,7 @@ func (s *SecurityScheme) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *js
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
x.Extensions.sanitize()
if len(x.Extensions) == 0 {
x.Extensions = nil
}
s.VendorExtensible.Extensions = x.Extensions
s.Extensions = internal.SanitizeExtensions(x.Extensions)
s.SecuritySchemeProps = x.SecuritySchemeProps
return nil
}

View File

@ -35,6 +35,9 @@ type Swagger struct {
// MarshalJSON marshals this swagger structure to json
func (s Swagger) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(s)
}
b1, err := json.Marshal(s.SwaggerProps)
if err != nil {
return nil, err
@ -46,12 +49,22 @@ func (s Swagger) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}
// MarshalJSON marshals this swagger structure to json
func (s Swagger) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Extensions
SwaggerProps
}
x.Extensions = internal.SanitizeExtensions(s.Extensions)
x.SwaggerProps = s.SwaggerProps
return opts.MarshalNext(enc, x)
}
// UnmarshalJSON unmarshals a swagger spec from json
func (s *Swagger) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshaling {
return jsonv2.Unmarshal(data, s)
}
var sw Swagger
if err := json.Unmarshal(data, &sw.SwaggerProps); err != nil {
return err
@ -75,15 +88,8 @@ func (s *Swagger) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.De
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
s.Extensions = x.Extensions
s.Extensions = internal.SanitizeExtensions(x.Extensions)
s.SwaggerProps = x.SwaggerProps
s.Extensions.sanitize()
if len(s.Extensions) == 0 {
s.Extensions = nil
}
return nil
}
@ -126,6 +132,9 @@ var jsFalse = []byte("false")
// MarshalJSON convert this object to JSON
func (s SchemaOrBool) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(s)
}
if s.Schema != nil {
return json.Marshal(s.Schema)
}
@ -136,6 +145,18 @@ func (s SchemaOrBool) MarshalJSON() ([]byte, error) {
return jsTrue, nil
}
// MarshalJSON convert this object to JSON
func (s SchemaOrBool) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
if s.Schema != nil {
return opts.MarshalNext(enc, s.Schema)
}
if s.Schema == nil && !s.Allows {
return enc.WriteToken(jsonv2.False)
}
return enc.WriteToken(jsonv2.True)
}
// UnmarshalJSON converts this bool or schema object from a JSON structure
func (s *SchemaOrBool) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshaling {
@ -143,15 +164,15 @@ func (s *SchemaOrBool) UnmarshalJSON(data []byte) error {
}
var nw SchemaOrBool
if len(data) >= 4 {
if data[0] == '{' {
var sch Schema
if err := json.Unmarshal(data, &sch); err != nil {
return err
}
nw.Schema = &sch
if len(data) > 0 && data[0] == '{' {
var sch Schema
if err := json.Unmarshal(data, &sch); err != nil {
return err
}
nw.Allows = !(data[0] == 'f' && data[1] == 'a' && data[2] == 'l' && data[3] == 's' && data[4] == 'e')
nw.Schema = &sch
nw.Allows = true
} else {
json.Unmarshal(data, &nw.Allows)
}
*s = nw
return nil
@ -185,6 +206,9 @@ type SchemaOrStringArray struct {
// MarshalJSON converts this schema object or array into JSON structure
func (s SchemaOrStringArray) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(s)
}
if len(s.Property) > 0 {
return json.Marshal(s.Property)
}
@ -194,6 +218,17 @@ func (s SchemaOrStringArray) MarshalJSON() ([]byte, error) {
return []byte("null"), nil
}
// MarshalJSON converts this schema object or array into JSON structure
func (s SchemaOrStringArray) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
if len(s.Property) > 0 {
return opts.MarshalNext(enc, s.Property)
}
if s.Schema != nil {
return opts.MarshalNext(enc, s.Schema)
}
return enc.WriteToken(jsonv2.Null)
}
// UnmarshalJSON converts this schema object or array from a JSON structure
func (s *SchemaOrStringArray) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshaling {
@ -347,12 +382,23 @@ func (s *SchemaOrArray) ContainsType(name string) bool {
// MarshalJSON converts this schema object or array into JSON structure
func (s SchemaOrArray) MarshalJSON() ([]byte, error) {
if len(s.Schemas) > 0 {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(s)
}
if s.Schemas != nil {
return json.Marshal(s.Schemas)
}
return json.Marshal(s.Schema)
}
// MarshalJSON converts this schema object or array into JSON structure
func (s SchemaOrArray) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
if s.Schemas != nil {
return opts.MarshalNext(enc, s.Schemas)
}
return opts.MarshalNext(enc, s.Schema)
}
// UnmarshalJSON converts this schema object or array from a JSON structure
func (s *SchemaOrArray) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshaling {

View File

@ -41,6 +41,9 @@ type Tag struct {
// MarshalJSON marshal this to JSON
func (t Tag) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(t)
}
b1, err := json.Marshal(t.TagProps)
if err != nil {
return nil, err
@ -52,6 +55,16 @@ func (t Tag) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}
func (t Tag) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Extensions
TagProps
}
x.Extensions = internal.SanitizeExtensions(t.Extensions)
x.TagProps = t.TagProps
return opts.MarshalNext(enc, x)
}
// UnmarshalJSON marshal this from JSON
func (t *Tag) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshaling {
@ -72,11 +85,7 @@ func (t *Tag) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decode
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
x.Extensions.sanitize()
if len(x.Extensions) == 0 {
x.Extensions = nil
}
t.VendorExtensible.Extensions = x.Extensions
t.Extensions = internal.SanitizeExtensions(x.Extensions)
t.TagProps = x.TagProps
return nil
}