k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/validation/validate/schema_props.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  	"fmt"
    19  	"reflect"
    20  
    21  	"k8s.io/kube-openapi/pkg/validation/spec"
    22  	"k8s.io/kube-openapi/pkg/validation/strfmt"
    23  )
    24  
    25  type schemaPropsValidator struct {
    26  	Path            string
    27  	In              string
    28  	AllOf           []spec.Schema
    29  	OneOf           []spec.Schema
    30  	AnyOf           []spec.Schema
    31  	Not             *spec.Schema
    32  	Dependencies    spec.Dependencies
    33  	anyOfValidators []SchemaValidator
    34  	allOfValidators []SchemaValidator
    35  	oneOfValidators []SchemaValidator
    36  	notValidator    *SchemaValidator
    37  	Root            interface{}
    38  	KnownFormats    strfmt.Registry
    39  	Options         SchemaValidatorOptions
    40  }
    41  
    42  func (s *schemaPropsValidator) SetPath(path string) {
    43  	s.Path = path
    44  	for _, v := range s.anyOfValidators {
    45  		v.SetPath(path)
    46  	}
    47  	for _, v := range s.allOfValidators {
    48  		v.SetPath(path)
    49  	}
    50  	for _, v := range s.oneOfValidators {
    51  		v.SetPath(path)
    52  	}
    53  	if s.notValidator != nil {
    54  		s.notValidator.SetPath(path)
    55  	}
    56  }
    57  
    58  func newSchemaPropsValidator(path string, in string, allOf, oneOf, anyOf []spec.Schema, not *spec.Schema, deps spec.Dependencies, root interface{}, formats strfmt.Registry, options ...Option) *schemaPropsValidator {
    59  	var anyValidators []SchemaValidator
    60  	for _, v := range anyOf {
    61  		v := v
    62  		anyValidators = append(anyValidators, *NewSchemaValidator(&v, root, path, formats, options...))
    63  	}
    64  	var allValidators []SchemaValidator
    65  	for _, v := range allOf {
    66  		v := v
    67  		allValidators = append(allValidators, *NewSchemaValidator(&v, root, path, formats, options...))
    68  	}
    69  	var oneValidators []SchemaValidator
    70  	for _, v := range oneOf {
    71  		v := v
    72  		oneValidators = append(oneValidators, *NewSchemaValidator(&v, root, path, formats, options...))
    73  	}
    74  
    75  	var notValidator *SchemaValidator
    76  	if not != nil {
    77  		notValidator = NewSchemaValidator(not, root, path, formats, options...)
    78  	}
    79  
    80  	schOptions := &SchemaValidatorOptions{}
    81  	for _, o := range options {
    82  		o(schOptions)
    83  	}
    84  	return &schemaPropsValidator{
    85  		Path:            path,
    86  		In:              in,
    87  		AllOf:           allOf,
    88  		OneOf:           oneOf,
    89  		AnyOf:           anyOf,
    90  		Not:             not,
    91  		Dependencies:    deps,
    92  		anyOfValidators: anyValidators,
    93  		allOfValidators: allValidators,
    94  		oneOfValidators: oneValidators,
    95  		notValidator:    notValidator,
    96  		Root:            root,
    97  		KnownFormats:    formats,
    98  		Options:         *schOptions,
    99  	}
   100  }
   101  
   102  func (s *schemaPropsValidator) Applies(source interface{}, kind reflect.Kind) bool {
   103  	r := reflect.TypeOf(source) == specSchemaType
   104  	debugLog("schema props validator for %q applies %t for %T (kind: %v)\n", s.Path, r, source, kind)
   105  	return r
   106  }
   107  
   108  func (s *schemaPropsValidator) Validate(data interface{}) *Result {
   109  	mainResult := new(Result)
   110  
   111  	// Intermediary error results
   112  
   113  	// IMPORTANT! messages from underlying validators
   114  	keepResultAnyOf := new(Result)
   115  	keepResultOneOf := new(Result)
   116  	keepResultAllOf := new(Result)
   117  
   118  	// Validates at least one in anyOf schemas
   119  	var firstSuccess *Result
   120  	if len(s.anyOfValidators) > 0 {
   121  		var bestFailures *Result
   122  		succeededOnce := false
   123  		for _, anyOfSchema := range s.anyOfValidators {
   124  			result := anyOfSchema.Validate(data)
   125  			// We keep inner IMPORTANT! errors no matter what MatchCount tells us
   126  			keepResultAnyOf.Merge(result.keepRelevantErrors())
   127  			if result.IsValid() {
   128  				bestFailures = nil
   129  				succeededOnce = true
   130  				if firstSuccess == nil {
   131  					firstSuccess = result
   132  				}
   133  				keepResultAnyOf = new(Result)
   134  				break
   135  			}
   136  			// MatchCount is used to select errors from the schema with most positive checks
   137  			if bestFailures == nil || result.MatchCount > bestFailures.MatchCount {
   138  				bestFailures = result
   139  			}
   140  		}
   141  
   142  		if !succeededOnce {
   143  			mainResult.AddErrors(mustValidateAtLeastOneSchemaMsg(s.Path))
   144  		}
   145  		if bestFailures != nil {
   146  			mainResult.Merge(bestFailures)
   147  		} else if firstSuccess != nil {
   148  			mainResult.Merge(firstSuccess)
   149  		}
   150  	}
   151  
   152  	// Validates exactly one in oneOf schemas
   153  	if len(s.oneOfValidators) > 0 {
   154  		var bestFailures *Result
   155  		var firstSuccess *Result
   156  		validated := 0
   157  
   158  		for _, oneOfSchema := range s.oneOfValidators {
   159  			result := oneOfSchema.Validate(data)
   160  			// We keep inner IMPORTANT! errors no matter what MatchCount tells us
   161  			keepResultOneOf.Merge(result.keepRelevantErrors())
   162  			if result.IsValid() {
   163  				validated++
   164  				bestFailures = nil
   165  				if firstSuccess == nil {
   166  					firstSuccess = result
   167  				}
   168  				keepResultOneOf = new(Result)
   169  				continue
   170  			}
   171  			// MatchCount is used to select errors from the schema with most positive checks
   172  			if validated == 0 && (bestFailures == nil || result.MatchCount > bestFailures.MatchCount) {
   173  				bestFailures = result
   174  			}
   175  		}
   176  
   177  		if validated != 1 {
   178  			additionalMsg := ""
   179  			if validated == 0 {
   180  				additionalMsg = "Found none valid"
   181  			} else {
   182  				additionalMsg = fmt.Sprintf("Found %d valid alternatives", validated)
   183  			}
   184  
   185  			mainResult.AddErrors(mustValidateOnlyOneSchemaMsg(s.Path, additionalMsg))
   186  			if bestFailures != nil {
   187  				mainResult.Merge(bestFailures)
   188  			}
   189  		} else if firstSuccess != nil {
   190  			mainResult.Merge(firstSuccess)
   191  		}
   192  	}
   193  
   194  	// Validates all of allOf schemas
   195  	if len(s.allOfValidators) > 0 {
   196  		validated := 0
   197  
   198  		for _, allOfSchema := range s.allOfValidators {
   199  			result := allOfSchema.Validate(data)
   200  			// We keep inner IMPORTANT! errors no matter what MatchCount tells us
   201  			keepResultAllOf.Merge(result.keepRelevantErrors())
   202  			//keepResultAllOf.Merge(result)
   203  			if result.IsValid() {
   204  				validated++
   205  			}
   206  			mainResult.Merge(result)
   207  		}
   208  
   209  		if validated != len(s.allOfValidators) {
   210  			additionalMsg := ""
   211  			if validated == 0 {
   212  				additionalMsg = ". None validated"
   213  			}
   214  
   215  			mainResult.AddErrors(mustValidateAllSchemasMsg(s.Path, additionalMsg))
   216  		}
   217  	}
   218  
   219  	if s.notValidator != nil {
   220  		result := s.notValidator.Validate(data)
   221  		// We keep inner IMPORTANT! errors no matter what MatchCount tells us
   222  		if result.IsValid() {
   223  			mainResult.AddErrors(mustNotValidatechemaMsg(s.Path))
   224  		}
   225  	}
   226  
   227  	if s.Dependencies != nil && len(s.Dependencies) > 0 && reflect.TypeOf(data).Kind() == reflect.Map {
   228  		val := data.(map[string]interface{})
   229  		for key := range val {
   230  			if dep, ok := s.Dependencies[key]; ok {
   231  
   232  				if dep.Schema != nil {
   233  					mainResult.Merge(NewSchemaValidator(dep.Schema, s.Root, s.Path+"."+key, s.KnownFormats, s.Options.Options()...).Validate(data))
   234  					continue
   235  				}
   236  
   237  				if len(dep.Property) > 0 {
   238  					for _, depKey := range dep.Property {
   239  						if _, ok := val[depKey]; !ok {
   240  							mainResult.AddErrors(hasADependencyMsg(s.Path, depKey))
   241  						}
   242  					}
   243  				}
   244  			}
   245  		}
   246  	}
   247  
   248  	mainResult.Inc()
   249  	// In the end we retain best failures for schema validation
   250  	// plus, if any, composite errors which may explain special cases (tagged as IMPORTANT!).
   251  	return mainResult.Merge(keepResultAllOf, keepResultOneOf, keepResultAnyOf)
   252  }