Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

validate: Expose hooks to inject custom behavior during traversal #406

Merged
merged 6 commits into from
Jul 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions pkg/validation/validate/object_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func (o *objectValidator) Validate(data interface{}) *Result {
// Cases: properties which are not regular properties and have not been matched by the PatternProperties validator
if o.AdditionalProperties != nil && o.AdditionalProperties.Schema != nil {
// AdditionalProperties as Schema
res.Merge(NewSchemaValidator(o.AdditionalProperties.Schema, o.Root, o.Path+"."+key, o.KnownFormats, o.Options.Options()...).Validate(value))
res.Merge(o.Options.NewValidatorForField(key, o.AdditionalProperties.Schema, o.Root, o.Path+"."+key, o.KnownFormats, o.Options.Options()...).Validate(value))
} else if regularProperty && !(matched || succeededOnce) {
// TODO: this is dead code since regularProperty=false here
res.AddErrors(errors.FailedAllPatternProperties(o.Path, o.In, key))
Expand All @@ -121,7 +121,7 @@ func (o *objectValidator) Validate(data interface{}) *Result {

// Recursively validates each property against its schema
if v, ok := val[pName]; ok {
r := NewSchemaValidator(&pSchema, o.Root, rName, o.KnownFormats, o.Options.Options()...).Validate(v)
r := o.Options.NewValidatorForField(pName, &pSchema, o.Root, rName, o.KnownFormats, o.Options.Options()...).Validate(v)
res.Merge(r)
}
}
Expand All @@ -144,7 +144,7 @@ func (o *objectValidator) Validate(data interface{}) *Result {
if !regularProperty && (matched /*|| succeededOnce*/) {
for _, pName := range patterns {
if v, ok := o.PatternProperties[pName]; ok {
res.Merge(NewSchemaValidator(&v, o.Root, o.Path+"."+key, o.KnownFormats, o.Options.Options()...).Validate(value))
res.Merge(o.Options.NewValidatorForField(key, &v, o.Root, o.Path+"."+key, o.KnownFormats, o.Options.Options()...).Validate(value))
}
}
}
Expand All @@ -163,7 +163,7 @@ func (o *objectValidator) validatePatternProperty(key string, value interface{},
if match, _ := regexp.MatchString(k, key); match {
patterns = append(patterns, k)
matched = true
validator := NewSchemaValidator(&sch, o.Root, o.Path+"."+key, o.KnownFormats, o.Options.Options()...)
validator := o.Options.NewValidatorForField(key, &sch, o.Root, o.Path+"."+key, o.KnownFormats, o.Options.Options()...)

res := validator.Validate(value)
result.Merge(res)
Expand Down
4 changes: 2 additions & 2 deletions pkg/validation/validate/object_validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ func itemsFixture() map[string]interface{} {
}
}

func expectAllValid(t *testing.T, ov valueValidator, dataValid, dataInvalid map[string]interface{}) {
func expectAllValid(t *testing.T, ov ValueValidator, dataValid, dataInvalid map[string]interface{}) {
res := ov.Validate(dataValid)
assert.Equal(t, 0, len(res.Errors))

res = ov.Validate(dataInvalid)
assert.Equal(t, 0, len(res.Errors))
}

func expectOnlyInvalid(t *testing.T, ov valueValidator, dataValid, dataInvalid map[string]interface{}) {
func expectOnlyInvalid(t *testing.T, ov ValueValidator, dataValid, dataInvalid map[string]interface{}) {
res := ov.Validate(dataValid)
assert.Equal(t, 0, len(res.Errors))

Expand Down
36 changes: 26 additions & 10 deletions pkg/validation/validate/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type SchemaValidator struct {
Path string
in string
Schema *spec.Schema
validators []valueValidator
validators []ValueValidator
Root interface{}
KnownFormats strfmt.Registry
Options SchemaValidatorOptions
Expand Down Expand Up @@ -78,7 +78,15 @@ func NewSchemaValidator(schema *spec.Schema, rootSchema interface{}, root string
for _, o := range options {
o(&s.Options)
}
s.validators = []valueValidator{

if s.Options.NewValidatorForIndex == nil {
s.Options.NewValidatorForIndex = s.NewValidatorForIndex
}
if s.Options.NewValidatorForField == nil {
s.Options.NewValidatorForField = s.NewValidatorForField
}

s.validators = []ValueValidator{
s.typeValidator(),
s.schemaPropsValidator(),
s.stringValidator(),
Expand All @@ -91,6 +99,14 @@ func NewSchemaValidator(schema *spec.Schema, rootSchema interface{}, root string
return &s
}

func (s *SchemaValidator) NewValidatorForField(field string, schema *spec.Schema, rootSchema interface{}, root string, formats strfmt.Registry, opts ...Option) ValueValidator {
return NewSchemaValidator(schema, rootSchema, root, formats, opts...)
}

func (s *SchemaValidator) NewValidatorForIndex(index int, schema *spec.Schema, rootSchema interface{}, root string, formats strfmt.Registry, opts ...Option) ValueValidator {
return NewSchemaValidator(schema, rootSchema, root, formats, opts...)
}

// SetPath sets the path for this schema validator
func (s *SchemaValidator) SetPath(path string) {
s.Path = path
Expand Down Expand Up @@ -174,19 +190,19 @@ func (s *SchemaValidator) Validate(data interface{}) *Result {
return result
}

func (s *SchemaValidator) typeValidator() valueValidator {
func (s *SchemaValidator) typeValidator() ValueValidator {
return &typeValidator{Type: s.Schema.Type, Nullable: s.Schema.Nullable, Format: s.Schema.Format, In: s.in, Path: s.Path}
}

func (s *SchemaValidator) commonValidator() valueValidator {
func (s *SchemaValidator) commonValidator() ValueValidator {
return &basicCommonValidator{
Path: s.Path,
In: s.in,
Enum: s.Schema.Enum,
}
}

func (s *SchemaValidator) sliceValidator() valueValidator {
func (s *SchemaValidator) sliceValidator() ValueValidator {
return &schemaSliceValidator{
Path: s.Path,
In: s.in,
Expand All @@ -201,7 +217,7 @@ func (s *SchemaValidator) sliceValidator() valueValidator {
}
}

func (s *SchemaValidator) numberValidator() valueValidator {
func (s *SchemaValidator) numberValidator() ValueValidator {
return &numberValidator{
Path: s.Path,
In: s.in,
Expand All @@ -214,7 +230,7 @@ func (s *SchemaValidator) numberValidator() valueValidator {
}
}

func (s *SchemaValidator) stringValidator() valueValidator {
func (s *SchemaValidator) stringValidator() ValueValidator {
return &stringValidator{
Path: s.Path,
In: s.in,
Expand All @@ -224,7 +240,7 @@ func (s *SchemaValidator) stringValidator() valueValidator {
}
}

func (s *SchemaValidator) formatValidator() valueValidator {
func (s *SchemaValidator) formatValidator() ValueValidator {
return &formatValidator{
Path: s.Path,
In: s.in,
Expand All @@ -233,12 +249,12 @@ func (s *SchemaValidator) formatValidator() valueValidator {
}
}

func (s *SchemaValidator) schemaPropsValidator() valueValidator {
func (s *SchemaValidator) schemaPropsValidator() ValueValidator {
sch := s.Schema
return newSchemaPropsValidator(s.Path, s.in, sch.AllOf, sch.OneOf, sch.AnyOf, sch.Not, sch.Dependencies, s.Root, s.KnownFormats, s.Options.Options()...)
}

func (s *SchemaValidator) objectValidator() valueValidator {
func (s *SchemaValidator) objectValidator() ValueValidator {
return &objectValidator{
Path: s.Path,
In: s.in,
Expand Down
7 changes: 7 additions & 0 deletions pkg/validation/validate/schema_option.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,16 @@

package validate

import (
"k8s.io/kube-openapi/pkg/validation/spec"
"k8s.io/kube-openapi/pkg/validation/strfmt"
)

// SchemaValidatorOptions defines optional rules for schema validation
type SchemaValidatorOptions struct {
validationRulesEnabled bool
NewValidatorForIndex func(index int, schema *spec.Schema, rootSchema interface{}, root string, formats strfmt.Registry, opts ...Option) ValueValidator
NewValidatorForField func(field string, schema *spec.Schema, rootSchema interface{}, root string, formats strfmt.Registry, opts ...Option) ValueValidator
}

// Option sets optional rules for schema validation
Expand Down
7 changes: 3 additions & 4 deletions pkg/validation/validate/slice_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,8 @@ func (s *schemaSliceValidator) Validate(data interface{}) *Result {
size := val.Len()

if s.Items != nil && s.Items.Schema != nil {
validator := NewSchemaValidator(s.Items.Schema, s.Root, s.Path, s.KnownFormats, s.Options.Options()...)
for i := 0; i < size; i++ {
validator.SetPath(fmt.Sprintf("%s[%d]", s.Path, i))
validator := s.Options.NewValidatorForIndex(i, s.Items.Schema, s.Root, fmt.Sprintf("%s[%d]", s.Path, i), s.KnownFormats, s.Options.Options()...)
value := val.Index(i)
result.Merge(validator.Validate(value.Interface()))
}
Expand All @@ -66,7 +65,7 @@ func (s *schemaSliceValidator) Validate(data interface{}) *Result {
if s.Items != nil && len(s.Items.Schemas) > 0 {
itemsSize = len(s.Items.Schemas)
for i := 0; i < itemsSize; i++ {
validator := NewSchemaValidator(&s.Items.Schemas[i], s.Root, fmt.Sprintf("%s[%d]", s.Path, i), s.KnownFormats, s.Options.Options()...)
validator := s.Options.NewValidatorForIndex(i, &s.Items.Schemas[i], s.Root, fmt.Sprintf("%s[%d]", s.Path, i), s.KnownFormats, s.Options.Options()...)
if val.Len() <= i {
break
}
Expand All @@ -79,7 +78,7 @@ func (s *schemaSliceValidator) Validate(data interface{}) *Result {
}
if s.AdditionalItems.Schema != nil {
for i := itemsSize; i < size-itemsSize+1; i++ {
validator := NewSchemaValidator(s.AdditionalItems.Schema, s.Root, fmt.Sprintf("%s[%d]", s.Path, i), s.KnownFormats, s.Options.Options()...)
validator := s.Options.NewValidatorForIndex(i, s.AdditionalItems.Schema, s.Root, fmt.Sprintf("%s[%d]", s.Path, i), s.KnownFormats, s.Options.Options()...)
result.Merge(validator.Validate(val.Index(i).Interface()))
}
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/validation/validate/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import (
"k8s.io/kube-openapi/pkg/validation/spec"
)

// valueValidator validates the values it applies to.
type valueValidator interface {
// ValueValidator validates the values it applies to.
type ValueValidator interface {
// SetPath sets the exact path of the validator prior to calling Validate.
// The exact path contains the map keys and array indices to locate the
// value to be validated from the root data element.
Expand Down