vendor: bump k8s dependencies to v0.29.2

Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax
2024-02-24 16:41:41 +01:00
parent ae0a5e495a
commit 303e509bbf
761 changed files with 66147 additions and 29461 deletions

View File

@ -1,51 +0,0 @@
/*
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 util
import (
"reflect"
"k8s.io/kube-openapi/pkg/schemamutation"
"k8s.io/kube-openapi/pkg/validation/spec"
)
// wrapRefs wraps OpenAPI V3 Schema refs that contain sibling elements.
// AllOf is used to wrap the Ref to prevent references from having sibling elements
// Please see https://github.com/kubernetes/kubernetes/issues/106387#issuecomment-967640388
func WrapRefs(schema *spec.Schema) *spec.Schema {
walker := schemamutation.Walker{
SchemaCallback: func(schema *spec.Schema) *spec.Schema {
orig := schema
clone := func() {
if orig == schema {
schema = new(spec.Schema)
*schema = *orig
}
}
if schema.Ref.String() != "" && !reflect.DeepEqual(*schema, spec.Schema{SchemaProps: spec.SchemaProps{Ref: schema.Ref}}) {
clone()
refSchema := new(spec.Schema)
refSchema.Ref = schema.Ref
schema.Ref = spec.Ref{}
schema.AllOf = []spec.Schema{*refSchema}
}
return schema
},
RefCallback: schemamutation.RefCallbackNoop,
}
return walker.WalkSchema(schema)
}

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

@ -0,0 +1,290 @@
/*
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 cached 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.
//
// All the operations in this module are thread-safe.
//
// # 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:
// - [Func]: A source cache that recomputes the content every time.
// - [Once]: A source cache that always produces the
// same content, it is only called once.
// - [Transform]: A cache that transforms data from one format to
// another. It's only refreshed when the source changes.
// - [Merge]: A cache that aggregates multiple caches in a map into one.
// It's only refreshed when the source changes.
// - [MergeList]: A cache that aggregates multiple caches in a list into one.
// It's only refreshed when the source changes.
// - [Atomic]: A cache adapter that atomically replaces the source with a new one.
// - [LastSuccess]: A cache adapter that caches the last successful and returns
// it if the next call fails. It extends [Atomic].
//
// # 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"
"sync/atomic"
)
// Value is wrapping a value behind a getter for lazy evaluation.
type Value[T any] interface {
Get() (value T, etag string, err error)
}
// Result is wrapping T and error into a struct for cases where a tuple is more
// convenient or necessary in Golang.
type Result[T any] struct {
Value T
Etag string
Err error
}
func (r Result[T]) Get() (T, string, error) {
return r.Value, r.Etag, r.Err
}
// Func wraps a (thread-safe) function as a Value[T].
func Func[T any](fn func() (T, string, error)) Value[T] {
return valueFunc[T](fn)
}
type valueFunc[T any] func() (T, string, error)
func (c valueFunc[T]) Get() (T, string, error) {
return c()
}
// Static returns constant values.
func Static[T any](value T, etag string) Value[T] {
return Result[T]{Value: value, Etag: etag}
}
// Merge merges a of cached values. The merge 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 run again.
//
// 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.
//
// Also note that Golang map iteration is not stable. If the mergeFn
// depends on the order iteration to be stable, it will need to
// implement its own sorting or iteration order.
func Merge[K comparable, T, V any](mergeFn func(results map[K]Result[T]) (V, string, error), caches map[K]Value[T]) Value[V] {
list := make([]Value[T], 0, len(caches))
// map from index to key
indexes := make(map[int]K, len(caches))
i := 0
for k := range caches {
list = append(list, caches[k])
indexes[i] = k
i++
}
return MergeList(func(results []Result[T]) (V, string, error) {
if len(results) != len(indexes) {
panic(fmt.Errorf("invalid result length %d, expected %d", len(results), len(indexes)))
}
m := make(map[K]Result[T], len(results))
for i := range results {
m[indexes[i]] = results[i]
}
return mergeFn(m)
}, list)
}
// MergeList merges a list of cached values. The function only gets called if
// any of the dependency has changed.
//
// The benefit of ListMerger over the basic Merger is that caches are
// stored in an ordered list so the order of the cache will be
// preserved in the order of the results passed to the mergeFn.
//
// 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.
//
// 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 MergeList[T, V any](mergeFn func(results []Result[T]) (V, string, error), delegates []Value[T]) Value[V] {
return &listMerger[T, V]{
mergeFn: mergeFn,
delegates: delegates,
}
}
type listMerger[T, V any] struct {
lock sync.Mutex
mergeFn func([]Result[T]) (V, string, error)
delegates []Value[T]
cache []Result[T]
result Result[V]
}
func (c *listMerger[T, V]) prepareResultsLocked() []Result[T] {
cacheResults := make([]Result[T], len(c.delegates))
ch := make(chan struct {
int
Result[T]
}, len(c.delegates))
for i := range c.delegates {
go func(index int) {
value, etag, err := c.delegates[index].Get()
ch <- struct {
int
Result[T]
}{index, Result[T]{Value: value, Etag: etag, Err: err}}
}(i)
}
for i := 0; i < len(c.delegates); i++ {
res := <-ch
cacheResults[res.int] = res.Result
}
return cacheResults
}
func (c *listMerger[T, V]) needsRunningLocked(results []Result[T]) bool {
if c.cache == nil {
return true
}
if c.result.Err != nil {
return true
}
if len(results) != len(c.cache) {
panic(fmt.Errorf("invalid number of results: %v (expected %v)", len(results), len(c.cache)))
}
for i, oldResult := range c.cache {
newResult := results[i]
if newResult.Etag != oldResult.Etag || newResult.Err != nil || oldResult.Err != nil {
return true
}
}
return false
}
func (c *listMerger[T, V]) Get() (V, string, error) {
c.lock.Lock()
defer c.lock.Unlock()
cacheResults := c.prepareResultsLocked()
if c.needsRunningLocked(cacheResults) {
c.cache = cacheResults
c.result.Value, c.result.Etag, c.result.Err = c.mergeFn(c.cache)
}
return c.result.Value, c.result.Etag, c.result.Err
}
// Transform the result of another cached value. The transformFn will only be called
// if the source has updated, otherwise, the 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 Transform[T, V any](transformerFn func(T, string, error) (V, string, error), source Value[T]) Value[V] {
return MergeList(func(delegates []Result[T]) (V, string, error) {
if len(delegates) != 1 {
panic(fmt.Errorf("invalid cache for transformer cache: %v", delegates))
}
return transformerFn(delegates[0].Value, delegates[0].Etag, delegates[0].Err)
}, []Value[T]{source})
}
// Once calls Value[T].Get() lazily and only once, even in case of an error result.
func Once[T any](d Value[T]) Value[T] {
return &once[T]{
data: d,
}
}
type once[T any] struct {
once sync.Once
data Value[T]
result Result[T]
}
func (c *once[T]) Get() (T, string, error) {
c.once.Do(func() {
c.result.Value, c.result.Etag, c.result.Err = c.data.Get()
})
return c.result.Value, c.result.Etag, c.result.Err
}
// Replaceable extends the Value[T] interface with the ability to change the
// underlying Value[T] after construction.
type Replaceable[T any] interface {
Value[T]
Store(Value[T])
}
// Atomic wraps a Value[T] as an atomic value that can be replaced. It implements
// Replaceable[T].
type Atomic[T any] struct {
value atomic.Pointer[Value[T]]
}
var _ Replaceable[[]byte] = &Atomic[[]byte]{}
func (x *Atomic[T]) Store(val Value[T]) { x.value.Store(&val) }
func (x *Atomic[T]) Get() (T, string, error) { return (*x.value.Load()).Get() }
// LastSuccess calls Value[T].Get(), but hides errors by returning the last
// success if there has been any.
type LastSuccess[T any] struct {
Atomic[T]
success atomic.Pointer[Result[T]]
}
var _ Replaceable[[]byte] = &LastSuccess[[]byte]{}
func (c *LastSuccess[T]) Get() (T, string, error) {
success := c.success.Load()
value, etag, err := c.Atomic.Get()
if err == nil {
if success == nil {
c.success.CompareAndSwap(nil, &Result[T]{Value: value, Etag: etag, Err: err})
}
return value, etag, err
}
if success != nil {
return success.Value, success.Etag, success.Err
}
return value, etag, err
}

View File

@ -22,7 +22,6 @@ import (
"github.com/emicklei/go-restful/v3"
"k8s.io/kube-openapi/pkg/openapiconv"
"k8s.io/kube-openapi/pkg/spec3"
"k8s.io/kube-openapi/pkg/validation/spec"
)
@ -172,43 +171,6 @@ type OpenAPIV3Config struct {
DefaultSecurity []map[string][]string
}
// ConvertConfigToV3 converts a Config object to an OpenAPIV3Config object
func ConvertConfigToV3(config *Config) *OpenAPIV3Config {
if config == nil {
return nil
}
v3Config := &OpenAPIV3Config{
Info: config.Info,
IgnorePrefixes: config.IgnorePrefixes,
GetDefinitions: config.GetDefinitions,
GetOperationIDAndTags: config.GetOperationIDAndTags,
GetOperationIDAndTagsFromRoute: config.GetOperationIDAndTagsFromRoute,
GetDefinitionName: config.GetDefinitionName,
Definitions: config.Definitions,
SecuritySchemes: make(spec3.SecuritySchemes),
DefaultSecurity: config.DefaultSecurity,
DefaultResponse: openapiconv.ConvertResponse(config.DefaultResponse, []string{"application/json"}),
CommonResponses: make(map[int]*spec3.Response),
ResponseDefinitions: make(map[string]*spec3.Response),
}
if config.SecurityDefinitions != nil {
for s, securityScheme := range *config.SecurityDefinitions {
v3Config.SecuritySchemes[s] = openapiconv.ConvertSecurityScheme(securityScheme)
}
}
for k, commonResponse := range config.CommonResponses {
v3Config.CommonResponses[k] = openapiconv.ConvertResponse(&commonResponse, []string{"application/json"})
}
for k, responseDefinition := range config.ResponseDefinitions {
v3Config.ResponseDefinitions[k] = openapiconv.ConvertResponse(&responseDefinition, []string{"application/json"})
}
return v3Config
}
type typeInfo struct {
name string
format string
@ -246,38 +208,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

@ -21,35 +21,29 @@ import (
"crypto/sha512"
"encoding/json"
"fmt"
"mime"
"net/http"
"net/url"
"path"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/golang/protobuf/proto"
openapi_v3 "github.com/google/gnostic/openapiv3"
openapi_v3 "github.com/google/gnostic-models/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 +59,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.LastSuccess[*spec3.OpenAPI]
pbCache cached.Value[timedSpec]
jsonCache cached.Value[timedSpec]
}
func newOpenAPIV3Group() *openAPIV3Group {
o := &openAPIV3Group{}
o.jsonCache = cached.Transform[*spec3.OpenAPI](func(spec *spec3.OpenAPI, etag string, err error) (timedSpec, string, error) {
if err != nil {
return timedSpec{}, "", err
}
json, err := json.Marshal(spec)
if err != nil {
return timedSpec{}, "", err
}
return timedSpec{spec: json, lastModified: time.Now()}, computeETag(json), nil
}, &o.specCache)
o.pbCache = cached.Transform(func(ts timedSpec, etag string, err error) (timedSpec, string, error) {
if err != nil {
return timedSpec{}, "", err
}
proto, err := ToV3ProtoBinary(ts.spec)
if err != nil {
return timedSpec{}, "", err
}
return timedSpec{spec: proto, lastModified: ts.lastModified}, etag, nil
}, o.jsonCache)
return o
}
func (o *openAPIV3Group) UpdateSpec(openapi cached.Value[*spec3.OpenAPI]) {
o.specCache.Store(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.LastSuccess[timedSpec]
}
func computeETag(data []byte) string {
@ -106,92 +134,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.Store(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.Value[timedSpec] {
caches := make(map[string]cached.Value[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.Merge(func(results map[string]cached.Result[timedSpec]) (timedSpec, string, error) {
discovery := &OpenAPIV3Discovery{Paths: make(map[string]OpenAPIV3DiscoveryGroupVersion)}
for gvName, result := range results {
if result.Err != nil {
return timedSpec{}, "", result.Err
}
discovery.Paths[gvName] = OpenAPIV3DiscoveryGroupVersion{
ServerRelativeURL: constructServerRelativeURL(gvName, result.Etag),
}
}
j, err := json.Marshal(discovery)
if err != nil {
return nil, err
return 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 timedSpec{spec: j, lastModified: time.Now()}, computeETag(j), nil
}, 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
switch getType {
case subTypeJSON:
ts, etag, err := v.jsonCache.Get()
return ts.spec, etag, ts.lastModified, err
case subTypeProtobuf, subTypeProtobufDeprecated:
ts, etag, err := v.pbCache.Get()
return ts.spec, etag, ts.lastModified, err
default:
return nil, "", time.Now(), fmt.Errorf("Invalid accept clause %s", getType)
}
return nil, "", time.Now(), fmt.Errorf("Invalid accept clause %s", getType)
}
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.Value[*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.Store(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.Static(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.Store(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))
ts, etag, err := o.discoveryCache.Get()
if err != nil {
klog.Errorf("Error serving discovery: %s", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Etag", strconv.Quote(etag))
w.Header().Set("Content-Type", "application/json")
http.ServeContent(w, r, "/openapi/v3", ts.lastModified, bytes.NewReader(ts.spec))
}
func (o *OpenAPIService) HandleGroupVersion(w http.ResponseWriter, r *http.Request) {
@ -210,11 +236,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 +257,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 +293,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,8 @@ 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
var UseOptimizedJSONMarshalingV3 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:]
}
}

View File

@ -1,322 +0,0 @@
/*
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 openapiconv
import (
"strings"
klog "k8s.io/klog/v2"
builderutil "k8s.io/kube-openapi/pkg/builder3/util"
"k8s.io/kube-openapi/pkg/spec3"
"k8s.io/kube-openapi/pkg/validation/spec"
)
var OpenAPIV2DefPrefix = "#/definitions/"
var OpenAPIV3DefPrefix = "#/components/schemas/"
// ConvertV2ToV3 converts an OpenAPI V2 object into V3.
// Certain references may be shared between the V2 and V3 objects in the conversion.
func ConvertV2ToV3(v2Spec *spec.Swagger) *spec3.OpenAPI {
v3Spec := &spec3.OpenAPI{
Version: "3.0.0",
Info: v2Spec.Info,
ExternalDocs: ConvertExternalDocumentation(v2Spec.ExternalDocs),
Paths: ConvertPaths(v2Spec.Paths),
Components: ConvertComponents(v2Spec.SecurityDefinitions, v2Spec.Definitions, v2Spec.Responses, v2Spec.Produces),
}
return v3Spec
}
func ConvertExternalDocumentation(v2ED *spec.ExternalDocumentation) *spec3.ExternalDocumentation {
if v2ED == nil {
return nil
}
return &spec3.ExternalDocumentation{
ExternalDocumentationProps: spec3.ExternalDocumentationProps{
Description: v2ED.Description,
URL: v2ED.URL,
},
}
}
func ConvertComponents(v2SecurityDefinitions spec.SecurityDefinitions, v2Definitions spec.Definitions, v2Responses map[string]spec.Response, produces []string) *spec3.Components {
components := &spec3.Components{}
if v2Definitions != nil {
components.Schemas = make(map[string]*spec.Schema)
}
for s, schema := range v2Definitions {
components.Schemas[s] = ConvertSchema(&schema)
}
if v2SecurityDefinitions != nil {
components.SecuritySchemes = make(spec3.SecuritySchemes)
}
for s, securityScheme := range v2SecurityDefinitions {
components.SecuritySchemes[s] = ConvertSecurityScheme(securityScheme)
}
if v2Responses != nil {
components.Responses = make(map[string]*spec3.Response)
}
for r, response := range v2Responses {
components.Responses[r] = ConvertResponse(&response, produces)
}
return components
}
func ConvertSchema(v2Schema *spec.Schema) *spec.Schema {
if v2Schema == nil {
return nil
}
v3Schema := spec.Schema{
VendorExtensible: v2Schema.VendorExtensible,
SchemaProps: v2Schema.SchemaProps,
SwaggerSchemaProps: v2Schema.SwaggerSchemaProps,
ExtraProps: v2Schema.ExtraProps,
}
if refString := v2Schema.Ref.String(); refString != "" {
if idx := strings.Index(refString, OpenAPIV2DefPrefix); idx != -1 {
v3Schema.Ref = spec.MustCreateRef(OpenAPIV3DefPrefix + refString[idx+len(OpenAPIV2DefPrefix):])
} else {
klog.Errorf("Error: Swagger V2 Ref %s does not contain #/definitions\n", refString)
}
}
if v2Schema.Properties != nil {
v3Schema.Properties = make(map[string]spec.Schema)
for key, property := range v2Schema.Properties {
v3Schema.Properties[key] = *ConvertSchema(&property)
}
}
if v2Schema.Items != nil {
v3Schema.Items = &spec.SchemaOrArray{
Schema: ConvertSchema(v2Schema.Items.Schema),
Schemas: ConvertSchemaList(v2Schema.Items.Schemas),
}
}
if v2Schema.AdditionalProperties != nil {
v3Schema.AdditionalProperties = &spec.SchemaOrBool{
Schema: ConvertSchema(v2Schema.AdditionalProperties.Schema),
Allows: v2Schema.AdditionalProperties.Allows,
}
}
if v2Schema.AdditionalItems != nil {
v3Schema.AdditionalItems = &spec.SchemaOrBool{
Schema: ConvertSchema(v2Schema.AdditionalItems.Schema),
Allows: v2Schema.AdditionalItems.Allows,
}
}
return builderutil.WrapRefs(&v3Schema)
}
func ConvertSchemaList(v2SchemaList []spec.Schema) []spec.Schema {
if v2SchemaList == nil {
return nil
}
v3SchemaList := []spec.Schema{}
for _, s := range v2SchemaList {
v3SchemaList = append(v3SchemaList, *ConvertSchema(&s))
}
return v3SchemaList
}
func ConvertSecurityScheme(v2securityScheme *spec.SecurityScheme) *spec3.SecurityScheme {
if v2securityScheme == nil {
return nil
}
securityScheme := &spec3.SecurityScheme{
VendorExtensible: v2securityScheme.VendorExtensible,
SecuritySchemeProps: spec3.SecuritySchemeProps{
Description: v2securityScheme.Description,
Type: v2securityScheme.Type,
Name: v2securityScheme.Name,
In: v2securityScheme.In,
},
}
if v2securityScheme.Flow != "" {
securityScheme.Flows = make(map[string]*spec3.OAuthFlow)
securityScheme.Flows[v2securityScheme.Flow] = &spec3.OAuthFlow{
OAuthFlowProps: spec3.OAuthFlowProps{
AuthorizationUrl: v2securityScheme.AuthorizationURL,
TokenUrl: v2securityScheme.TokenURL,
Scopes: v2securityScheme.Scopes,
},
}
}
return securityScheme
}
func ConvertPaths(v2Paths *spec.Paths) *spec3.Paths {
if v2Paths == nil {
return nil
}
paths := &spec3.Paths{
VendorExtensible: v2Paths.VendorExtensible,
}
if v2Paths.Paths != nil {
paths.Paths = make(map[string]*spec3.Path)
}
for k, v := range v2Paths.Paths {
paths.Paths[k] = ConvertPathItem(v)
}
return paths
}
func ConvertPathItem(v2pathItem spec.PathItem) *spec3.Path {
path := &spec3.Path{
Refable: v2pathItem.Refable,
PathProps: spec3.PathProps{
Get: ConvertOperation(v2pathItem.Get),
Put: ConvertOperation(v2pathItem.Put),
Post: ConvertOperation(v2pathItem.Post),
Delete: ConvertOperation(v2pathItem.Delete),
Options: ConvertOperation(v2pathItem.Options),
Head: ConvertOperation(v2pathItem.Head),
Patch: ConvertOperation(v2pathItem.Patch),
},
VendorExtensible: v2pathItem.VendorExtensible,
}
for _, param := range v2pathItem.Parameters {
path.Parameters = append(path.Parameters, ConvertParameter(param))
}
return path
}
func ConvertOperation(v2Operation *spec.Operation) *spec3.Operation {
if v2Operation == nil {
return nil
}
operation := &spec3.Operation{
VendorExtensible: v2Operation.VendorExtensible,
OperationProps: spec3.OperationProps{
Description: v2Operation.Description,
ExternalDocs: ConvertExternalDocumentation(v2Operation.OperationProps.ExternalDocs),
Tags: v2Operation.Tags,
Summary: v2Operation.Summary,
Deprecated: v2Operation.Deprecated,
OperationId: v2Operation.ID,
},
}
for _, param := range v2Operation.Parameters {
if param.ParamProps.Name == "body" && param.ParamProps.Schema != nil {
operation.OperationProps.RequestBody = &spec3.RequestBody{
RequestBodyProps: spec3.RequestBodyProps{},
}
if v2Operation.Consumes != nil {
operation.RequestBody.Content = make(map[string]*spec3.MediaType)
}
for _, consumer := range v2Operation.Consumes {
operation.RequestBody.Content[consumer] = &spec3.MediaType{
MediaTypeProps: spec3.MediaTypeProps{
Schema: ConvertSchema(param.ParamProps.Schema),
},
}
}
} else {
operation.Parameters = append(operation.Parameters, ConvertParameter(param))
}
}
operation.Responses = &spec3.Responses{ResponsesProps: spec3.ResponsesProps{
Default: ConvertResponse(v2Operation.Responses.Default, v2Operation.Produces),
},
VendorExtensible: v2Operation.Responses.VendorExtensible,
}
if v2Operation.Responses.StatusCodeResponses != nil {
operation.Responses.StatusCodeResponses = make(map[int]*spec3.Response)
}
for k, v := range v2Operation.Responses.StatusCodeResponses {
operation.Responses.StatusCodeResponses[k] = ConvertResponse(&v, v2Operation.Produces)
}
return operation
}
func ConvertResponse(v2Response *spec.Response, produces []string) *spec3.Response {
if v2Response == nil {
return nil
}
response := &spec3.Response{
Refable: ConvertRefableResponse(v2Response.Refable),
VendorExtensible: v2Response.VendorExtensible,
ResponseProps: spec3.ResponseProps{
Description: v2Response.Description,
},
}
if v2Response.Schema != nil {
if produces != nil {
response.Content = make(map[string]*spec3.MediaType)
}
for _, producer := range produces {
response.ResponseProps.Content[producer] = &spec3.MediaType{
MediaTypeProps: spec3.MediaTypeProps{
Schema: ConvertSchema(v2Response.Schema),
},
}
}
}
return response
}
func ConvertParameter(v2Param spec.Parameter) *spec3.Parameter {
param := &spec3.Parameter{
Refable: ConvertRefableParameter(v2Param.Refable),
VendorExtensible: v2Param.VendorExtensible,
ParameterProps: spec3.ParameterProps{
Name: v2Param.Name,
Description: v2Param.Description,
In: v2Param.In,
Required: v2Param.Required,
Schema: ConvertSchema(v2Param.Schema),
AllowEmptyValue: v2Param.AllowEmptyValue,
},
}
// Convert SimpleSchema into Schema
if param.Schema == nil {
param.Schema = &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{v2Param.Type},
Format: v2Param.Format,
UniqueItems: v2Param.UniqueItems,
},
}
}
return param
}
func ConvertRefableParameter(refable spec.Refable) spec.Refable {
if refable.Ref.String() != "" {
return spec.Refable{Ref: spec.MustCreateRef(strings.Replace(refable.Ref.String(), "#/parameters/", "#/components/parameters/", 1))}
}
return refable
}
func ConvertRefableResponse(refable spec.Refable) spec.Refable {
if refable.Ref.String() != "" {
return spec.Refable{Ref: spec.MustCreateRef(strings.Replace(refable.Ref.String(), "#/responses/", "#/components/responses/", 1))}
}
return refable
}

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

@ -1,519 +0,0 @@
/*
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 schemamutation
import (
"k8s.io/kube-openapi/pkg/validation/spec"
)
// Walker runs callback functions on all references of an OpenAPI spec,
// replacing the values when visiting corresponding types.
type Walker struct {
// SchemaCallback will be called on each schema, taking the original schema,
// and before any other callbacks of the Walker.
// If the schema needs to be mutated, DO NOT mutate it in-place,
// always create a copy, mutate, and return it.
SchemaCallback func(schema *spec.Schema) *spec.Schema
// RefCallback will be called on each ref.
// If the ref needs to be mutated, DO NOT mutate it in-place,
// always create a copy, mutate, and return it.
RefCallback func(ref *spec.Ref) *spec.Ref
}
type SchemaCallbackFunc func(schema *spec.Schema) *spec.Schema
type RefCallbackFunc func(ref *spec.Ref) *spec.Ref
var SchemaCallBackNoop SchemaCallbackFunc = func(schema *spec.Schema) *spec.Schema {
return schema
}
var RefCallbackNoop RefCallbackFunc = func(ref *spec.Ref) *spec.Ref {
return ref
}
// ReplaceReferences rewrites the references without mutating the input.
// The output might share data with the input.
func ReplaceReferences(walkRef func(ref *spec.Ref) *spec.Ref, sp *spec.Swagger) *spec.Swagger {
walker := &Walker{RefCallback: walkRef, SchemaCallback: SchemaCallBackNoop}
return walker.WalkRoot(sp)
}
func (w *Walker) WalkSchema(schema *spec.Schema) *spec.Schema {
if schema == nil {
return nil
}
orig := schema
clone := func() {
if orig == schema {
schema = &spec.Schema{}
*schema = *orig
}
}
// Always run callback on the whole schema first
// so that SchemaCallback can take the original schema as input.
schema = w.SchemaCallback(schema)
if r := w.RefCallback(&schema.Ref); r != &schema.Ref {
clone()
schema.Ref = *r
}
definitionsCloned := false
for k, v := range schema.Definitions {
if s := w.WalkSchema(&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 := w.WalkSchema(&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 := w.WalkSchema(&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
}
}
allOfCloned := false
for i := range schema.AllOf {
if s := w.WalkSchema(&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 := w.WalkSchema(&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 := w.WalkSchema(&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 := w.WalkSchema(schema.Not); s != schema.Not {
clone()
schema.Not = s
}
}
if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
if s := w.WalkSchema(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 := w.WalkSchema(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 := w.WalkSchema(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 := w.WalkSchema(&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
}
func (w *Walker) walkParameter(param *spec.Parameter) *spec.Parameter {
if param == nil {
return nil
}
orig := param
cloned := false
clone := func() {
if !cloned {
cloned = true
param = &spec.Parameter{}
*param = *orig
}
}
if r := w.RefCallback(&param.Ref); r != &param.Ref {
clone()
param.Ref = *r
}
if s := w.WalkSchema(param.Schema); s != param.Schema {
clone()
param.Schema = s
}
if param.Items != nil {
if r := w.RefCallback(&param.Items.Ref); r != &param.Items.Ref {
param.Items.Ref = *r
}
}
return param
}
func (w *Walker) walkParameters(params []spec.Parameter) ([]spec.Parameter, bool) {
if params == nil {
return nil, false
}
orig := params
cloned := false
clone := func() {
if !cloned {
cloned = true
params = make([]spec.Parameter, len(params))
copy(params, orig)
}
}
for i := range params {
if s := w.walkParameter(&params[i]); s != &params[i] {
clone()
params[i] = *s
}
}
return params, cloned
}
func (w *Walker) walkResponse(resp *spec.Response) *spec.Response {
if resp == nil {
return nil
}
orig := resp
cloned := false
clone := func() {
if !cloned {
cloned = true
resp = &spec.Response{}
*resp = *orig
}
}
if r := w.RefCallback(&resp.Ref); r != &resp.Ref {
clone()
resp.Ref = *r
}
if s := w.WalkSchema(resp.Schema); s != resp.Schema {
clone()
resp.Schema = s
}
return resp
}
func (w *Walker) walkResponses(resps *spec.Responses) *spec.Responses {
if resps == nil {
return nil
}
orig := resps
cloned := false
clone := func() {
if !cloned {
cloned = true
resps = &spec.Responses{}
*resps = *orig
}
}
if r := w.walkResponse(resps.ResponsesProps.Default); r != resps.ResponsesProps.Default {
clone()
resps.Default = r
}
responsesCloned := false
for k, v := range resps.ResponsesProps.StatusCodeResponses {
if r := w.walkResponse(&v); r != &v {
if !responsesCloned {
responsesCloned = true
clone()
resps.ResponsesProps.StatusCodeResponses = make(map[int]spec.Response, len(orig.StatusCodeResponses))
for k2, v2 := range orig.StatusCodeResponses {
resps.ResponsesProps.StatusCodeResponses[k2] = v2
}
}
resps.ResponsesProps.StatusCodeResponses[k] = *r
}
}
return resps
}
func (w *Walker) walkOperation(op *spec.Operation) *spec.Operation {
if op == nil {
return nil
}
orig := op
cloned := false
clone := func() {
if !cloned {
cloned = true
op = &spec.Operation{}
*op = *orig
}
}
parametersCloned := false
for i := range op.Parameters {
if s := w.walkParameter(&op.Parameters[i]); s != &op.Parameters[i] {
if !parametersCloned {
parametersCloned = true
clone()
op.Parameters = make([]spec.Parameter, len(orig.Parameters))
copy(op.Parameters, orig.Parameters)
}
op.Parameters[i] = *s
}
}
if r := w.walkResponses(op.Responses); r != op.Responses {
clone()
op.Responses = r
}
return op
}
func (w *Walker) walkPathItem(pathItem *spec.PathItem) *spec.PathItem {
if pathItem == nil {
return nil
}
orig := pathItem
cloned := false
clone := func() {
if !cloned {
cloned = true
pathItem = &spec.PathItem{}
*pathItem = *orig
}
}
if p, changed := w.walkParameters(pathItem.Parameters); changed {
clone()
pathItem.Parameters = p
}
if op := w.walkOperation(pathItem.Get); op != pathItem.Get {
clone()
pathItem.Get = op
}
if op := w.walkOperation(pathItem.Head); op != pathItem.Head {
clone()
pathItem.Head = op
}
if op := w.walkOperation(pathItem.Delete); op != pathItem.Delete {
clone()
pathItem.Delete = op
}
if op := w.walkOperation(pathItem.Options); op != pathItem.Options {
clone()
pathItem.Options = op
}
if op := w.walkOperation(pathItem.Patch); op != pathItem.Patch {
clone()
pathItem.Patch = op
}
if op := w.walkOperation(pathItem.Post); op != pathItem.Post {
clone()
pathItem.Post = op
}
if op := w.walkOperation(pathItem.Put); op != pathItem.Put {
clone()
pathItem.Put = op
}
return pathItem
}
func (w *Walker) walkPaths(paths *spec.Paths) *spec.Paths {
if paths == nil {
return nil
}
orig := paths
cloned := false
clone := func() {
if !cloned {
cloned = true
paths = &spec.Paths{}
*paths = *orig
}
}
pathsCloned := false
for k, v := range paths.Paths {
if p := w.walkPathItem(&v); p != &v {
if !pathsCloned {
pathsCloned = true
clone()
paths.Paths = make(map[string]spec.PathItem, len(orig.Paths))
for k2, v2 := range orig.Paths {
paths.Paths[k2] = v2
}
}
paths.Paths[k] = *p
}
}
return paths
}
func (w *Walker) WalkRoot(swagger *spec.Swagger) *spec.Swagger {
if swagger == nil {
return nil
}
orig := swagger
cloned := false
clone := func() {
if !cloned {
cloned = true
swagger = &spec.Swagger{}
*swagger = *orig
}
}
parametersCloned := false
for k, v := range swagger.Parameters {
if p := w.walkParameter(&v); p != &v {
if !parametersCloned {
parametersCloned = true
clone()
swagger.Parameters = make(map[string]spec.Parameter, len(orig.Parameters))
for k2, v2 := range orig.Parameters {
swagger.Parameters[k2] = v2
}
}
swagger.Parameters[k] = *p
}
}
responsesCloned := false
for k, v := range swagger.Responses {
if r := w.walkResponse(&v); r != &v {
if !responsesCloned {
responsesCloned = true
clone()
swagger.Responses = make(map[string]spec.Response, len(orig.Responses))
for k2, v2 := range orig.Responses {
swagger.Responses[k2] = v2
}
}
swagger.Responses[k] = *r
}
}
definitionsCloned := false
for k, v := range swagger.Definitions {
if s := w.WalkSchema(&v); s != &v {
if !definitionsCloned {
definitionsCloned = true
clone()
swagger.Definitions = make(spec.Definitions, len(orig.Definitions))
for k2, v2 := range orig.Definitions {
swagger.Definitions[k2] = v2
}
}
swagger.Definitions[k] = *s
}
}
if swagger.Paths != nil {
if p := w.walkPaths(swagger.Paths); p != swagger.Paths {
clone()
swagger.Paths = p
}
}
return swagger
}

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"
)
@ -29,6 +32,9 @@ type Encoding struct {
// MarshalJSON is a custom marshal function that knows how to encode Encoding as JSON
func (e *Encoding) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(e)
}
b1, err := json.Marshal(e.EncodingProps)
if err != nil {
return nil, err
@ -40,7 +46,20 @@ func (e *Encoding) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}
func (e *Encoding) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
EncodingProps encodingPropsOmitZero `json:",inline"`
spec.Extensions
}
x.Extensions = internal.SanitizeExtensions(e.Extensions)
x.EncodingProps = encodingPropsOmitZero(e.EncodingProps)
return opts.MarshalNext(enc, x)
}
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 +69,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 +91,15 @@ 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"`
}
type encodingPropsOmitZero struct {
ContentType string `json:"contentType,omitempty"`
Headers map[string]*Header `json:"headers,omitempty"`
Style string `json:"style,omitempty"`
Explode bool `json:"explode,omitzero"`
AllowReserved bool `json:"allowReserved,omitzero"`
}

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
@ -33,6 +36,9 @@ type Example struct {
// MarshalJSON is a custom marshal function that knows how to encode RequestBody as JSON
func (e *Example) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(e)
}
b1, err := json.Marshal(e.Refable)
if err != nil {
return nil, err
@ -47,8 +53,22 @@ func (e *Example) MarshalJSON() ([]byte, error) {
}
return swag.ConcatJSON(b1, b2, b3), nil
}
func (e *Example) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Ref string `json:"$ref,omitempty"`
ExampleProps `json:",inline"`
spec.Extensions
}
x.Ref = e.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(e.Extensions)
x.ExampleProps = e.ExampleProps
return opts.MarshalNext(enc, x)
}
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 +81,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 {
@ -36,6 +39,9 @@ type ExternalDocumentationProps struct {
// MarshalJSON is a custom marshal function that knows how to encode Responses as JSON
func (e *ExternalDocumentation) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(e)
}
b1, err := json.Marshal(e.ExternalDocumentationProps)
if err != nil {
return nil, err
@ -47,7 +53,20 @@ func (e *ExternalDocumentation) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}
func (e *ExternalDocumentation) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
ExternalDocumentationProps `json:",inline"`
spec.Extensions
}
x.Extensions = internal.SanitizeExtensions(e.Extensions)
x.ExternalDocumentationProps = e.ExternalDocumentationProps
return opts.MarshalNext(enc, x)
}
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 +75,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
}

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

@ -0,0 +1,281 @@
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"
for i, val := range o.SecurityRequirement {
if val == nil {
o.SecurityRequirement[i] = make(map[string][]string)
}
for k, v := range val {
if v == nil {
val[k] = make([]string, 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 *Operation, c fuzz.Continue) {
c.FuzzNoCustom(v)
// Do not fuzz null values into the array.
for i, val := range v.SecurityRequirement {
if val == nil {
v.SecurityRequirement[i] = make(map[string][]string)
}
for k, v := range val {
if v == nil {
val[k] = make([]string, 0)
}
}
}
},
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"
)
@ -34,6 +36,9 @@ type Header struct {
// MarshalJSON is a custom marshal function that knows how to encode Header as JSON
func (h *Header) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(h)
}
b1, err := json.Marshal(h.Refable)
if err != nil {
return nil, err
@ -49,7 +54,22 @@ func (h *Header) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2, b3), nil
}
func (h *Header) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Ref string `json:"$ref,omitempty"`
HeaderProps headerPropsOmitZero `json:",inline"`
spec.Extensions
}
x.Ref = h.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(h.Extensions)
x.HeaderProps = headerPropsOmitZero(h.HeaderProps)
return opts.MarshalNext(enc, x)
}
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 +83,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
@ -88,3 +124,19 @@ type HeaderProps struct {
// Examples of the header
Examples map[string]*Example `json:"examples,omitempty"`
}
// Marshaling structure only, always edit along with corresponding
// struct (or compilation will fail).
type headerPropsOmitZero struct {
Description string `json:"description,omitempty"`
Required bool `json:"required,omitzero"`
Deprecated bool `json:"deprecated,omitzero"`
AllowEmptyValue bool `json:"allowEmptyValue,omitzero"`
Style string `json:"style,omitempty"`
Explode bool `json:"explode,omitzero"`
AllowReserved bool `json:"allowReserved,omitzero"`
Schema *spec.Schema `json:"schema,omitzero"`
Content map[string]*MediaType `json:"content,omitempty"`
Example interface{} `json:"example,omitempty"`
Examples map[string]*Example `json:"examples,omitempty"`
}

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"
)
@ -32,6 +35,9 @@ type MediaType struct {
// MarshalJSON is a custom marshal function that knows how to encode MediaType as JSON
func (m *MediaType) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(m)
}
b1, err := json.Marshal(m.MediaTypeProps)
if err != nil {
return nil, err
@ -43,7 +49,20 @@ func (m *MediaType) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}
func (e *MediaType) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
MediaTypeProps mediaTypePropsOmitZero `json:",inline"`
spec.Extensions
}
x.Extensions = internal.SanitizeExtensions(e.Extensions)
x.MediaTypeProps = mediaTypePropsOmitZero(e.MediaTypeProps)
return opts.MarshalNext(enc, x)
}
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 +72,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
@ -64,3 +97,10 @@ type MediaTypeProps struct {
// A map between a property name and its encoding information. The key, being the property name, MUST exist in the schema as a property. The encoding object SHALL only apply to requestBody objects when the media type is multipart or application/x-www-form-urlencoded
Encoding map[string]*Encoding `json:"encoding,omitempty"`
}
type mediaTypePropsOmitZero struct {
Schema *spec.Schema `json:"schema,omitzero"`
Example interface{} `json:"example,omitempty"`
Examples map[string]*Example `json:"examples,omitempty"`
Encoding map[string]*Encoding `json:"encoding,omitempty"`
}

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
@ -33,6 +35,9 @@ type Operation struct {
// MarshalJSON is a custom marshal function that knows how to encode Operation as JSON
func (o *Operation) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(o)
}
b1, err := json.Marshal(o.OperationProps)
if err != nil {
return nil, err
@ -44,14 +49,40 @@ func (o *Operation) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}
func (o *Operation) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
spec.Extensions
OperationProps operationPropsOmitZero `json:",inline"`
}
x.Extensions = internal.SanitizeExtensions(o.Extensions)
x.OperationProps = operationPropsOmitZero(o.OperationProps)
return opts.MarshalNext(enc, x)
}
// 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 +104,21 @@ 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"`
}
type operationPropsOmitZero struct {
Tags []string `json:"tags,omitempty"`
Summary string `json:"summary,omitempty"`
Description string `json:"description,omitempty"`
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitzero"`
OperationId string `json:"operationId,omitempty"`
Parameters []*Parameter `json:"parameters,omitempty"`
RequestBody *RequestBody `json:"requestBody,omitzero"`
Responses *Responses `json:"responses,omitzero"`
Deprecated bool `json:"deprecated,omitzero"`
SecurityRequirement []map[string][]string `json:"security,omitempty"`
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"
)
@ -34,6 +36,9 @@ type Parameter struct {
// MarshalJSON is a custom marshal function that knows how to encode Parameter as JSON
func (p *Parameter) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(p)
}
b1, err := json.Marshal(p.Refable)
if err != nil {
return nil, err
@ -49,7 +54,23 @@ func (p *Parameter) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2, b3), nil
}
func (p *Parameter) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Ref string `json:"$ref,omitempty"`
ParameterProps parameterPropsOmitZero `json:",inline"`
spec.Extensions
}
x.Ref = p.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(p.Extensions)
x.ParameterProps = parameterPropsOmitZero(p.ParameterProps)
return opts.MarshalNext(enc, x)
}
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 +84,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
@ -92,3 +129,19 @@ type ParameterProps struct {
// Examples of the parameter's potential value. Each example SHOULD contain a value in the correct format as specified in the parameter encoding
Examples map[string]*Example `json:"examples,omitempty"`
}
type parameterPropsOmitZero struct {
Name string `json:"name,omitempty"`
In string `json:"in,omitempty"`
Description string `json:"description,omitempty"`
Required bool `json:"required,omitzero"`
Deprecated bool `json:"deprecated,omitzero"`
AllowEmptyValue bool `json:"allowEmptyValue,omitzero"`
Style string `json:"style,omitempty"`
Explode bool `json:"explode,omitzero"`
AllowReserved bool `json:"allowReserved,omitzero"`
Schema *spec.Schema `json:"schema,omitzero"`
Content map[string]*MediaType `json:"content,omitempty"`
Example interface{} `json:"example,omitempty"`
Examples map[string]*Example `json:"examples,omitempty"`
}

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
@ -32,19 +35,48 @@ type Paths struct {
// MarshalJSON is a custom marshal function that knows how to encode Paths as JSON
func (p *Paths) MarshalJSON() ([]byte, error) {
b1, err := json.Marshal(p.Paths)
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(p)
}
b1, err := json.Marshal(p.VendorExtensible)
if err != nil {
return nil, err
}
b2, err := json.Marshal(p.VendorExtensible)
pths := make(map[string]*Path)
for k, v := range p.Paths {
if strings.HasPrefix(k, "/") {
pths[k] = v
}
}
b2, err := json.Marshal(pths)
if err != nil {
return nil, err
}
return swag.ConcatJSON(b1, b2), nil
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)
}
// 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 +106,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
@ -85,6 +170,9 @@ type Path struct {
// MarshalJSON is a custom marshal function that knows how to encode Path as JSON
func (p *Path) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(p)
}
b1, err := json.Marshal(p.Refable)
if err != nil {
return nil, err
@ -100,7 +188,22 @@ func (p *Path) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2, b3), nil
}
func (p *Path) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Ref string `json:"$ref,omitempty"`
spec.Extensions
PathProps
}
x.Ref = p.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(p.Extensions)
x.PathProps = p.PathProps
return opts.MarshalNext(enc, x)
}
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 +216,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
@ -34,6 +36,9 @@ type RequestBody struct {
// MarshalJSON is a custom marshal function that knows how to encode RequestBody as JSON
func (r *RequestBody) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(r)
}
b1, err := json.Marshal(r.Refable)
if err != nil {
return nil, err
@ -49,7 +54,22 @@ func (r *RequestBody) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2, b3), nil
}
func (r *RequestBody) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Ref string `json:"$ref,omitempty"`
RequestBodyProps requestBodyPropsOmitZero `json:",inline"`
spec.Extensions
}
x.Ref = r.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(r.Extensions)
x.RequestBodyProps = requestBodyPropsOmitZero(r.RequestBodyProps)
return opts.MarshalNext(enc, x)
}
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 +91,25 @@ type RequestBodyProps struct {
// Required determines if the request body is required in the request
Required bool `json:"required,omitempty"`
}
type requestBodyPropsOmitZero struct {
Description string `json:"description,omitempty"`
Content map[string]*MediaType `json:"content,omitempty"`
Required bool `json:"required,omitzero"`
}
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
@ -34,6 +37,9 @@ type Responses struct {
// MarshalJSON is a custom marshal function that knows how to encode Responses as JSON
func (r *Responses) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(r)
}
b1, err := json.Marshal(r.ResponsesProps)
if err != nil {
return nil, err
@ -45,14 +51,35 @@ func (r *Responses) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), 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,omitzero"`
}
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)
}
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 +105,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
@ -108,6 +201,9 @@ type Response struct {
// MarshalJSON is a custom marshal function that knows how to encode Response as JSON
func (r *Response) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(r)
}
b1, err := json.Marshal(r.Refable)
if err != nil {
return nil, err
@ -123,7 +219,22 @@ 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"`
spec.Extensions
ResponseProps `json:",inline"`
}
x.Ref = r.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(r.Extensions)
x.ResponseProps = r.ResponseProps
return opts.MarshalNext(enc, x)
}
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 +244,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 +275,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
@ -159,6 +284,9 @@ type Link struct {
// MarshalJSON is a custom marshal function that knows how to encode Link as JSON
func (r *Link) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(r)
}
b1, err := json.Marshal(r.Refable)
if err != nil {
return nil, err
@ -174,7 +302,22 @@ func (r *Link) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2, b3), nil
}
func (r *Link) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Ref string `json:"$ref,omitempty"`
spec.Extensions
LinkProps `json:",inline"`
}
x.Ref = r.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(r.Extensions)
x.LinkProps = r.LinkProps
return opts.MarshalNext(enc, x)
}
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 +331,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,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"
)
// SecurityScheme defines reusable Security Scheme Object, more at https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#securitySchemeObject
@ -32,6 +34,9 @@ type SecurityScheme struct {
// MarshalJSON is a custom marshal function that knows how to encode SecurityScheme as JSON
func (s *SecurityScheme) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(s)
}
b1, err := json.Marshal(s.SecuritySchemeProps)
if err != nil {
return nil, err
@ -47,6 +52,18 @@ func (s *SecurityScheme) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2, b3), nil
}
func (s *SecurityScheme) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Ref string `json:"$ref,omitempty"`
SecuritySchemeProps `json:",inline"`
spec.Extensions
}
x.Ref = s.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(s.Extensions)
x.SecuritySchemeProps = s.SecuritySchemeProps
return opts.MarshalNext(enc, x)
}
// UnmarshalJSON hydrates this items instance with the data from JSON
func (s *SecurityScheme) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &s.SecuritySchemeProps); err != nil {

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 {
@ -39,6 +41,9 @@ type ServerProps struct {
// MarshalJSON is a custom marshal function that knows how to encode Responses as JSON
func (s *Server) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(s)
}
b1, err := json.Marshal(s.ServerProps)
if err != nil {
return nil, err
@ -50,7 +55,21 @@ func (s *Server) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}
func (s *Server) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
ServerProps `json:",inline"`
spec.Extensions
}
x.Extensions = internal.SanitizeExtensions(s.Extensions)
x.ServerProps = s.ServerProps
return opts.MarshalNext(enc, x)
}
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 +79,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
@ -76,6 +109,9 @@ type ServerVariableProps struct {
// MarshalJSON is a custom marshal function that knows how to encode Responses as JSON
func (s *ServerVariable) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(s)
}
b1, err := json.Marshal(s.ServerVariableProps)
if err != nil {
return nil, err
@ -87,7 +123,20 @@ func (s *ServerVariable) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}
func (s *ServerVariable) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
ServerVariableProps `json:",inline"`
spec.Extensions
}
x.Extensions = internal.SanitizeExtensions(s.Extensions)
x.ServerVariableProps = s.ServerVariableProps
return opts.MarshalNext(enc, x)
}
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 +145,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"
)
@ -32,6 +36,40 @@ type OpenAPI struct {
Servers []*Server `json:"servers,omitempty"`
// Components hold various schemas for the specification
Components *Components `json:"components,omitempty"`
// SecurityRequirement holds a declaration of which security mechanisms can be used across the API
SecurityRequirement []map[string][]string `json:"security,omitempty"`
// 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)
}
func (o *OpenAPI) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(o)
}
type OpenAPIWithNoFunctions OpenAPI
p := (*OpenAPIWithNoFunctions)(o)
return json.Marshal(&p)
}
func (o *OpenAPI) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
type OpenAPIOmitZero struct {
Version string `json:"openapi"`
Info *spec.Info `json:"info"`
Paths *Paths `json:"paths,omitzero"`
Servers []*Server `json:"servers,omitempty"`
Components *Components `json:"components,omitzero"`
SecurityRequirement []map[string][]string `json:"security,omitempty"`
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitzero"`
}
x := (*OpenAPIOmitZero)(o)
return opts.MarshalNext(enc, x)
}

View File

@ -21,7 +21,7 @@ import (
"sort"
"strings"
openapi_v2 "github.com/google/gnostic/openapiv2"
openapi_v2 "github.com/google/gnostic-models/openapiv2"
"gopkg.in/yaml.v2"
)

View File

@ -21,7 +21,7 @@ import (
"reflect"
"strings"
openapi_v3 "github.com/google/gnostic/openapiv3"
openapi_v3 "github.com/google/gnostic-models/openapiv3"
"gopkg.in/yaml.v3"
)
@ -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())

View File

@ -1,502 +0,0 @@
/*
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 spec
import (
"github.com/go-openapi/jsonreference"
"github.com/google/go-cmp/cmp"
fuzz "github.com/google/gofuzz"
)
var SwaggerFuzzFuncs []interface{} = []interface{}{
func(v *Responses, c fuzz.Continue) {
c.FuzzNoCustom(v)
if v.Default != nil {
// Check if we hit maxDepth and left an incomplete value
if v.Default.Description == "" {
v.Default = nil
v.StatusCodeResponses = nil
}
}
// conversion has no way to discern empty statusCodeResponses from
// nil, since "default" is always included in the map.
// So avoid empty responses list
if len(v.StatusCodeResponses) == 0 {
v.StatusCodeResponses = nil
}
},
func(v *Operation, c fuzz.Continue) {
c.FuzzNoCustom(v)
if v != nil {
// force non-nil
v.Responses = &Responses{}
c.Fuzz(v.Responses)
v.Schemes = nil
if c.RandBool() {
v.Schemes = append(v.Schemes, "http")
}
if c.RandBool() {
v.Schemes = append(v.Schemes, "https")
}
if c.RandBool() {
v.Schemes = append(v.Schemes, "ws")
}
if c.RandBool() {
v.Schemes = append(v.Schemes, "wss")
}
// Gnostic unconditionally makes security values non-null
// So do not fuzz null values into the array.
for i, val := range v.Security {
if val == nil {
v.Security[i] = make(map[string][]string)
}
for k, v := range val {
if v == nil {
val[k] = make([]string, 0)
}
}
}
}
},
func(v map[int]Response, c fuzz.Continue) {
n := 0
c.Fuzz(&n)
if n == 0 {
// Test that fuzzer is not at maxDepth so we do not
// end up with empty elements
return
}
// Prevent negative numbers
num := c.Intn(4)
for i := 0; i < num+2; i++ {
val := Response{}
c.Fuzz(&val)
val.Description = c.RandString() + "x"
v[100*(i+1)+c.Intn(100)] = val
}
},
func(v map[string]PathItem, c fuzz.Continue) {
n := 0
c.Fuzz(&n)
if n == 0 {
// Test that fuzzer is not at maxDepth so we do not
// end up with empty elements
return
}
num := c.Intn(5)
for i := 0; i < num+2; i++ {
val := PathItem{}
c.Fuzz(&val)
// Ref params are only allowed in certain locations, so
// possibly add a few to PathItems
numRefsToAdd := c.Intn(5)
for i := 0; i < numRefsToAdd; i++ {
theRef := Parameter{}
c.Fuzz(&theRef.Refable)
val.Parameters = append(val.Parameters, theRef)
}
v["/"+c.RandString()] = val
}
},
func(v *SchemaOrArray, c fuzz.Continue) {
*v = SchemaOrArray{}
// gnostic parser just doesn't support more
// than one Schema here
v.Schema = &Schema{}
c.Fuzz(&v.Schema)
},
func(v *SchemaOrBool, c fuzz.Continue) {
*v = SchemaOrBool{}
if c.RandBool() {
v.Allows = c.RandBool()
} else {
v.Schema = &Schema{}
v.Allows = true
c.Fuzz(&v.Schema)
}
},
func(v map[string]Response, c fuzz.Continue) {
n := 0
c.Fuzz(&n)
if n == 0 {
// Test that fuzzer is not at maxDepth so we do not
// end up with empty elements
return
}
// Response definitions are not allowed to
// be refs
for i := 0; i < c.Intn(5)+1; i++ {
resp := &Response{}
c.Fuzz(resp)
resp.Ref = Ref{}
resp.Description = c.RandString() + "x"
// Response refs are not vendor extensible by gnostic
resp.VendorExtensible.Extensions = nil
v[c.RandString()+"x"] = *resp
}
},
func(v *Header, c fuzz.Continue) {
if v != nil {
c.FuzzNoCustom(v)
// descendant Items of Header may not be refs
cur := v.Items
for cur != nil {
cur.Ref = Ref{}
cur = cur.Items
}
}
},
func(v *Ref, c fuzz.Continue) {
*v = Ref{}
v.Ref, _ = jsonreference.New("http://asd.com/" + c.RandString())
},
func(v *Response, c fuzz.Continue) {
*v = Response{}
if c.RandBool() {
v.Ref = Ref{}
v.Ref.Ref, _ = jsonreference.New("http://asd.com/" + c.RandString())
} else {
c.Fuzz(&v.VendorExtensible)
c.Fuzz(&v.Schema)
c.Fuzz(&v.ResponseProps)
v.Headers = nil
v.Ref = Ref{}
n := 0
c.Fuzz(&n)
if n != 0 {
// Test that fuzzer is not at maxDepth so we do not
// end up with empty elements
num := c.Intn(4)
for i := 0; i < num; i++ {
if v.Headers == nil {
v.Headers = make(map[string]Header)
}
hdr := Header{}
c.Fuzz(&hdr)
if hdr.Type == "" {
// hit maxDepth, just abort trying to make haders
v.Headers = nil
break
}
v.Headers[c.RandString()+"x"] = hdr
}
} else {
v.Headers = nil
}
}
v.Description = c.RandString() + "x"
// Gnostic parses empty as nil, so to keep avoid putting empty
if len(v.Headers) == 0 {
v.Headers = nil
}
},
func(v **Info, c fuzz.Continue) {
// Info is never nil
*v = &Info{}
c.FuzzNoCustom(*v)
(*v).Title = c.RandString() + "x"
},
func(v *Extensions, c fuzz.Continue) {
// gnostic parser only picks up x- vendor extensions
numChildren := c.Intn(5)
for i := 0; i < numChildren; i++ {
if *v == nil {
*v = Extensions{}
}
(*v)["x-"+c.RandString()] = c.RandString()
}
},
func(v *Swagger, c fuzz.Continue) {
c.FuzzNoCustom(v)
if v.Paths == nil {
// Force paths non-nil since it does not have omitempty in json tag.
// This means a perfect roundtrip (via json) is impossible,
// since we can't tell the difference between empty/unspecified paths
v.Paths = &Paths{}
c.Fuzz(v.Paths)
}
v.Swagger = "2.0"
// Gnostic support serializing ID at all
// unavoidable data loss
v.ID = ""
v.Schemes = nil
if c.RandUint64()%2 == 1 {
v.Schemes = append(v.Schemes, "http")
}
if c.RandUint64()%2 == 1 {
v.Schemes = append(v.Schemes, "https")
}
if c.RandUint64()%2 == 1 {
v.Schemes = append(v.Schemes, "ws")
}
if c.RandUint64()%2 == 1 {
v.Schemes = append(v.Schemes, "wss")
}
// Gnostic unconditionally makes security values non-null
// So do not fuzz null values into the array.
for i, val := range v.Security {
if val == nil {
v.Security[i] = make(map[string][]string)
}
for k, v := range val {
if v == nil {
val[k] = make([]string, 0)
}
}
}
},
func(v *SecurityScheme, c fuzz.Continue) {
v.Description = c.RandString() + "x"
c.Fuzz(&v.VendorExtensible)
switch c.Intn(3) {
case 0:
v.Type = "basic"
case 1:
v.Type = "apiKey"
switch c.Intn(2) {
case 0:
v.In = "header"
case 1:
v.In = "query"
default:
panic("unreachable")
}
v.Name = "x" + c.RandString()
case 2:
v.Type = "oauth2"
switch c.Intn(4) {
case 0:
v.Flow = "accessCode"
v.TokenURL = "https://" + c.RandString()
v.AuthorizationURL = "https://" + c.RandString()
case 1:
v.Flow = "application"
v.TokenURL = "https://" + c.RandString()
case 2:
v.Flow = "implicit"
v.AuthorizationURL = "https://" + c.RandString()
case 3:
v.Flow = "password"
v.TokenURL = "https://" + c.RandString()
default:
panic("unreachable")
}
c.Fuzz(&v.Scopes)
default:
panic("unreachable")
}
},
func(v *interface{}, c fuzz.Continue) {
*v = c.RandString() + "x"
},
func(v *string, c fuzz.Continue) {
*v = c.RandString() + "x"
},
func(v *ExternalDocumentation, c fuzz.Continue) {
v.Description = c.RandString() + "x"
v.URL = c.RandString() + "x"
},
func(v *SimpleSchema, c fuzz.Continue) {
c.FuzzNoCustom(v)
switch c.Intn(5) {
case 0:
v.Type = "string"
case 1:
v.Type = "number"
case 2:
v.Type = "boolean"
case 3:
v.Type = "integer"
case 4:
v.Type = "array"
default:
panic("unreachable")
}
switch c.Intn(5) {
case 0:
v.CollectionFormat = "csv"
case 1:
v.CollectionFormat = "ssv"
case 2:
v.CollectionFormat = "tsv"
case 3:
v.CollectionFormat = "pipes"
case 4:
v.CollectionFormat = ""
default:
panic("unreachable")
}
// None of the types which include SimpleSchema in our definitions
// actually support "example" in the official spec
v.Example = nil
// unsupported by openapi
v.Nullable = false
},
func(v *int64, c fuzz.Continue) {
c.Fuzz(v)
// Gnostic does not differentiate between 0 and non-specified
// so avoid using 0 for fuzzer
if *v == 0 {
*v = 1
}
},
func(v *float64, c fuzz.Continue) {
c.Fuzz(v)
// Gnostic does not differentiate between 0 and non-specified
// so avoid using 0 for fuzzer
if *v == 0.0 {
*v = 1.0
}
},
func(v *Parameter, c fuzz.Continue) {
if v == nil {
return
}
c.Fuzz(&v.VendorExtensible)
if c.RandBool() {
// body param
v.Description = c.RandString() + "x"
v.Name = c.RandString() + "x"
v.In = "body"
c.Fuzz(&v.Description)
c.Fuzz(&v.Required)
v.Schema = &Schema{}
c.Fuzz(&v.Schema)
} else {
c.Fuzz(&v.SimpleSchema)
c.Fuzz(&v.CommonValidations)
v.AllowEmptyValue = false
v.Description = c.RandString() + "x"
v.Name = c.RandString() + "x"
switch c.Intn(4) {
case 0:
// Header param
v.In = "header"
case 1:
// Form data param
v.In = "formData"
v.AllowEmptyValue = c.RandBool()
case 2:
// Query param
v.In = "query"
v.AllowEmptyValue = c.RandBool()
case 3:
// Path param
v.In = "path"
v.Required = true
default:
panic("unreachable")
}
// descendant Items of Parameter may not be refs
cur := v.Items
for cur != nil {
cur.Ref = Ref{}
cur = cur.Items
}
}
},
func(v *Schema, c fuzz.Continue) {
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 = StringOrArray{"file"}
} else {
// normal schema
c.Fuzz(&v.SchemaProps)
c.Fuzz(&v.SwaggerSchemaProps)
c.Fuzz(&v.VendorExtensible)
// c.Fuzz(&v.ExtraProps)
// ExtraProps will not roundtrip - gnostic throws out
// unrecognized keys
}
// Not supported by official openapi v2 spec
// and stripped by k8s apiserver
v.ID = ""
v.AnyOf = nil
v.OneOf = nil
v.Not = nil
v.Nullable = false
v.AdditionalItems = nil
v.Schema = ""
v.PatternProperties = nil
v.Definitions = nil
v.Dependencies = nil
},
}
var SwaggerDiffOptions = []cmp.Option{
// cmp.Diff panics on Ref since jsonreference.Ref uses unexported fields
cmp.Comparer(func(a Ref, b Ref) bool {
return a.String() == b.String()
}),
}

View File

@ -21,7 +21,7 @@ import (
"strconv"
"github.com/go-openapi/jsonreference"
openapi_v2 "github.com/google/gnostic/openapiv2"
openapi_v2 "github.com/google/gnostic-models/openapiv2"
)
// Interfaces

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
}