apiextensions: add structural schema intermediate types

This commit is contained in:
Dr. Stefan Schimanski 2019-04-29 10:22:59 +02:00
parent d8a9dfacbf
commit f9dc278e75
6 changed files with 831 additions and 0 deletions

View File

@ -686,6 +686,10 @@ func (v *specStandardValidatorV3) validate(schema *apiextensions.JSONSchemaProps
return allErrs return allErrs
} }
//
// WARNING: if anything new is allowed below, NewStructural must be adapted to support it.
//
if schema.Default != nil { if schema.Default != nil {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("default"), "default is not supported")) allErrs = append(allErrs, field.Forbidden(fldPath.Child("default"), "default is not supported"))
} }

View File

@ -0,0 +1,82 @@
/*
Copyright 2019 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 schema
import (
"fmt"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// validateStructuralCompleteness checks that every specified field or array in s is also specified
// outside of value validation.
func validateStructuralCompleteness(s *Structural, fldPath *field.Path) field.ErrorList {
if s == nil {
return nil
}
return validateValueValidationCompleteness(s.ValueValidation, s, fldPath, fldPath)
}
func validateValueValidationCompleteness(v *ValueValidation, s *Structural, sPath, vPath *field.Path) field.ErrorList {
if v == nil {
return nil
}
if s == nil {
return field.ErrorList{field.Required(sPath, fmt.Sprintf("because it is defined in %s", vPath.String()))}
}
allErrs := field.ErrorList{}
allErrs = append(allErrs, validateNestedValueValidationCompleteness(v.Not, s, sPath, vPath.Child("not"))...)
for i := range v.AllOf {
allErrs = append(allErrs, validateNestedValueValidationCompleteness(&v.AllOf[i], s, sPath, vPath.Child("allOf").Index(i))...)
}
for i := range v.AnyOf {
allErrs = append(allErrs, validateNestedValueValidationCompleteness(&v.AnyOf[i], s, sPath, vPath.Child("anyOf").Index(i))...)
}
for i := range v.OneOf {
allErrs = append(allErrs, validateNestedValueValidationCompleteness(&v.OneOf[i], s, sPath, vPath.Child("oneOf").Index(i))...)
}
return allErrs
}
func validateNestedValueValidationCompleteness(v *NestedValueValidation, s *Structural, sPath, vPath *field.Path) field.ErrorList {
if v == nil {
return nil
}
if s == nil {
return field.ErrorList{field.Required(sPath, fmt.Sprintf("because it is defined in %s", vPath.String()))}
}
allErrs := field.ErrorList{}
allErrs = append(allErrs, validateValueValidationCompleteness(&v.ValueValidation, s, sPath, vPath)...)
allErrs = append(allErrs, validateNestedValueValidationCompleteness(v.Items, s.Items, sPath.Child("items"), vPath.Child("items"))...)
for k, vFld := range v.Properties {
if sFld, ok := s.Properties[k]; !ok {
allErrs = append(allErrs, field.Required(sPath.Child("properties").Key(k), fmt.Sprintf("because it is defined in %s", vPath.Child("properties").Key(k))))
} else {
allErrs = append(allErrs, validateNestedValueValidationCompleteness(&vFld, &sFld, sPath.Child("properties").Key(k), vPath.Child("properties").Key(k))...)
}
}
// don't check additionalProperties as this is not allowed (and checked during validation)
return allErrs
}

View File

@ -0,0 +1,276 @@
/*
Copyright 2019 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 schema
import (
"fmt"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
)
// NewStructural converts an OpenAPI v3 schema into a structural schema. A pre-validated JSONSchemaProps will
// not fail on NewStructural. This means that we require that:
//
// - items is not an array of schemas
// - the following fields are not set:
// - id
// - schema
// - $ref
// - patternProperties
// - dependencies
// - additionalItems
// - definitions.
//
// The follow fields are not preserved:
// - externalDocs
// - example.
func NewStructural(s *apiextensions.JSONSchemaProps) (*Structural, error) {
if s == nil {
return nil, nil
}
if err := validateUnsupportedFields(s); err != nil {
return nil, err
}
vv, err := newValueValidation(s)
if err != nil {
return nil, err
}
g, err := newGenerics(s)
if err != nil {
return nil, err
}
x, err := newExtensions(s)
if err != nil {
return nil, err
}
ss := &Structural{
Generic: *g,
Extensions: *x,
ValueValidation: vv,
}
if s.Items != nil {
if len(s.Items.JSONSchemas) > 0 {
// we validate that it is not an array
return nil, fmt.Errorf("OpenAPIV3Schema 'items' must be a schema, but is an array")
}
item, err := NewStructural(s.Items.Schema)
if err != nil {
return nil, err
}
ss.Items = item
}
if len(s.Properties) > 0 {
ss.Properties = make(map[string]Structural, len(s.Properties))
for k, x := range s.Properties {
fld, err := NewStructural(&x)
if err != nil {
return nil, err
}
ss.Properties[k] = *fld
}
}
return ss, nil
}
func newGenerics(s *apiextensions.JSONSchemaProps) (*Generic, error) {
if s == nil {
return nil, nil
}
g := &Generic{
Type: s.Type,
Description: s.Description,
Title: s.Title,
Nullable: s.Nullable,
}
if s.Default != nil {
g.Default = JSON{interface{}(*s.Default)}
}
if s.AdditionalProperties != nil {
if s.AdditionalProperties.Schema != nil {
ss, err := NewStructural(s.AdditionalProperties.Schema)
if err != nil {
return nil, err
}
g.AdditionalProperties = &StructuralOrBool{Structural: ss}
} else {
g.AdditionalProperties = &StructuralOrBool{Bool: s.AdditionalProperties.Allows}
}
}
return g, nil
}
func newValueValidation(s *apiextensions.JSONSchemaProps) (*ValueValidation, error) {
if s == nil {
return nil, nil
}
not, err := newNestedValueValidation(s.Not)
if err != nil {
return nil, err
}
v := &ValueValidation{
Format: s.Format,
Maximum: s.Maximum,
ExclusiveMaximum: s.ExclusiveMaximum,
Minimum: s.Minimum,
ExclusiveMinimum: s.ExclusiveMinimum,
MaxLength: s.MaxLength,
MinLength: s.MinLength,
Pattern: s.Pattern,
MaxItems: s.MaxItems,
MinItems: s.MinItems,
UniqueItems: s.UniqueItems,
MultipleOf: s.MultipleOf,
MaxProperties: s.MaxProperties,
MinProperties: s.MinProperties,
Required: s.Required,
Not: not,
}
for _, e := range s.Enum {
v.Enum = append(v.Enum, JSON{e})
}
for _, x := range s.AllOf {
clause, err := newNestedValueValidation(&x)
if err != nil {
return nil, err
}
v.AllOf = append(v.AllOf, *clause)
}
for _, x := range s.AnyOf {
clause, err := newNestedValueValidation(&x)
if err != nil {
return nil, err
}
v.AnyOf = append(v.AnyOf, *clause)
}
for _, x := range s.OneOf {
clause, err := newNestedValueValidation(&x)
if err != nil {
return nil, err
}
v.OneOf = append(v.OneOf, *clause)
}
return v, nil
}
func newNestedValueValidation(s *apiextensions.JSONSchemaProps) (*NestedValueValidation, error) {
if s == nil {
return nil, nil
}
if err := validateUnsupportedFields(s); err != nil {
return nil, err
}
vv, err := newValueValidation(s)
if err != nil {
return nil, err
}
g, err := newGenerics(s)
if err != nil {
return nil, err
}
x, err := newExtensions(s)
if err != nil {
return nil, err
}
v := &NestedValueValidation{
ValueValidation: *vv,
ForbiddenGenerics: *g,
ForbiddenExtensions: *x,
}
if s.Items != nil {
if len(s.Items.JSONSchemas) > 0 {
// we validate that it is not an array
return nil, fmt.Errorf("OpenAPIV3Schema 'items' must be a schema, but is an array")
}
nvv, err := newNestedValueValidation(s.Items.Schema)
if err != nil {
return nil, err
}
v.Items = nvv
}
if s.Properties != nil {
v.Properties = make(map[string]NestedValueValidation, len(s.Properties))
for k, x := range s.Properties {
nvv, err := newNestedValueValidation(&x)
if err != nil {
return nil, err
}
v.Properties[k] = *nvv
}
}
return v, nil
}
func newExtensions(s *apiextensions.JSONSchemaProps) (*Extensions, error) {
if s == nil {
return nil, nil
}
return &Extensions{
XPreserveUnknownFields: s.XPreserveUnknownFields,
XEmbeddedResource: s.XEmbeddedResource,
XIntOrString: s.XIntOrString,
}, nil
}
// validateUnsupportedFields checks that those fields rejected by validation are actually unset.
func validateUnsupportedFields(s *apiextensions.JSONSchemaProps) error {
if len(s.ID) > 0 {
return fmt.Errorf("OpenAPIV3Schema 'id' is not supported")
}
if len(s.Schema) > 0 {
return fmt.Errorf("OpenAPIV3Schema 'schema' is not supported")
}
if s.Ref != nil && len(*s.Ref) > 0 {
return fmt.Errorf("OpenAPIV3Schema '$ref' is not supported")
}
if len(s.PatternProperties) > 0 {
return fmt.Errorf("OpenAPIV3Schema 'patternProperties' is not supported")
}
if len(s.Dependencies) > 0 {
return fmt.Errorf("OpenAPIV3Schema 'dependencies' is not supported")
}
if s.AdditionalItems != nil {
return fmt.Errorf("OpenAPIV3Schema 'additionalItems' is not supported")
}
if len(s.Definitions) > 0 {
return fmt.Errorf("OpenAPIV3Schema 'definitions' is not supported")
}
return nil
}

View File

@ -0,0 +1,160 @@
/*
Copyright 2019 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 schema
import (
"k8s.io/apimachinery/pkg/runtime"
)
// +k8s:deepcopy-gen=true
// Structural represents a structural schema.
type Structural struct {
Items *Structural
Properties map[string]Structural
Generic
Extensions
*ValueValidation
}
// +k8s:deepcopy-gen=true
// StructuralOrBool is either a structural schema or a boolean.
type StructuralOrBool struct {
Structural *Structural
Bool bool
}
// +k8s:deepcopy-gen=true
// Generic contains the generic schema fields not allowed in value validation.
type Generic struct {
Description string
// type specifies the type of a value.
// It can be object, array, number, integer, boolean, string.
// It is optional only if x-kubernetes-preserve-unknown-fields
// or x-kubernetes-int-or-string is true.
Type string
Title string
Default JSON
AdditionalProperties *StructuralOrBool
Nullable bool
}
// +k8s:deepcopy-gen=true
// Extensions contains the Kubernetes OpenAPI v3 vendor extensions.
type Extensions struct {
// x-kubernetes-preserve-unknown-fields stops the API server
// decoding step from pruning fields which are not specified
// in the validation schema. This affects fields recursively,
// but switches back to normal pruning behaviour if nested
// properties or additionalProperties are specified in the schema.
XPreserveUnknownFields bool
// x-kubernetes-embedded-resource defines that the value is an
// embedded Kubernetes runtime.Object, with TypeMeta and
// ObjectMeta. The type must be object. It is allowed to further
// restrict the embedded object. Both ObjectMeta and TypeMeta
// are validated automatically. x-kubernetes-preserve-unknown-fields
// must be true.
XEmbeddedResource bool
// x-kubernetes-int-or-string specifies that this value is
// either an integer or a string. If this is true, an empty
// type is allowed and type as child of anyOf is permitted
// if following one of the following patterns:
//
// 1) anyOf:
// - type: integer
// - type: string
// 2) allOf:
// - anyOf:
// - type: integer
// - type: string
// - ... zero or more
XIntOrString bool
}
// +k8s:deepcopy-gen=true
// ValueValidation contains all schema fields not contributing to the structure of the schema.
type ValueValidation struct {
Format string
Maximum *float64
ExclusiveMaximum bool
Minimum *float64
ExclusiveMinimum bool
MaxLength *int64
MinLength *int64
Pattern string
MaxItems *int64
MinItems *int64
UniqueItems bool
MultipleOf *float64
Enum []JSON
MaxProperties *int64
MinProperties *int64
Required []string
AllOf []NestedValueValidation
OneOf []NestedValueValidation
AnyOf []NestedValueValidation
Not *NestedValueValidation
}
// +k8s:deepcopy-gen=true
// NestedValueValidation contains value validations, items and properties usable when nested
// under a logical junctor, and catch all structs for generic and vendor extensions schema fields.
type NestedValueValidation struct {
ValueValidation
Items *NestedValueValidation
Properties map[string]NestedValueValidation
// Anything set in the following will make the scheme
// non-structural, with the exception of these two patterns if
// x-kubernetes-int-or-string is true:
//
// 1) anyOf:
// - type: integer
// - type: string
// 2) allOf:
// - anyOf:
// - type: integer
// - type: string
// - ... zero or more
ForbiddenGenerics Generic
ForbiddenExtensions Extensions
}
// JSON wraps an arbitrary JSON value to be able to implement deepcopy.
type JSON struct {
Object interface{}
}
// DeepCopy creates a deep copy of the wrapped JSON value.
func (j JSON) DeepCopy() JSON {
return JSON{runtime.DeepCopyJSONValue(j.Object)}
}
// DeepCopyInto creates a deep copy of the wrapped JSON value and stores it in into.
func (j JSON) DeepCopyInto(into *JSON) {
into.Object = runtime.DeepCopyJSONValue(j.Object)
}

View File

@ -0,0 +1,238 @@
/*
Copyright 2019 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 schema
import (
"reflect"
"k8s.io/apimachinery/pkg/util/validation/field"
)
var intOrStringAnyOf = []NestedValueValidation{
{ForbiddenGenerics: Generic{
Type: "integer",
}},
{ForbiddenGenerics: Generic{
Type: "string",
}},
}
type level int
const (
rootLevel level = iota
itemLevel
fieldLevel
)
// ValidateStructural checks that s is a structural schema with the invariants:
//
// * structurality: both `ForbiddenGenerics` and `ForbiddenExtensions` only have zero values, with the two exceptions for IntOrString.
// * RawExtension: for every schema with `x-kubernetes-embedded-resource: true`, `x-kubernetes-preserve-unknown-fields: true` and `type: object` are set
// * IntOrString: for `x-kubernetes-int-or-string: true` either `type` is empty under `anyOf` and `allOf` or the schema structure is one of these:
//
// 1) anyOf:
// - type: integer
// - type: string
// 2) allOf:
// - anyOf:
// - type: integer
// - type: string
// - ... zero or more
//
// * every specified field or array in s is also specified outside of value validation.
func ValidateStructural(s *Structural, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, validateStructuralInvariants(s, rootLevel, fldPath)...)
allErrs = append(allErrs, validateStructuralCompleteness(s, fldPath)...)
return allErrs
}
// validateStructuralInvariants checks the invariants of a structural schema.
func validateStructuralInvariants(s *Structural, lvl level, fldPath *field.Path) field.ErrorList {
if s == nil {
return nil
}
allErrs := field.ErrorList{}
allErrs = append(allErrs, validateStructuralInvariants(s.Items, itemLevel, fldPath.Child("items"))...)
for k, v := range s.Properties {
allErrs = append(allErrs, validateStructuralInvariants(&v, fieldLevel, fldPath.Child("properties").Key(k))...)
}
allErrs = append(allErrs, validateGeneric(&s.Generic, fldPath)...)
allErrs = append(allErrs, validateExtensions(&s.Extensions, fldPath)...)
// detect the two IntOrString exceptions:
// 1) anyOf:
// - type: integer
// - type: string
// 2) allOf:
// - anyOf:
// - type: integer
// - type: string
// - ... zero or more
skipAnyOf := false
skipFirstAllOfAnyOf := false
if s.XIntOrString && s.ValueValidation != nil {
if len(s.ValueValidation.AnyOf) == 2 && reflect.DeepEqual(s.ValueValidation.AnyOf, intOrStringAnyOf) {
skipAnyOf = true
} else if len(s.ValueValidation.AllOf) >= 1 && len(s.ValueValidation.AllOf[0].AnyOf) == 2 && reflect.DeepEqual(s.ValueValidation.AllOf[0].AnyOf, intOrStringAnyOf) {
skipFirstAllOfAnyOf = true
}
}
allErrs = append(allErrs, validateValueValidation(s.ValueValidation, skipAnyOf, skipFirstAllOfAnyOf, fldPath)...)
if s.XEmbeddedResource && s.Type != "object" {
if len(s.Type) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must be object if x-kubernetes-embedded-resource is true"))
} else {
allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), s.Type, "must be object if x-kubernetes-embedded-resource is true"))
}
} else if len(s.Type) == 0 && !s.Extensions.XIntOrString && !s.Extensions.XPreserveUnknownFields {
switch lvl {
case rootLevel:
allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must not be empty at the root"))
case itemLevel:
allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must not be empty for specified array items"))
case fieldLevel:
allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must not be empty for specified object fields"))
}
}
if lvl == rootLevel && len(s.Type) > 0 && s.Type != "object" {
allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), s.Type, "must be object at the root"))
}
if s.XEmbeddedResource && !s.XPreserveUnknownFields && s.Properties == nil {
allErrs = append(allErrs, field.Required(fldPath.Child("properties"), "must not be empty if x-kubernetes-embedded-resource is true without x-kubernetes-preserve-unknown-fields"))
}
return allErrs
}
// validateGeneric checks the generic fields of a structural schema.
func validateGeneric(g *Generic, fldPath *field.Path) field.ErrorList {
if g == nil {
return nil
}
allErrs := field.ErrorList{}
if g.AdditionalProperties != nil {
if g.AdditionalProperties.Structural != nil {
allErrs = append(allErrs, validateStructuralInvariants(g.AdditionalProperties.Structural, fieldLevel, fldPath.Child("additionalProperties"))...)
}
}
return allErrs
}
// validateExtensions checks Kubernetes vendor extensions of a structural schema.
func validateExtensions(x *Extensions, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if x.XIntOrString && x.XPreserveUnknownFields {
allErrs = append(allErrs, field.Invalid(fldPath.Child("x-kubernetes-preserve-unknown-fields"), x.XPreserveUnknownFields, "must be false if x-kubernetes-int-or-string is true"))
}
if x.XIntOrString && x.XEmbeddedResource {
allErrs = append(allErrs, field.Invalid(fldPath.Child("x-kubernetes-embedded-resource"), x.XEmbeddedResource, "must be false if x-kubernetes-int-or-string is true"))
}
return allErrs
}
// validateValueValidation checks the value validation in a structural schema.
func validateValueValidation(v *ValueValidation, skipAnyOf, skipFirstAllOfAnyOf bool, fldPath *field.Path) field.ErrorList {
if v == nil {
return nil
}
allErrs := field.ErrorList{}
if !skipAnyOf {
for i := range v.AnyOf {
allErrs = append(allErrs, validateNestedValueValidation(&v.AnyOf[i], false, false, fldPath.Child("anyOf").Index(i))...)
}
}
for i := range v.AllOf {
skipAnyOf := false
if skipFirstAllOfAnyOf && i == 0 {
skipAnyOf = true
}
allErrs = append(allErrs, validateNestedValueValidation(&v.AllOf[i], skipAnyOf, false, fldPath.Child("allOf").Index(i))...)
}
for i := range v.OneOf {
allErrs = append(allErrs, validateNestedValueValidation(&v.OneOf[i], false, false, fldPath.Child("oneOf").Index(i))...)
}
allErrs = append(allErrs, validateNestedValueValidation(v.Not, false, false, fldPath.Child("not"))...)
return allErrs
}
// validateNestedValueValidation checks the nested value validation under a logic junctor in a structural schema.
func validateNestedValueValidation(v *NestedValueValidation, skipAnyOf, skipAllOfAnyOf bool, fldPath *field.Path) field.ErrorList {
if v == nil {
return nil
}
allErrs := field.ErrorList{}
allErrs = append(allErrs, validateValueValidation(&v.ValueValidation, skipAnyOf, skipAllOfAnyOf, fldPath)...)
allErrs = append(allErrs, validateNestedValueValidation(v.Items, false, false, fldPath.Child("items"))...)
for k, fld := range v.Properties {
allErrs = append(allErrs, validateNestedValueValidation(&fld, false, false, fldPath.Child("properties").Key(k))...)
}
if len(v.ForbiddenGenerics.Type) > 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("type"), "must be empty to be structural"))
}
if v.ForbiddenGenerics.AdditionalProperties != nil {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("additionalProperties"), "must be undefined to be structural"))
}
if v.ForbiddenGenerics.Default.Object != nil {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("default"), "must be undefined to be structural"))
}
if len(v.ForbiddenGenerics.Title) > 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("title"), "must be empty to be structural"))
}
if len(v.ForbiddenGenerics.Description) > 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("description"), "must be empty to be structural"))
}
if v.ForbiddenGenerics.Nullable {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("nullable"), "must be false to be structural"))
}
if v.ForbiddenExtensions.XPreserveUnknownFields {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-preserve-unknown-fields"), "must be false to be structural"))
}
if v.ForbiddenExtensions.XEmbeddedResource {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-embedded-resource"), "must be false to be structural"))
}
if v.ForbiddenExtensions.XIntOrString {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-int-or-string"), "must be false to be structural"))
}
return allErrs
}

View File

@ -0,0 +1,71 @@
/*
Copyright 2019 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 schema
import (
"reflect"
"testing"
fuzz "github.com/google/gofuzz"
"k8s.io/apimachinery/pkg/util/rand"
)
func TestValidateNestedValueValidationComplete(t *testing.T) {
fuzzer := fuzz.New()
fuzzer.Funcs(
func(s *JSON, c fuzz.Continue) {
if c.RandBool() {
s.Object = float64(42.0)
}
},
func(s **StructuralOrBool, c fuzz.Continue) {
if c.RandBool() {
*s = &StructuralOrBool{}
}
},
)
fuzzer.NilChance(0)
// check that we didn't forget to check any forbidden generic field
tt := reflect.TypeOf(Generic{})
for i := 0; i < tt.NumField(); i++ {
vv := &NestedValueValidation{}
x := reflect.ValueOf(&vv.ForbiddenGenerics).Elem()
i := rand.Intn(x.NumField())
fuzzer.Fuzz(x.Field(i).Addr().Interface())
errs := validateNestedValueValidation(vv, false, false, nil)
if len(errs) == 0 && !reflect.DeepEqual(vv.ForbiddenGenerics, Generic{}) {
t.Errorf("expected ForbiddenGenerics validation errors for: %#v", vv)
}
}
// check that we didn't forget to check any forbidden extension field
tt = reflect.TypeOf(Extensions{})
for i := 0; i < tt.NumField(); i++ {
vv := &NestedValueValidation{}
x := reflect.ValueOf(&vv.ForbiddenExtensions).Elem()
i := rand.Intn(x.NumField())
fuzzer.Fuzz(x.Field(i).Addr().Interface())
errs := validateNestedValueValidation(vv, false, false, nil)
if len(errs) == 0 && !reflect.DeepEqual(vv.ForbiddenExtensions, Extensions{}) {
t.Errorf("expected ForbiddenExtensions validation errors for: %#v", vv)
}
}
}