rebase: bump the golang-dependencies group with 1 update

Bumps the golang-dependencies group with 1 update: [golang.org/x/crypto](https://github.com/golang/crypto).


Updates `golang.org/x/crypto` from 0.16.0 to 0.17.0
- [Commits](https://github.com/golang/crypto/compare/v0.16.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2023-12-18 20:31:00 +00:00
committed by mergify[bot]
parent 1ad79314f9
commit e5d9b68d36
398 changed files with 33924 additions and 10753 deletions

View File

@ -156,7 +156,9 @@ func (o *openAPI) buildRequestBody(parameters []common.Parameter, consumes []str
}
r := &spec3.RequestBody{
RequestBodyProps: spec3.RequestBodyProps{
Content: map[string]*spec3.MediaType{},
Content: map[string]*spec3.MediaType{},
Description: param.Description(),
Required: param.Required(),
},
}
for _, consume := range consumes {
@ -172,9 +174,9 @@ func (o *openAPI) buildRequestBody(parameters []common.Parameter, consumes []str
return nil, nil
}
func newOpenAPI(config *common.Config) openAPI {
func newOpenAPI(config *common.OpenAPIV3Config) openAPI {
o := openAPI{
config: common.ConvertConfigToV3(config),
config: config,
spec: &spec3.OpenAPI{
Version: "3.0.0",
Info: config.Info,
@ -313,12 +315,12 @@ func (o *openAPI) buildOpenAPISpec(webServices []common.RouteContainer) error {
// BuildOpenAPISpec builds OpenAPI v3 spec given a list of route containers and common.Config to customize it.
//
// Deprecated: BuildOpenAPISpecFromRoutes should be used instead.
func BuildOpenAPISpec(webServices []*restful.WebService, config *common.Config) (*spec3.OpenAPI, error) {
func BuildOpenAPISpec(webServices []*restful.WebService, config *common.OpenAPIV3Config) (*spec3.OpenAPI, error) {
return BuildOpenAPISpecFromRoutes(restfuladapter.AdaptWebServices(webServices), config)
}
// BuildOpenAPISpecFromRoutes builds OpenAPI v3 spec given a list of route containers and common.Config to customize it.
func BuildOpenAPISpecFromRoutes(webServices []common.RouteContainer, config *common.Config) (*spec3.OpenAPI, error) {
func BuildOpenAPISpecFromRoutes(webServices []common.RouteContainer, config *common.OpenAPIV3Config) (*spec3.OpenAPI, error) {
a := newOpenAPI(config)
err := a.buildOpenAPISpec(webServices)
if err != nil {
@ -330,7 +332,7 @@ func BuildOpenAPISpecFromRoutes(webServices []common.RouteContainer, config *com
// BuildOpenAPIDefinitionsForResource builds a partial OpenAPI spec given a sample object and common.Config to customize it.
// BuildOpenAPIDefinitionsForResources returns the OpenAPI spec which includes the definitions for the
// passed type names.
func BuildOpenAPIDefinitionsForResources(config *common.Config, names ...string) (map[string]*spec.Schema, error) {
func BuildOpenAPIDefinitionsForResources(config *common.OpenAPIV3Config, names ...string) (map[string]*spec.Schema, error) {
o := newOpenAPI(config)
// We can discard the return value of toSchema because all we care about is the side effect of calling it.
// All the models created for this resource get added to o.swagger.Definitions

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// Package cache provides a cache mechanism based on etags to lazily
// 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.
@ -25,16 +25,18 @@ limitations under the License.
//
// This package uses a source/transform/sink model of caches to build
// the dependency tree, and can be used as follows:
// - [NewSource]: A source cache that recomputes the content every time.
// - [NewStaticSource]: A source cache that always produces the
// - [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.
// - [NewTransformer]: A cache that transforms data from one format to
// - [Transform]: A cache that transforms data from one format to
// another. It's only refreshed when the source changes.
// - [NewMerger]: A cache that aggregates multiple caches into one.
// - [Merge]: A cache that aggregates multiple caches in a map into one.
// It's only refreshed when the source changes.
// - [Replaceable]: A cache adapter that can be atomically
// replaced with a new one, and saves the previous results in case an
// error pops-up.
// - [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
//
@ -54,61 +56,45 @@ import (
"sync/atomic"
)
// Result is the content returned from a call to a cache. It can either
// be created with [NewResultOK] if the call was a success, or
// [NewResultErr] if the call resulted in an error.
// 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 {
Data T
Etag string
Err error
Value T
Etag string
Err error
}
// NewResultOK creates a new [Result] for a successful operation.
func NewResultOK[T any](data T, etag string) Result[T] {
return Result[T]{
Data: data,
Etag: etag,
}
func (r Result[T]) Get() (T, string, error) {
return r.Value, r.Etag, r.Err
}
// NewResultErr creates a new [Result] when an error has happened.
func NewResultErr[T any](err error) Result[T] {
return Result[T]{
Err: 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)
}
// Result can be treated as a [Data] if necessary.
func (r Result[T]) Get() Result[T] {
return r
type valueFunc[T any] func() (T, string, error)
func (c valueFunc[T]) Get() (T, string, error) {
return c()
}
// Data is a cache that performs an action whose result data will be
// cached. It also returns an "etag" identifier to version the cache, so
// that the caller can know if they have the most recent version of the
// cache (and can decide to cache some operation based on that).
//
// The [NewMerger] and [NewTransformer] automatically handle
// that for you by checking if the etag is updated before calling the
// merging or transforming function.
type Data[T any] interface {
// Returns the cached data, as well as an "etag" to identify the
// version of the cache, or an error if something happened.
Get() Result[T]
// Static returns constant values.
func Static[T any](value T, etag string) Value[T] {
return Result[T]{Value: value, Etag: etag}
}
// NewMerger creates a new merge cache, a cache that merges the result
// of other caches. The function only gets called if any of the
// dependency has changed.
// 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 reran.
//
// The caches and results are mapped by K so that associated data can be
// retrieved. The map of dependencies can not be modified after
// creation, and a new merger should be created (and probably replaced
// using a [Replaceable]).
// 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
@ -118,18 +104,19 @@ type Data[T any] interface {
// 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 NewMerger[K comparable, T, V any](mergeFn func(results map[K]Result[T]) Result[V], caches map[K]Data[T]) Data[V] {
listCaches := make([]Data[T], 0, len(caches))
// maps from index to key
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 {
listCaches = append(listCaches, caches[k])
list = append(list, caches[k])
indexes[i] = k
i++
}
return NewListMerger(func(results []Result[T]) Result[V] {
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)))
}
@ -138,20 +125,11 @@ func NewMerger[K comparable, T, V any](mergeFn func(results map[K]Result[T]) Res
m[indexes[i]] = results[i]
}
return mergeFn(m)
}, listCaches)
}, list)
}
type listMerger[T, V any] struct {
lock sync.Mutex
mergeFn func([]Result[T]) Result[V]
caches []Data[T]
cacheResults []Result[T]
result Result[V]
}
// NewListMerger creates a new merge cache that merges the results of
// other caches in list form. The function only gets called if any of
// the dependency has changed.
// 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
@ -165,31 +143,37 @@ type listMerger[T, V any] struct {
// 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 NewListMerger[T, V any](mergeFn func(results []Result[T]) Result[V], caches []Data[T]) Data[V] {
func MergeList[T, V any](mergeFn func(results []Result[T]) (V, string, error), delegates []Value[T]) Value[V] {
return &listMerger[T, V]{
mergeFn: mergeFn,
caches: caches,
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.caches))
cacheResults := make([]Result[T], len(c.delegates))
ch := make(chan struct {
int
Result[T]
}, len(c.caches))
for i := range c.caches {
}, 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,
c.caches[index].Get(),
}
}{index, Result[T]{Value: value, Etag: etag, Err: err}}
}(i)
}
for i := 0; i < len(c.caches); i++ {
for i := 0; i < len(c.delegates); i++ {
res := <-ch
cacheResults[res.int] = res.Result
}
@ -197,16 +181,16 @@ func (c *listMerger[T, V]) prepareResultsLocked() []Result[T] {
}
func (c *listMerger[T, V]) needsRunningLocked(results []Result[T]) bool {
if c.cacheResults == nil {
if c.cache == nil {
return true
}
if c.result.Err != nil {
return true
}
if len(results) != len(c.cacheResults) {
panic(fmt.Errorf("invalid number of results: %v (expected %v)", len(results), len(c.cacheResults)))
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.cacheResults {
for i, oldResult := range c.cache {
newResult := results[i]
if newResult.Etag != oldResult.Etag || newResult.Err != nil || oldResult.Err != nil {
return true
@ -215,98 +199,92 @@ func (c *listMerger[T, V]) needsRunningLocked(results []Result[T]) bool {
return false
}
func (c *listMerger[T, V]) Get() Result[V] {
func (c *listMerger[T, V]) Get() (V, string, error) {
c.lock.Lock()
defer c.lock.Unlock()
cacheResults := c.prepareResultsLocked()
if c.needsRunningLocked(cacheResults) {
c.cacheResults = cacheResults
c.result = c.mergeFn(c.cacheResults)
c.cache = cacheResults
c.result.Value, c.result.Etag, c.result.Err = c.mergeFn(c.cache)
}
return c.result
return c.result.Value, c.result.Etag, c.result.Err
}
// NewTransformer creates a new cache that transforms the result of
// another cache. The transformFn will only be called if the source
// cache has updated the output, otherwise, the cached result will be
// returned.
// 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 NewTransformer[T, V any](transformerFn func(Result[T]) Result[V], source Data[T]) Data[V] {
return NewListMerger(func(caches []Result[T]) Result[V] {
if len(caches) != 1 {
panic(fmt.Errorf("invalid cache for transformer cache: %v", caches))
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(caches[0])
}, []Data[T]{source})
return transformerFn(delegates[0].Value, delegates[0].Etag, delegates[0].Err)
}, []Value[T]{source})
}
// NewSource creates a new cache that generates some data. This
// will always be called since we don't know the origin of the data and
// if it needs to be updated or not. sourceFn MUST be thread-safe.
func NewSource[T any](sourceFn func() Result[T]) Data[T] {
c := source[T](sourceFn)
return &c
}
type source[T any] func() Result[T]
func (c *source[T]) Get() Result[T] {
return (*c)()
}
// NewStaticSource creates a new cache that always generates the
// same data. This will only be called once (lazily).
func NewStaticSource[T any](staticFn func() Result[T]) Data[T] {
return &static[T]{
fn: staticFn,
// 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 static[T any] struct {
type once[T any] struct {
once sync.Once
fn func() Result[T]
data Value[T]
result Result[T]
}
func (c *static[T]) Get() Result[T] {
func (c *once[T]) Get() (T, string, error) {
c.once.Do(func() {
c.result = c.fn()
c.result.Value, c.result.Etag, c.result.Err = c.data.Get()
})
return c.result
return c.result.Value, c.result.Etag, c.result.Err
}
// Replaceable is a cache that carries the result even when the cache is
// replaced. This is the type that should typically be stored in
// structs.
type Replaceable[T any] struct {
cache atomic.Pointer[Data[T]]
result atomic.Pointer[Result[T]]
// 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])
}
// Get retrieves the data from the underlying source. [Replaceable]
// implements the [Data] interface itself. This is a pass-through
// that calls the most recent underlying cache. If the cache fails but
// previously had returned a success, that success will be returned
// instead. If the cache fails but we never returned a success, that
// failure is returned.
func (c *Replaceable[T]) Get() Result[T] {
result := (*c.cache.Load()).Get()
// 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]]
}
for {
cResult := c.result.Load()
if result.Err != nil && cResult != nil && cResult.Err == nil {
return *cResult
}
if c.result.CompareAndSwap(cResult, &result) {
return result
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
}
}
// Replace changes the cache.
func (c *Replaceable[T]) Replace(cache Data[T]) {
c.cache.Swap(&cache)
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

View File

@ -30,6 +30,7 @@ import (
openapi_v2 "github.com/google/gnostic-models/openapiv2"
"github.com/google/uuid"
"github.com/munnerz/goautoneg"
klog "k8s.io/klog/v2"
"k8s.io/kube-openapi/pkg/builder"
"k8s.io/kube-openapi/pkg/cached"
@ -59,52 +60,52 @@ type timedSpec struct {
// OpenAPIService is the service responsible for serving OpenAPI spec. It has
// the ability to safely change the spec while serving it.
type OpenAPIService struct {
specCache cached.Replaceable[*spec.Swagger]
jsonCache cached.Data[timedSpec]
protoCache cached.Data[timedSpec]
specCache cached.LastSuccess[*spec.Swagger]
jsonCache cached.Value[timedSpec]
protoCache cached.Value[timedSpec]
}
// NewOpenAPIService builds an OpenAPIService starting with the given spec.
func NewOpenAPIService(swagger *spec.Swagger) *OpenAPIService {
return NewOpenAPIServiceLazy(cached.NewResultOK(swagger, uuid.New().String()))
return NewOpenAPIServiceLazy(cached.Static(swagger, uuid.New().String()))
}
// NewOpenAPIServiceLazy builds an OpenAPIService from lazy spec.
func NewOpenAPIServiceLazy(swagger cached.Data[*spec.Swagger]) *OpenAPIService {
func NewOpenAPIServiceLazy(swagger cached.Value[*spec.Swagger]) *OpenAPIService {
o := &OpenAPIService{}
o.UpdateSpecLazy(swagger)
o.jsonCache = cached.NewTransformer[*spec.Swagger](func(result cached.Result[*spec.Swagger]) cached.Result[timedSpec] {
if result.Err != nil {
return cached.NewResultErr[timedSpec](result.Err)
}
json, err := result.Data.MarshalJSON()
o.jsonCache = cached.Transform[*spec.Swagger](func(spec *spec.Swagger, etag string, err error) (timedSpec, string, error) {
if err != nil {
return cached.NewResultErr[timedSpec](err)
return timedSpec{}, "", err
}
return cached.NewResultOK(timedSpec{spec: json, lastModified: time.Now()}, computeETag(json))
json, err := spec.MarshalJSON()
if err != nil {
return timedSpec{}, "", err
}
return timedSpec{spec: json, lastModified: time.Now()}, computeETag(json), nil
}, &o.specCache)
o.protoCache = cached.NewTransformer(func(result cached.Result[timedSpec]) cached.Result[timedSpec] {
if result.Err != nil {
return cached.NewResultErr[timedSpec](result.Err)
}
proto, err := ToProtoBinary(result.Data.spec)
o.protoCache = cached.Transform(func(ts timedSpec, etag string, err error) (timedSpec, string, error) {
if err != nil {
return cached.NewResultErr[timedSpec](err)
return timedSpec{}, "", err
}
proto, err := ToProtoBinary(ts.spec)
if err != nil {
return timedSpec{}, "", err
}
// We can re-use the same etag as json because of the Vary header.
return cached.NewResultOK(timedSpec{spec: proto, lastModified: result.Data.lastModified}, result.Etag)
return timedSpec{spec: proto, lastModified: ts.lastModified}, etag, nil
}, o.jsonCache)
return o
}
func (o *OpenAPIService) UpdateSpec(swagger *spec.Swagger) error {
o.UpdateSpecLazy(cached.NewResultOK(swagger, uuid.New().String()))
o.UpdateSpecLazy(cached.Static(swagger, uuid.New().String()))
return nil
}
func (o *OpenAPIService) UpdateSpecLazy(swagger cached.Data[*spec.Swagger]) {
o.specCache.Replace(swagger)
func (o *OpenAPIService) UpdateSpecLazy(swagger cached.Value[*spec.Swagger]) {
o.specCache.Store(swagger)
}
func ToProtoBinary(json []byte) ([]byte, error) {
@ -130,7 +131,7 @@ func (o *OpenAPIService) RegisterOpenAPIVersionedService(servePath string, handl
Type string
SubType string
ReturnedContentType string
GetDataAndEtag cached.Data[timedSpec]
GetDataAndEtag cached.Value[timedSpec]
}{
{"application", subTypeJSON, "application/" + subTypeJSON, o.jsonCache},
{"application", subTypeProtobufDeprecated, "application/" + subTypeProtobuf, o.protoCache},
@ -154,11 +155,11 @@ func (o *OpenAPIService) RegisterOpenAPIVersionedService(servePath string, handl
continue
}
// serve the first matching media type in the sorted clause list
result := accepts.GetDataAndEtag.Get()
if result.Err != nil {
klog.Errorf("Error in OpenAPI handler: %s", result.Err)
ts, etag, err := accepts.GetDataAndEtag.Get()
if err != nil {
klog.Errorf("Error in OpenAPI handler: %s", err)
// only return a 503 if we have no older cache data to serve
if result.Data.spec == nil {
if ts.spec == nil {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
@ -167,9 +168,9 @@ func (o *OpenAPIService) RegisterOpenAPIVersionedService(servePath string, handl
w.Header().Set("Content-Type", accepts.ReturnedContentType)
// ETag must be enclosed in double quotes: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
w.Header().Set("Etag", strconv.Quote(result.Etag))
w.Header().Set("Etag", strconv.Quote(etag))
// ServeContent will take care of caching using eTag.
http.ServeContent(w, r, servePath, result.Data.lastModified, bytes.NewReader(result.Data.spec))
http.ServeContent(w, r, servePath, ts.lastModified, bytes.NewReader(ts.spec))
return
}
}

View File

@ -33,6 +33,7 @@ import (
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"
@ -73,38 +74,38 @@ type timedSpec struct {
// This type is protected by the lock on OpenAPIService.
type openAPIV3Group struct {
specCache cached.Replaceable[*spec3.OpenAPI]
pbCache cached.Data[timedSpec]
jsonCache cached.Data[timedSpec]
specCache cached.LastSuccess[*spec3.OpenAPI]
pbCache cached.Value[timedSpec]
jsonCache cached.Value[timedSpec]
}
func newOpenAPIV3Group() *openAPIV3Group {
o := &openAPIV3Group{}
o.jsonCache = cached.NewTransformer[*spec3.OpenAPI](func(result cached.Result[*spec3.OpenAPI]) cached.Result[timedSpec] {
if result.Err != nil {
return cached.NewResultErr[timedSpec](result.Err)
}
json, err := json.Marshal(result.Data)
o.jsonCache = cached.Transform[*spec3.OpenAPI](func(spec *spec3.OpenAPI, etag string, err error) (timedSpec, string, error) {
if err != nil {
return cached.NewResultErr[timedSpec](err)
return timedSpec{}, "", err
}
return cached.NewResultOK(timedSpec{spec: json, lastModified: time.Now()}, computeETag(json))
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.NewTransformer(func(result cached.Result[timedSpec]) cached.Result[timedSpec] {
if result.Err != nil {
return cached.NewResultErr[timedSpec](result.Err)
}
proto, err := ToV3ProtoBinary(result.Data.spec)
o.pbCache = cached.Transform(func(ts timedSpec, etag string, err error) (timedSpec, string, error) {
if err != nil {
return cached.NewResultErr[timedSpec](err)
return timedSpec{}, "", err
}
return cached.NewResultOK(timedSpec{spec: proto, lastModified: result.Data.lastModified}, result.Etag)
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.Data[*spec3.OpenAPI]) {
o.specCache.Replace(openapi)
func (o *openAPIV3Group) UpdateSpec(openapi cached.Value[*spec3.OpenAPI]) {
o.specCache.Store(openapi)
}
// OpenAPIService is the service responsible for serving OpenAPI spec. It has
@ -114,7 +115,7 @@ type OpenAPIService struct {
mutex sync.Mutex
v3Schema map[string]*openAPIV3Group
discoveryCache cached.Replaceable[timedSpec]
discoveryCache cached.LastSuccess[timedSpec]
}
func computeETag(data []byte) string {
@ -137,20 +138,20 @@ func NewOpenAPIService() *OpenAPIService {
o := &OpenAPIService{}
o.v3Schema = make(map[string]*openAPIV3Group)
// We're not locked because we haven't shared the structure yet.
o.discoveryCache.Replace(o.buildDiscoveryCacheLocked())
o.discoveryCache.Store(o.buildDiscoveryCacheLocked())
return o
}
func (o *OpenAPIService) buildDiscoveryCacheLocked() cached.Data[timedSpec] {
caches := make(map[string]cached.Data[timedSpec], len(o.v3Schema))
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
}
return cached.NewMerger(func(results map[string]cached.Result[timedSpec]) cached.Result[timedSpec] {
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 cached.NewResultErr[timedSpec](result.Err)
return timedSpec{}, "", result.Err
}
discovery.Paths[gvName] = OpenAPIV3DiscoveryGroupVersion{
ServerRelativeURL: constructServerRelativeURL(gvName, result.Etag),
@ -158,9 +159,9 @@ func (o *OpenAPIService) buildDiscoveryCacheLocked() cached.Data[timedSpec] {
}
j, err := json.Marshal(discovery)
if err != nil {
return cached.NewResultErr[timedSpec](err)
return timedSpec{}, "", err
}
return cached.NewResultOK(timedSpec{spec: j, lastModified: time.Now()}, computeETag(j))
return timedSpec{spec: j, lastModified: time.Now()}, computeETag(j), nil
}, caches)
}
@ -171,32 +172,32 @@ func (o *OpenAPIService) getSingleGroupBytes(getType string, group string) ([]by
if !ok {
return nil, "", time.Now(), fmt.Errorf("Cannot find CRD group %s", group)
}
result := cached.Result[timedSpec]{}
switch getType {
case subTypeJSON:
result = v.jsonCache.Get()
ts, etag, err := v.jsonCache.Get()
return ts.spec, etag, ts.lastModified, err
case subTypeProtobuf, subTypeProtobufDeprecated:
result = v.pbCache.Get()
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 result.Data.spec, result.Etag, result.Data.lastModified, result.Err
}
// UpdateGroupVersionLazy adds or updates an existing group with the new cached.
func (o *OpenAPIService) UpdateGroupVersionLazy(group string, openapi cached.Data[*spec3.OpenAPI]) {
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] = newOpenAPIV3Group()
// Since there is a new item, we need to re-build the cache map.
o.discoveryCache.Replace(o.buildDiscoveryCacheLocked())
o.discoveryCache.Store(o.buildDiscoveryCacheLocked())
}
o.v3Schema[group].UpdateSpec(openapi)
}
func (o *OpenAPIService) UpdateGroupVersion(group string, openapi *spec3.OpenAPI) {
o.UpdateGroupVersionLazy(group, cached.NewResultOK(openapi, uuid.New().String()))
o.UpdateGroupVersionLazy(group, cached.Static(openapi, uuid.New().String()))
}
func (o *OpenAPIService) DeleteGroupVersion(group string) {
@ -204,19 +205,19 @@ func (o *OpenAPIService) DeleteGroupVersion(group string) {
defer o.mutex.Unlock()
delete(o.v3Schema, group)
// Rebuild the merge cache map since the items have changed.
o.discoveryCache.Replace(o.buildDiscoveryCacheLocked())
o.discoveryCache.Store(o.buildDiscoveryCacheLocked())
}
func (o *OpenAPIService) HandleDiscovery(w http.ResponseWriter, r *http.Request) {
result := o.discoveryCache.Get()
if result.Err != nil {
klog.Errorf("Error serving discovery: %s", result.Err)
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(result.Etag))
w.Header().Set("Etag", strconv.Quote(etag))
w.Header().Set("Content-Type", "application/json")
http.ServeContent(w, r, "/openapi/v3", result.Data.lastModified, bytes.NewReader(result.Data.spec))
http.ServeContent(w, r, "/openapi/v3", ts.lastModified, bytes.NewReader(ts.spec))
}
func (o *OpenAPIService) HandleGroupVersion(w http.ResponseWriter, r *http.Request) {

View File

@ -22,3 +22,4 @@ var UseOptimizedJSONUnmarshalingV3 bool = true
// Used by tests to selectively disable experimental JSON marshaler
var UseOptimizedJSONMarshaling bool = true
var UseOptimizedJSONMarshalingV3 bool = true

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
}

View File

@ -32,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
@ -43,6 +46,16 @@ 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)
@ -82,3 +95,11 @@ type EncodingProps struct {
// 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

@ -36,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
@ -50,6 +53,17 @@ 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 {

View File

@ -39,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
@ -50,6 +53,16 @@ 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)

View File

@ -35,6 +35,18 @@ var OpenAPIV3FuzzFuncs []interface{} = []interface{}{
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) {
@ -169,6 +181,21 @@ var OpenAPIV3FuzzFuncs []interface{} = []interface{}{
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++ {

View File

@ -36,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
@ -51,6 +54,18 @@ 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)
@ -109,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

@ -35,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
@ -46,6 +49,16 @@ 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)
@ -84,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

@ -35,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
@ -46,6 +49,16 @@ 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 {
@ -95,3 +108,17 @@ type OperationProps struct {
// 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

@ -36,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
@ -51,6 +54,18 @@ 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)
@ -114,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

@ -35,15 +35,41 @@ 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
@ -144,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
@ -159,6 +188,18 @@ 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)

View File

@ -36,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
@ -51,6 +54,18 @@ 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)
@ -77,6 +92,12 @@ type RequestBodyProps struct {
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

View File

@ -37,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
@ -48,6 +51,25 @@ 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)
@ -179,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
@ -194,6 +219,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"`
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)
@ -247,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
@ -262,6 +302,18 @@ 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)

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"
)
@ -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

@ -41,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
@ -52,6 +55,16 @@ 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)
@ -96,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
@ -107,6 +123,16 @@ 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)

View File

@ -36,6 +36,8 @@ 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"`
}
@ -48,3 +50,26 @@ func (o *OpenAPI) UnmarshalJSON(data []byte) error {
}
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

@ -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()
}),
}