k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/validation/validate/schema.go (about)

     1  // Copyright 2015 go-swagger maintainers
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package validate
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"reflect"
    21  
    22  	"github.com/go-openapi/swag"
    23  	"k8s.io/kube-openapi/pkg/validation/errors"
    24  	"k8s.io/kube-openapi/pkg/validation/spec"
    25  	"k8s.io/kube-openapi/pkg/validation/strfmt"
    26  )
    27  
    28  var (
    29  	specSchemaType = reflect.TypeOf(&spec.Schema{})
    30  	//specItemsType     = reflect.TypeOf(&spec.Items{})
    31  )
    32  
    33  // SchemaValidator validates data against a JSON schema
    34  type SchemaValidator struct {
    35  	Path         string
    36  	in           string
    37  	Schema       *spec.Schema
    38  	validators   []ValueValidator
    39  	Root         interface{}
    40  	KnownFormats strfmt.Registry
    41  	Options      SchemaValidatorOptions
    42  }
    43  
    44  // AgainstSchema validates the specified data against the provided schema, using a registry of supported formats.
    45  //
    46  // When no pre-parsed *spec.Schema structure is provided, it uses a JSON schema as default. See example.
    47  func AgainstSchema(schema *spec.Schema, data interface{}, formats strfmt.Registry, options ...Option) error {
    48  	res := NewSchemaValidator(schema, nil, "", formats, options...).Validate(data)
    49  	if res.HasErrors() {
    50  		return errors.CompositeValidationError(res.Errors...)
    51  	}
    52  	return nil
    53  }
    54  
    55  // NewSchemaValidator creates a new schema validator.
    56  //
    57  // Panics if the provided schema is invalid.
    58  func NewSchemaValidator(schema *spec.Schema, rootSchema interface{}, root string, formats strfmt.Registry, options ...Option) *SchemaValidator {
    59  	if schema == nil {
    60  		return nil
    61  	}
    62  
    63  	if rootSchema == nil {
    64  		rootSchema = schema
    65  	}
    66  
    67  	if ref := schema.Ref.String(); ref != "" {
    68  		panic(fmt.Sprintf("schema references not supported: %s", ref))
    69  	}
    70  
    71  	s := SchemaValidator{
    72  		Path:         root,
    73  		in:           "body",
    74  		Schema:       schema,
    75  		Root:         rootSchema,
    76  		KnownFormats: formats,
    77  		Options:      SchemaValidatorOptions{}}
    78  	for _, o := range options {
    79  		o(&s.Options)
    80  	}
    81  
    82  	if s.Options.NewValidatorForIndex == nil {
    83  		s.Options.NewValidatorForIndex = s.NewValidatorForIndex
    84  	}
    85  	if s.Options.NewValidatorForField == nil {
    86  		s.Options.NewValidatorForField = s.NewValidatorForField
    87  	}
    88  
    89  	s.validators = []ValueValidator{
    90  		s.typeValidator(),
    91  		s.schemaPropsValidator(),
    92  		s.stringValidator(),
    93  		s.formatValidator(),
    94  		s.numberValidator(),
    95  		s.sliceValidator(),
    96  		s.commonValidator(),
    97  		s.objectValidator(),
    98  	}
    99  	return &s
   100  }
   101  
   102  func (s *SchemaValidator) NewValidatorForField(field string, schema *spec.Schema, rootSchema interface{}, root string, formats strfmt.Registry, opts ...Option) ValueValidator {
   103  	return NewSchemaValidator(schema, rootSchema, root, formats, opts...)
   104  }
   105  
   106  func (s *SchemaValidator) NewValidatorForIndex(index int, schema *spec.Schema, rootSchema interface{}, root string, formats strfmt.Registry, opts ...Option) ValueValidator {
   107  	return NewSchemaValidator(schema, rootSchema, root, formats, opts...)
   108  }
   109  
   110  // SetPath sets the path for this schema validator
   111  func (s *SchemaValidator) SetPath(path string) {
   112  	s.Path = path
   113  	for _, v := range s.validators {
   114  		v.SetPath(path)
   115  	}
   116  }
   117  
   118  // Applies returns true when this schema validator applies
   119  func (s *SchemaValidator) Applies(source interface{}, kind reflect.Kind) bool {
   120  	_, ok := source.(*spec.Schema)
   121  	return ok
   122  }
   123  
   124  // Validate validates the data against the schema
   125  func (s *SchemaValidator) Validate(data interface{}) *Result {
   126  	result := new(Result)
   127  	if s == nil {
   128  		return result
   129  	}
   130  
   131  	if data == nil {
   132  		result.Merge(s.validators[0].Validate(data)) // type validator
   133  		result.Merge(s.validators[6].Validate(data)) // common validator
   134  		return result
   135  	}
   136  
   137  	tpe := reflect.TypeOf(data)
   138  	kind := tpe.Kind()
   139  	for kind == reflect.Ptr {
   140  		tpe = tpe.Elem()
   141  		kind = tpe.Kind()
   142  	}
   143  	d := data
   144  
   145  	if kind == reflect.Struct {
   146  		// NOTE: since reflect retrieves the true nature of types
   147  		// this means that all strfmt types passed here (e.g. strfmt.Datetime, etc..)
   148  		// are converted here to strings, and structs are systematically converted
   149  		// to map[string]interface{}.
   150  		d = swag.ToDynamicJSON(data)
   151  	}
   152  
   153  	// TODO: this part should be handed over to type validator
   154  	// Handle special case of json.Number data (number marshalled as string)
   155  	isnumber := s.Schema.Type.Contains(numberType) || s.Schema.Type.Contains(integerType)
   156  	if num, ok := data.(json.Number); ok && isnumber {
   157  		if s.Schema.Type.Contains(integerType) { // avoid lossy conversion
   158  			in, erri := num.Int64()
   159  			if erri != nil {
   160  				result.AddErrors(invalidTypeConversionMsg(s.Path, erri))
   161  				result.Inc()
   162  				return result
   163  			}
   164  			d = in
   165  		} else {
   166  			nf, errf := num.Float64()
   167  			if errf != nil {
   168  				result.AddErrors(invalidTypeConversionMsg(s.Path, errf))
   169  				result.Inc()
   170  				return result
   171  			}
   172  			d = nf
   173  		}
   174  
   175  		tpe = reflect.TypeOf(d)
   176  		kind = tpe.Kind()
   177  	}
   178  
   179  	for _, v := range s.validators {
   180  		if !v.Applies(s.Schema, kind) {
   181  			debugLog("%T does not apply for %v", v, kind)
   182  			continue
   183  		}
   184  
   185  		err := v.Validate(d)
   186  		result.Merge(err)
   187  		result.Inc()
   188  	}
   189  	result.Inc()
   190  	return result
   191  }
   192  
   193  func (s *SchemaValidator) typeValidator() ValueValidator {
   194  	return &typeValidator{Type: s.Schema.Type, Nullable: s.Schema.Nullable, Format: s.Schema.Format, In: s.in, Path: s.Path}
   195  }
   196  
   197  func (s *SchemaValidator) commonValidator() ValueValidator {
   198  	return &basicCommonValidator{
   199  		Path: s.Path,
   200  		In:   s.in,
   201  		Enum: s.Schema.Enum,
   202  	}
   203  }
   204  
   205  func (s *SchemaValidator) sliceValidator() ValueValidator {
   206  	return &schemaSliceValidator{
   207  		Path:            s.Path,
   208  		In:              s.in,
   209  		MaxItems:        s.Schema.MaxItems,
   210  		MinItems:        s.Schema.MinItems,
   211  		UniqueItems:     s.Schema.UniqueItems,
   212  		AdditionalItems: s.Schema.AdditionalItems,
   213  		Items:           s.Schema.Items,
   214  		Root:            s.Root,
   215  		KnownFormats:    s.KnownFormats,
   216  		Options:         s.Options,
   217  	}
   218  }
   219  
   220  func (s *SchemaValidator) numberValidator() ValueValidator {
   221  	return &numberValidator{
   222  		Path:             s.Path,
   223  		In:               s.in,
   224  		Default:          s.Schema.Default,
   225  		MultipleOf:       s.Schema.MultipleOf,
   226  		Maximum:          s.Schema.Maximum,
   227  		ExclusiveMaximum: s.Schema.ExclusiveMaximum,
   228  		Minimum:          s.Schema.Minimum,
   229  		ExclusiveMinimum: s.Schema.ExclusiveMinimum,
   230  	}
   231  }
   232  
   233  func (s *SchemaValidator) stringValidator() ValueValidator {
   234  	return &stringValidator{
   235  		Path:      s.Path,
   236  		In:        s.in,
   237  		MaxLength: s.Schema.MaxLength,
   238  		MinLength: s.Schema.MinLength,
   239  		Pattern:   s.Schema.Pattern,
   240  	}
   241  }
   242  
   243  func (s *SchemaValidator) formatValidator() ValueValidator {
   244  	return &formatValidator{
   245  		Path:         s.Path,
   246  		In:           s.in,
   247  		Format:       s.Schema.Format,
   248  		KnownFormats: s.KnownFormats,
   249  	}
   250  }
   251  
   252  func (s *SchemaValidator) schemaPropsValidator() ValueValidator {
   253  	sch := s.Schema
   254  	return newSchemaPropsValidator(s.Path, s.in, sch.AllOf, sch.OneOf, sch.AnyOf, sch.Not, sch.Dependencies, s.Root, s.KnownFormats, s.Options.Options()...)
   255  }
   256  
   257  func (s *SchemaValidator) objectValidator() ValueValidator {
   258  	return &objectValidator{
   259  		Path:                 s.Path,
   260  		In:                   s.in,
   261  		MaxProperties:        s.Schema.MaxProperties,
   262  		MinProperties:        s.Schema.MinProperties,
   263  		Required:             s.Schema.Required,
   264  		Properties:           s.Schema.Properties,
   265  		AdditionalProperties: s.Schema.AdditionalProperties,
   266  		PatternProperties:    s.Schema.PatternProperties,
   267  		Root:                 s.Root,
   268  		KnownFormats:         s.KnownFormats,
   269  		Options:              s.Options,
   270  	}
   271  }