Update go mod and vendor in client dir to 0.28.0

This commit is contained in:
Raunak Pradip Shah
2023-11-08 10:57:27 +05:30
parent dad8b28c35
commit 1bf2305d28
628 changed files with 61667 additions and 17445 deletions

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

@@ -0,0 +1,312 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package cache provides a cache mechanism based on etags to lazily
// build, and/or cache results from expensive operation such that those
// operations are not repeated unnecessarily. The operations can be
// created as a tree, and replaced dynamically as needed.
//
// 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:
// - [NewSource]: A source cache that recomputes the content every time.
// - [NewStaticSource]: A source cache that always produces the
// same content, it is only called once.
// - [NewTransformer]: A cache that transforms data from one format to
// another. It's only refreshed when the source changes.
// - [NewMerger]: A cache that aggregates multiple caches into one.
// It's only refreshed when the source changes.
// - [Replaceable]: A cache adapter that can be atomically
// replaced with a new one, and saves the previous results in case an
// error pops-up.
//
// # 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"
)
// Result is the content returned from a call to a cache. It can either
// be created with [NewResultOK] if the call was a success, or
// [NewResultErr] if the call resulted in an error.
type Result[T any] struct {
Data T
Etag string
Err error
}
// NewResultOK creates a new [Result] for a successful operation.
func NewResultOK[T any](data T, etag string) Result[T] {
return Result[T]{
Data: data,
Etag: etag,
}
}
// NewResultErr creates a new [Result] when an error has happened.
func NewResultErr[T any](err error) Result[T] {
return Result[T]{
Err: err,
}
}
// Result can be treated as a [Data] if necessary.
func (r Result[T]) Get() Result[T] {
return r
}
// Data is a cache that performs an action whose result data will be
// cached. It also returns an "etag" identifier to version the cache, so
// that the caller can know if they have the most recent version of the
// cache (and can decide to cache some operation based on that).
//
// The [NewMerger] and [NewTransformer] automatically handle
// that for you by checking if the etag is updated before calling the
// merging or transforming function.
type Data[T any] interface {
// Returns the cached data, as well as an "etag" to identify the
// version of the cache, or an error if something happened.
Get() Result[T]
}
// NewMerger creates a new merge cache, a cache that merges the result
// of other caches. The function only gets called if any of the
// dependency has changed.
//
// If any of the dependency returned an error before, or any of the
// dependency returned an error this time, or if the mergeFn failed
// before, then the function is reran.
//
// The caches and results are mapped by K so that associated data can be
// retrieved. The map of dependencies can not be modified after
// creation, and a new merger should be created (and probably replaced
// using a [Replaceable]).
//
// Note that this assumes there is no "partial" merge, the merge
// function will remerge all the dependencies together everytime. Since
// the list of dependencies is constant, there is no way to save some
// partial merge information either.
//
// 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
indexes := make(map[int]K, len(caches))
i := 0
for k := range caches {
listCaches = append(listCaches, caches[k])
indexes[i] = k
i++
}
return NewListMerger(func(results []Result[T]) Result[V] {
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)
}, listCaches)
}
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.
//
// 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 NewListMerger[T, V any](mergeFn func(results []Result[T]) Result[V], caches []Data[T]) Data[V] {
return &listMerger[T, V]{
mergeFn: mergeFn,
caches: caches,
}
}
func (c *listMerger[T, V]) prepareResultsLocked() []Result[T] {
cacheResults := make([]Result[T], len(c.caches))
ch := make(chan struct {
int
Result[T]
}, len(c.caches))
for i := range c.caches {
go func(index int) {
ch <- struct {
int
Result[T]
}{
index,
c.caches[index].Get(),
}
}(i)
}
for i := 0; i < len(c.caches); i++ {
res := <-ch
cacheResults[res.int] = res.Result
}
return cacheResults
}
func (c *listMerger[T, V]) needsRunningLocked(results []Result[T]) bool {
if c.cacheResults == nil {
return true
}
if c.result.Err != nil {
return true
}
if len(results) != len(c.cacheResults) {
panic(fmt.Errorf("invalid number of results: %v (expected %v)", len(results), len(c.cacheResults)))
}
for i, oldResult := range c.cacheResults {
newResult := results[i]
if newResult.Etag != oldResult.Etag || newResult.Err != nil || oldResult.Err != nil {
return true
}
}
return false
}
func (c *listMerger[T, V]) Get() Result[V] {
c.lock.Lock()
defer c.lock.Unlock()
cacheResults := c.prepareResultsLocked()
if c.needsRunningLocked(cacheResults) {
c.cacheResults = cacheResults
c.result = c.mergeFn(c.cacheResults)
}
return c.result
}
// NewTransformer creates a new cache that transforms the result of
// another cache. The transformFn will only be called if the source
// cache has updated the output, otherwise, the cached result will be
// returned.
//
// If the dependency returned an error before, or it returns an error
// this time, or if the transformerFn failed before, the function is
// reran.
func NewTransformer[T, V any](transformerFn func(Result[T]) Result[V], source Data[T]) Data[V] {
return NewListMerger(func(caches []Result[T]) Result[V] {
if len(caches) != 1 {
panic(fmt.Errorf("invalid cache for transformer cache: %v", caches))
}
return transformerFn(caches[0])
}, []Data[T]{source})
}
// NewSource creates a new cache that generates some data. This
// will always be called since we don't know the origin of the data and
// if it needs to be updated or not. 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,
}
}
type static[T any] struct {
once sync.Once
fn func() Result[T]
result Result[T]
}
func (c *static[T]) Get() Result[T] {
c.once.Do(func() {
c.result = c.fn()
})
return c.result
}
// 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]]
}
// 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()
for {
cResult := c.result.Load()
if result.Err != nil && cResult != nil && cResult.Err == nil {
return *cResult
}
if c.result.CompareAndSwap(cResult, &result) {
return result
}
}
}
// Replace changes the cache.
func (c *Replaceable[T]) Replace(cache Data[T]) {
c.cache.Swap(&cache)
}

View File

@@ -24,19 +24,19 @@ import (
"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 (
@@ -58,23 +58,63 @@ type OpenAPIV3DiscoveryGroupVersion struct {
ServerRelativeURL string `json:"serverRelativeURL"`
}
func ToV3ProtoBinary(json []byte) ([]byte, error) {
document, err := openapi_v3.ParseDocument(json)
if err != nil {
return nil, err
}
return proto.Marshal(document)
}
type timedSpec struct {
spec []byte
lastModified time.Time
}
// This type is protected by the lock on OpenAPIService.
type openAPIV3Group struct {
specCache cached.Replaceable[*spec3.OpenAPI]
pbCache cached.Data[timedSpec]
jsonCache cached.Data[timedSpec]
}
func newOpenAPIV3Group() *openAPIV3Group {
o := &openAPIV3Group{}
o.jsonCache = cached.NewTransformer[*spec3.OpenAPI](func(result cached.Result[*spec3.OpenAPI]) cached.Result[timedSpec] {
if result.Err != nil {
return cached.NewResultErr[timedSpec](result.Err)
}
json, err := json.Marshal(result.Data)
if err != nil {
return cached.NewResultErr[timedSpec](err)
}
return cached.NewResultOK(timedSpec{spec: json, lastModified: time.Now()}, computeETag(json))
}, &o.specCache)
o.pbCache = cached.NewTransformer(func(result cached.Result[timedSpec]) cached.Result[timedSpec] {
if result.Err != nil {
return cached.NewResultErr[timedSpec](result.Err)
}
proto, err := ToV3ProtoBinary(result.Data.spec)
if err != nil {
return cached.NewResultErr[timedSpec](err)
}
return cached.NewResultOK(timedSpec{spec: proto, lastModified: result.Data.lastModified}, result.Etag)
}, o.jsonCache)
return o
}
func (o *openAPIV3Group) UpdateSpec(openapi cached.Data[*spec3.OpenAPI]) {
o.specCache.Replace(openapi)
}
// OpenAPIService is the service responsible for serving OpenAPI spec. It has
// the ability to safely change the spec while serving it.
type OpenAPIService struct {
// rwMutex protects All members of this service.
rwMutex sync.RWMutex
lastModified time.Time
v3Schema map[string]*OpenAPIV3Group
}
// Mutex protects the schema map.
mutex sync.Mutex
v3Schema map[string]*openAPIV3Group
type OpenAPIV3Group struct {
rwMutex sync.RWMutex
lastModified time.Time
pbCache handler.HandlerCache
jsonCache handler.HandlerCache
etagCache handler.HandlerCache
discoveryCache cached.Replaceable[timedSpec]
}
func computeETag(data []byte) string {
@@ -93,94 +133,90 @@ func constructServerRelativeURL(gvString, etag string) string {
}
// NewOpenAPIService builds an OpenAPIService starting with the given spec.
func NewOpenAPIService(spec *spec.Swagger) (*OpenAPIService, error) {
func NewOpenAPIService() *OpenAPIService {
o := &OpenAPIService{}
o.v3Schema = make(map[string]*OpenAPIV3Group)
return o, nil
o.v3Schema = make(map[string]*openAPIV3Group)
// We're not locked because we haven't shared the structure yet.
o.discoveryCache.Replace(o.buildDiscoveryCacheLocked())
return o
}
func (o *OpenAPIService) getGroupBytes() ([]byte, error) {
o.rwMutex.RLock()
defer o.rwMutex.RUnlock()
keys := make([]string, len(o.v3Schema))
i := 0
for k := range o.v3Schema {
keys[i] = k
i++
func (o *OpenAPIService) buildDiscoveryCacheLocked() cached.Data[timedSpec] {
caches := make(map[string]cached.Data[timedSpec], len(o.v3Schema))
for gvName, group := range o.v3Schema {
caches[gvName] = group.jsonCache
}
sort.Strings(keys)
discovery := &OpenAPIV3Discovery{Paths: make(map[string]OpenAPIV3DiscoveryGroupVersion)}
for gvString, groupVersion := range o.v3Schema {
etagBytes, err := groupVersion.etagCache.Get()
return cached.NewMerger(func(results map[string]cached.Result[timedSpec]) cached.Result[timedSpec] {
discovery := &OpenAPIV3Discovery{Paths: make(map[string]OpenAPIV3DiscoveryGroupVersion)}
for gvName, result := range results {
if result.Err != nil {
return cached.NewResultErr[timedSpec](result.Err)
}
discovery.Paths[gvName] = OpenAPIV3DiscoveryGroupVersion{
ServerRelativeURL: constructServerRelativeURL(gvName, result.Etag),
}
}
j, err := json.Marshal(discovery)
if err != nil {
return nil, err
return cached.NewResultErr[timedSpec](err)
}
discovery.Paths[gvString] = OpenAPIV3DiscoveryGroupVersion{
ServerRelativeURL: constructServerRelativeURL(gvString, string(etagBytes)),
}
}
j, err := json.Marshal(discovery)
if err != nil {
return nil, err
}
return j, nil
return cached.NewResultOK(timedSpec{spec: j, lastModified: time.Now()}, computeETag(j))
}, caches)
}
func (o *OpenAPIService) getSingleGroupBytes(getType string, group string) ([]byte, string, time.Time, error) {
o.rwMutex.RLock()
defer o.rwMutex.RUnlock()
o.mutex.Lock()
defer o.mutex.Unlock()
v, ok := o.v3Schema[group]
if !ok {
return nil, "", time.Now(), fmt.Errorf("Cannot find CRD group %s", group)
}
if getType == subTypeJSON {
specBytes, err := v.jsonCache.Get()
if err != nil {
return nil, "", v.lastModified, err
}
etagBytes, err := v.etagCache.Get()
return specBytes, string(etagBytes), v.lastModified, err
} else if getType == subTypeProtobuf || getType == subTypeProtobufDeprecated {
specPb, err := v.pbCache.Get()
if err != nil {
return nil, "", v.lastModified, err
}
etagBytes, err := v.etagCache.Get()
return specPb, string(etagBytes), v.lastModified, err
result := cached.Result[timedSpec]{}
switch getType {
case subTypeJSON:
result = v.jsonCache.Get()
case subTypeProtobuf, subTypeProtobufDeprecated:
result = v.pbCache.Get()
default:
return nil, "", time.Now(), fmt.Errorf("Invalid accept clause %s", getType)
}
return nil, "", time.Now(), fmt.Errorf("Invalid accept clause %s", getType)
return result.Data.spec, result.Etag, result.Data.lastModified, result.Err
}
func (o *OpenAPIService) UpdateGroupVersion(group string, openapi *spec3.OpenAPI) (err error) {
o.rwMutex.Lock()
defer o.rwMutex.Unlock()
// UpdateGroupVersionLazy adds or updates an existing group with the new cached.
func (o *OpenAPIService) UpdateGroupVersionLazy(group string, openapi cached.Data[*spec3.OpenAPI]) {
o.mutex.Lock()
defer o.mutex.Unlock()
if _, ok := o.v3Schema[group]; !ok {
o.v3Schema[group] = &OpenAPIV3Group{}
o.v3Schema[group] = newOpenAPIV3Group()
// Since there is a new item, we need to re-build the cache map.
o.discoveryCache.Replace(o.buildDiscoveryCacheLocked())
}
return o.v3Schema[group].UpdateSpec(openapi)
o.v3Schema[group].UpdateSpec(openapi)
}
func (o *OpenAPIService) UpdateGroupVersion(group string, openapi *spec3.OpenAPI) {
o.UpdateGroupVersionLazy(group, cached.NewResultOK(openapi, uuid.New().String()))
}
func (o *OpenAPIService) DeleteGroupVersion(group string) {
o.rwMutex.Lock()
defer o.rwMutex.Unlock()
o.mutex.Lock()
defer o.mutex.Unlock()
delete(o.v3Schema, group)
}
func ToV3ProtoBinary(json []byte) ([]byte, error) {
document, err := openapi_v3.ParseDocument(json)
if err != nil {
return nil, err
}
return proto.Marshal(document)
// Rebuild the merge cache map since the items have changed.
o.discoveryCache.Replace(o.buildDiscoveryCacheLocked())
}
func (o *OpenAPIService) HandleDiscovery(w http.ResponseWriter, r *http.Request) {
data, _ := o.getGroupBytes()
w.Header().Set("Etag", strconv.Quote(computeETag(data)))
result := o.discoveryCache.Get()
if result.Err != nil {
klog.Errorf("Error serving discovery: %s", result.Err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Etag", strconv.Quote(result.Etag))
w.Header().Set("Content-Type", "application/json")
http.ServeContent(w, r, "/openapi/v3", time.Now(), bytes.NewReader(data))
http.ServeContent(w, r, "/openapi/v3", result.Data.lastModified, bytes.NewReader(result.Data.spec))
}
func (o *OpenAPIService) HandleGroupVersion(w http.ResponseWriter, r *http.Request) {
@@ -256,30 +292,3 @@ func (o *OpenAPIService) RegisterOpenAPIV3VersionedService(servePath string, han
handler.HandlePrefix(servePath+"/", http.HandlerFunc(o.HandleGroupVersion))
return nil
}
func (o *OpenAPIV3Group) UpdateSpec(openapi *spec3.OpenAPI) (err error) {
o.rwMutex.Lock()
defer o.rwMutex.Unlock()
o.jsonCache = o.jsonCache.New(func() ([]byte, error) {
return json.Marshal(openapi)
})
o.pbCache = o.pbCache.New(func() ([]byte, error) {
json, err := o.jsonCache.Get()
if err != nil {
return nil, err
}
return ToV3ProtoBinary(json)
})
// TODO: This forces a json marshal of corresponding group-versions.
// We should look to replace this with a faster hashing mechanism.
o.etagCache = o.etagCache.New(func() ([]byte, error) {
json, err := o.jsonCache.Get()
if err != nil {
return nil, err
}
return []byte(computeETag(json)), nil
})
o.lastModified = time.Now()
return nil
}

View File

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

View File

@@ -1,57 +0,0 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package handler
import (
"sync"
)
// HandlerCache represents a lazy cache for generating a byte array
// It is used to lazily marshal OpenAPI v2/v3 and lazily generate the ETag
type HandlerCache struct {
BuildCache func() ([]byte, error)
once sync.Once
bytes []byte
err error
}
// Get either returns the cached value or calls BuildCache() once before caching and returning
// its results. If BuildCache returns an error, the last valid value for the cache (from prior
// calls to New()) is used instead if possible.
func (c *HandlerCache) Get() ([]byte, error) {
c.once.Do(func() {
bytes, err := c.BuildCache()
// if there is an error updating the cache, there can be situations where
// c.bytes contains a valid value (carried over from the previous update)
// but c.err is also not nil; the cache user is expected to check for this
c.err = err
if c.err == nil {
// don't override previous spec if we had an error
c.bytes = bytes
}
})
return c.bytes, c.err
}
// New creates a new HandlerCache for situations where a cache refresh is needed.
// This function is not thread-safe and should not be called at the same time as Get().
func (c *HandlerCache) New(cacheBuilder func() ([]byte, error)) HandlerCache {
return HandlerCache{
bytes: c.bytes,
BuildCache: cacheBuilder,
}
}

View File

@@ -0,0 +1,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
}

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

@@ -0,0 +1,337 @@
/*
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 schemaconv
import (
"fmt"
"sort"
"sigs.k8s.io/structured-merge-diff/v4/schema"
)
const (
quantityResource = "io.k8s.apimachinery.pkg.api.resource.Quantity"
rawExtensionResource = "io.k8s.apimachinery.pkg.runtime.RawExtension"
)
type convert struct {
preserveUnknownFields bool
output *schema.Schema
currentName string
current *schema.Atom
errorMessages []string
}
func (c *convert) push(name string, a *schema.Atom) *convert {
return &convert{
preserveUnknownFields: c.preserveUnknownFields,
output: c.output,
currentName: name,
current: a,
}
}
func (c *convert) top() *schema.Atom { return c.current }
func (c *convert) pop(c2 *convert) {
c.errorMessages = append(c.errorMessages, c2.errorMessages...)
}
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, atom schema.Atom) {
def := schema.TypeDef{
Name: name,
Atom: atom,
}
if def.Atom == (schema.Atom{}) {
// This could happen if there were a top-level reference.
return
}
c.output.Types = append(c.output.Types, def)
}
func (c *convert) addCommonTypes() {
c.output.Types = append(c.output.Types, untypedDef)
c.output.Types = append(c.output.Types, deducedDef)
}
var untypedName string = "__untyped_atomic_"
var untypedDef schema.TypeDef = schema.TypeDef{
Name: untypedName,
Atom: schema.Atom{
Scalar: ptr(schema.Scalar("untyped")),
List: &schema.List{
ElementType: schema.TypeRef{
NamedType: &untypedName,
},
ElementRelationship: schema.Atomic,
},
Map: &schema.Map{
ElementType: schema.TypeRef{
NamedType: &untypedName,
},
ElementRelationship: schema.Atomic,
},
},
}
var deducedName string = "__untyped_deduced_"
var deducedDef schema.TypeDef = schema.TypeDef{
Name: deducedName,
Atom: schema.Atom{
Scalar: ptr(schema.Scalar("untyped")),
List: &schema.List{
ElementType: schema.TypeRef{
NamedType: &untypedName,
},
ElementRelationship: schema.Atomic,
},
Map: &schema.Map{
ElementType: schema.TypeRef{
NamedType: &deducedName,
},
ElementRelationship: schema.Separable,
},
},
}
func makeUnions(extensions map[string]interface{}) ([]schema.Union, error) {
schemaUnions := []schema.Union{}
if iunions, ok := extensions["x-kubernetes-unions"]; ok {
unions, ok := iunions.([]interface{})
if !ok {
return nil, fmt.Errorf(`"x-kubernetes-unions" should be a list, got %#v`, unions)
}
for _, iunion := range unions {
union, ok := iunion.(map[interface{}]interface{})
if !ok {
return nil, fmt.Errorf(`"x-kubernetes-unions" items should be a map of string to unions, got %#v`, iunion)
}
unionMap := map[string]interface{}{}
for k, v := range union {
key, ok := k.(string)
if !ok {
return nil, fmt.Errorf(`"x-kubernetes-unions" has non-string key: %#v`, k)
}
unionMap[key] = v
}
schemaUnion, err := makeUnion(unionMap)
if err != nil {
return nil, err
}
schemaUnions = append(schemaUnions, schemaUnion)
}
}
// Make sure we have no overlap between unions
fs := map[string]struct{}{}
for _, u := range schemaUnions {
if u.Discriminator != nil {
if _, ok := fs[*u.Discriminator]; ok {
return nil, fmt.Errorf("%v field appears multiple times in unions", *u.Discriminator)
}
fs[*u.Discriminator] = struct{}{}
}
for _, f := range u.Fields {
if _, ok := fs[f.FieldName]; ok {
return nil, fmt.Errorf("%v field appears multiple times in unions", f.FieldName)
}
fs[f.FieldName] = struct{}{}
}
}
return schemaUnions, nil
}
func makeUnion(extensions map[string]interface{}) (schema.Union, error) {
union := schema.Union{
Fields: []schema.UnionField{},
}
if idiscriminator, ok := extensions["discriminator"]; ok {
discriminator, ok := idiscriminator.(string)
if !ok {
return schema.Union{}, fmt.Errorf(`"discriminator" must be a string, got: %#v`, idiscriminator)
}
union.Discriminator = &discriminator
}
if ifields, ok := extensions["fields-to-discriminateBy"]; ok {
fields, ok := ifields.(map[interface{}]interface{})
if !ok {
return schema.Union{}, fmt.Errorf(`"fields-to-discriminateBy" must be a map[string]string, got: %#v`, ifields)
}
// Needs sorted keys by field.
keys := []string{}
for ifield := range fields {
field, ok := ifield.(string)
if !ok {
return schema.Union{}, fmt.Errorf(`"fields-to-discriminateBy": field must be a string, got: %#v`, ifield)
}
keys = append(keys, field)
}
sort.Strings(keys)
reverseMap := map[string]struct{}{}
for _, field := range keys {
value := fields[field]
discriminated, ok := value.(string)
if !ok {
return schema.Union{}, fmt.Errorf(`"fields-to-discriminateBy"/%v: value must be a string, got: %#v`, field, value)
}
union.Fields = append(union.Fields, schema.UnionField{
FieldName: field,
DiscriminatorValue: discriminated,
})
// Check that we don't have the same discriminateBy multiple times.
if _, ok := reverseMap[discriminated]; ok {
return schema.Union{}, fmt.Errorf("Multiple fields have the same discriminated name: %v", discriminated)
}
reverseMap[discriminated] = struct{}{}
}
}
if union.Discriminator != nil && len(union.Fields) == 0 {
return schema.Union{}, fmt.Errorf("discriminator set to %v, but no fields in union", *union.Discriminator)
}
return union, nil
}
func toStringSlice(o interface{}) (out []string, ok bool) {
switch t := o.(type) {
case []interface{}:
for _, v := range t {
switch vt := v.(type) {
case string:
out = append(out, vt)
}
}
return out, true
case []string:
return t, true
}
return nil, false
}
func ptr(s schema.Scalar) *schema.Scalar { return &s }
// 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)
}
}

View File

@@ -18,7 +18,10 @@ package spec3
import (
"encoding/json"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/internal"
jsonv2 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
"k8s.io/kube-openapi/pkg/validation/spec"
)
@@ -41,6 +44,9 @@ func (e *Encoding) MarshalJSON() ([]byte, error) {
}
func (e *Encoding) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, e)
}
if err := json.Unmarshal(data, &e.EncodingProps); err != nil {
return err
}
@@ -50,6 +56,20 @@ func (e *Encoding) UnmarshalJSON(data []byte) error {
return nil
}
func (e *Encoding) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error {
var x struct {
spec.Extensions
EncodingProps
}
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
e.Extensions = internal.SanitizeExtensions(x.Extensions)
e.EncodingProps = x.EncodingProps
return nil
}
type EncodingProps struct {
// Content Type for encoding a specific property
ContentType string `json:"contentType,omitempty"`

View File

@@ -20,6 +20,9 @@ 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"
)
@@ -49,6 +52,9 @@ func (e *Example) MarshalJSON() ([]byte, error) {
}
func (e *Example) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, e)
}
if err := json.Unmarshal(data, &e.Refable); err != nil {
return err
}
@@ -61,6 +67,23 @@ func (e *Example) UnmarshalJSON(data []byte) error {
return nil
}
func (e *Example) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error {
var x struct {
spec.Extensions
ExampleProps
}
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
if err := internal.JSONRefFromMap(&e.Ref.Ref, x.Extensions); err != nil {
return err
}
e.Extensions = internal.SanitizeExtensions(x.Extensions)
e.ExampleProps = x.ExampleProps
return nil
}
type ExampleProps struct {
// Summary holds a short description of the example
Summary string `json:"summary,omitempty"`

View File

@@ -18,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"
)
@@ -48,6 +51,9 @@ func (e *ExternalDocumentation) MarshalJSON() ([]byte, error) {
}
func (e *ExternalDocumentation) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, e)
}
if err := json.Unmarshal(data, &e.ExternalDocumentationProps); err != nil {
return err
}
@@ -56,3 +62,16 @@ func (e *ExternalDocumentation) UnmarshalJSON(data []byte) error {
}
return nil
}
func (e *ExternalDocumentation) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error {
var x struct {
spec.Extensions
ExternalDocumentationProps
}
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
e.Extensions = internal.SanitizeExtensions(x.Extensions)
e.ExternalDocumentationProps = x.ExternalDocumentationProps
return nil
}

View File

@@ -1,10 +1,11 @@
package spec3
import (
fuzz "github.com/google/gofuzz"
"math/rand"
"strings"
fuzz "github.com/google/gofuzz"
"k8s.io/kube-openapi/pkg/validation/spec"
)
@@ -169,82 +170,85 @@ var OpenAPIV3FuzzFuncs []interface{} = []interface{}{
c.Fuzz(&v.VendorExtensible)
},
func(v *spec.Extensions, c fuzz.Continue) {
*v = spec.Extensions{}
numChildren := c.Intn(5)
if numChildren == 0 {
*v = nil
}
for i := 0; i < numChildren; i++ {
v.Add("x-"+randAlphanumString(), c.RandString()+"x")
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
}
c.Fuzz(&v.VendorExtensible)
c.Fuzz(&v.Description)
c.Fuzz(&v.Nullable)
c.Fuzz(&v.Title)
c.Fuzz(&v.Required)
c.Fuzz(&v.ExternalDocs)
n := c.Intn(8)
switch n {
case 0:
// To prevent exponential growth from recursively generating properties, only allow the schema to be an object with low frequency
if c.Intn(5) == 0 {
c.Fuzz(&v.Properties)
c.Fuzz(&v.MinProperties)
c.Fuzz(&v.MaxProperties)
} else {
v.Type = spec.StringOrArray{"integer"}
switch c.Intn(3) {
case 0:
v.Format = "int32"
case 1:
v.Format = "int64"
}
c.Fuzz(&v.MultipleOf)
c.Fuzz(&v.Minimum)
c.Fuzz(&v.Maximum)
c.Fuzz(&v.ExclusiveMaximum)
c.Fuzz(&v.ExclusiveMinimum)
}
case 1:
v.Type = spec.StringOrArray{"number"}
switch c.Intn(3) {
case 0:
v.Format = "float"
case 1:
v.Format = "double"
}
c.Fuzz(&v.MultipleOf)
c.Fuzz(&v.ExclusiveMaximum)
c.Fuzz(&v.ExclusiveMinimum)
c.Fuzz(&v.Minimum)
c.Fuzz(&v.Maximum)
case 2:
v.Type = spec.StringOrArray{"string"}
c.Fuzz(&v.MinLength)
c.Fuzz(&v.MaxLength)
case 3:
v.Type = spec.StringOrArray{"boolean"}
case 4:
v.Type = spec.StringOrArray{"array"}
s := spec.Schema{}
c.Fuzz(&s)
v.Items = &spec.SchemaOrArray{Schema: &s}
case 5:
c.Fuzz(&v.AnyOf)
case 6:
c.Fuzz(&v.AllOf)
case 7:
c.Fuzz(&v.OneOf)
if c.RandBool() {
// file schema
c.Fuzz(&v.Default)
c.Fuzz(&v.Description)
c.Fuzz(&v.Example)
c.Fuzz(&v.ExternalDocs)
c.Fuzz(&v.Format)
c.Fuzz(&v.ReadOnly)
c.Fuzz(&v.Required)
c.Fuzz(&v.Title)
v.Type = spec.StringOrArray{"file"}
} else {
// normal schema
c.Fuzz(&v.SchemaProps)
c.Fuzz(&v.SwaggerSchemaProps)
c.Fuzz(&v.VendorExtensible)
c.Fuzz(&v.ExtraProps)
}
},
}

View File

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

View File

@@ -18,7 +18,10 @@ package spec3
import (
"encoding/json"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/internal"
jsonv2 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
"k8s.io/kube-openapi/pkg/validation/spec"
)
@@ -44,6 +47,9 @@ func (m *MediaType) MarshalJSON() ([]byte, error) {
}
func (m *MediaType) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, m)
}
if err := json.Unmarshal(data, &m.MediaTypeProps); err != nil {
return err
}
@@ -53,6 +59,20 @@ 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

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"
)
@@ -46,12 +48,28 @@ func (o *Operation) MarshalJSON() ([]byte, error) {
// UnmarshalJSON hydrates this items instance with the data from JSON
func (o *Operation) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, o)
}
if err := json.Unmarshal(data, &o.OperationProps); err != nil {
return err
}
return json.Unmarshal(data, &o.VendorExtensible)
}
func (o *Operation) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error {
var x struct {
spec.Extensions
OperationProps
}
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
o.Extensions = internal.SanitizeExtensions(x.Extensions)
o.OperationProps = x.OperationProps
return nil
}
// OperationProps describes a single API operation on a path, more at https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#operationObject
type OperationProps struct {
// Tags holds a list of tags for API documentation control

View File

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

View File

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

View File

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

View File

@@ -18,9 +18,12 @@ package spec3
import (
"encoding/json"
"fmt"
"strconv"
"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"
)
@@ -46,13 +49,15 @@ func (r *Responses) MarshalJSON() ([]byte, error) {
}
func (r *Responses) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, r)
}
if err := json.Unmarshal(data, &r.ResponsesProps); err != nil {
return err
}
if err := json.Unmarshal(data, &r.VendorExtensible); err != nil {
return err
}
return nil
}
@@ -78,6 +83,9 @@ func (r ResponsesProps) MarshalJSON() ([]byte, error) {
// UnmarshalJSON unmarshals responses from JSON
func (r *ResponsesProps) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, r)
}
var res map[string]json.RawMessage
if err := json.Unmarshal(data, &res); err != nil {
return err
@@ -106,6 +114,60 @@ func (r *ResponsesProps) UnmarshalJSON(data []byte) error {
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
@@ -133,6 +195,9 @@ func (r *Response) MarshalJSON() ([]byte, error) {
}
func (r *Response) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, r)
}
if err := json.Unmarshal(data, &r.Refable); err != nil {
return err
}
@@ -142,7 +207,22 @@ func (r *Response) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &r.VendorExtensible); err != nil {
return err
}
return nil
}
func (r *Response) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error {
var x struct {
spec.Extensions
ResponseProps
}
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
if err := internal.JSONRefFromMap(&r.Ref.Ref, x.Extensions); err != nil {
return err
}
r.Extensions = internal.SanitizeExtensions(x.Extensions)
r.ResponseProps = x.ResponseProps
return nil
}
@@ -183,6 +263,9 @@ func (r *Link) MarshalJSON() ([]byte, error) {
}
func (r *Link) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, r)
}
if err := json.Unmarshal(data, &r.Refable); err != nil {
return err
}
@@ -196,6 +279,22 @@ func (r *Link) UnmarshalJSON(data []byte) error {
return nil
}
func (l *Link) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error {
var x struct {
spec.Extensions
LinkProps
}
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
if err := internal.JSONRefFromMap(&l.Ref.Ref, x.Extensions); err != nil {
return err
}
l.Extensions = internal.SanitizeExtensions(x.Extensions)
l.LinkProps = x.LinkProps
return nil
}
// LinkProps describes a single response from an API Operation, more at https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#responseObject
type LinkProps struct {
// OperationId is the name of an existing, resolvable OAS operation

View File

@@ -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"
)
@@ -50,6 +53,10 @@ func (s *Server) MarshalJSON() ([]byte, error) {
}
func (s *Server) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, s)
}
if err := json.Unmarshal(data, &s.ServerProps); err != nil {
return err
}
@@ -59,6 +66,20 @@ func (s *Server) UnmarshalJSON(data []byte) error {
return nil
}
func (s *Server) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error {
var x struct {
spec.Extensions
ServerProps
}
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
s.Extensions = internal.SanitizeExtensions(x.Extensions)
s.ServerProps = x.ServerProps
return nil
}
type ServerVariable struct {
ServerVariableProps
spec.VendorExtensible
@@ -87,6 +108,9 @@ func (s *ServerVariable) MarshalJSON() ([]byte, error) {
}
func (s *ServerVariable) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, s)
}
if err := json.Unmarshal(data, &s.ServerVariableProps); err != nil {
return err
}
@@ -95,3 +119,17 @@ func (s *ServerVariable) UnmarshalJSON(data []byte) error {
}
return nil
}
func (s *ServerVariable) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error {
var x struct {
spec.Extensions
ServerVariableProps
}
if err := opts.UnmarshalNext(dec, &x); err != nil {
return err
}
s.Extensions = internal.SanitizeExtensions(x.Extensions)
s.ServerVariableProps = x.ServerVariableProps
return nil
}

View File

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

View File

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

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

@@ -523,6 +523,7 @@ func (s Schema) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder)
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))
@@ -537,6 +538,7 @@ func (s Schema) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder)
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)
}
@@ -622,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

@@ -164,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
@@ -385,7 +385,7 @@ func (s SchemaOrArray) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshaling {
return internal.DeterministicMarshal(s)
}
if len(s.Schemas) > 0 {
if s.Schemas != nil {
return json.Marshal(s.Schemas)
}
return json.Marshal(s.Schema)
@@ -393,7 +393,7 @@ func (s SchemaOrArray) MarshalJSON() ([]byte, error) {
// MarshalJSON converts this schema object or array into JSON structure
func (s SchemaOrArray) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
if len(s.Schemas) > 0 {
if s.Schemas != nil {
return opts.MarshalNext(enc, s.Schemas)
}
return opts.MarshalNext(enc, s.Schema)