k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/validation/validate/object_validator.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 "reflect" 19 "regexp" 20 21 "k8s.io/kube-openapi/pkg/validation/errors" 22 "k8s.io/kube-openapi/pkg/validation/spec" 23 "k8s.io/kube-openapi/pkg/validation/strfmt" 24 ) 25 26 type objectValidator struct { 27 Path string 28 In string 29 MaxProperties *int64 30 MinProperties *int64 31 Required []string 32 Properties map[string]spec.Schema 33 AdditionalProperties *spec.SchemaOrBool 34 PatternProperties map[string]spec.Schema 35 Root interface{} 36 KnownFormats strfmt.Registry 37 Options SchemaValidatorOptions 38 } 39 40 func (o *objectValidator) SetPath(path string) { 41 o.Path = path 42 } 43 44 func (o *objectValidator) Applies(source interface{}, kind reflect.Kind) bool { 45 // TODO: this should also work for structs 46 // there is a problem in the type validator where it will be unhappy about null values 47 // so that requires more testing 48 r := reflect.TypeOf(source) == specSchemaType && (kind == reflect.Map || kind == reflect.Struct) 49 debugLog("object validator for %q applies %t for %T (kind: %v)\n", o.Path, r, source, kind) 50 return r 51 } 52 53 func (o *objectValidator) Validate(data interface{}) *Result { 54 val := data.(map[string]interface{}) 55 // TODO: guard against nil data 56 numKeys := int64(len(val)) 57 58 res := new(Result) 59 60 if o.MinProperties != nil && numKeys < *o.MinProperties { 61 res.AddErrors(errors.TooFewProperties(o.Path, o.In, *o.MinProperties, numKeys)) 62 } 63 if o.MaxProperties != nil && numKeys > *o.MaxProperties { 64 res.AddErrors(errors.TooManyProperties(o.Path, o.In, *o.MaxProperties, numKeys)) 65 } 66 67 // check validity of field names 68 if o.AdditionalProperties != nil && !o.AdditionalProperties.Allows { 69 // Case: additionalProperties: false 70 for k := range val { 71 _, regularProperty := o.Properties[k] 72 matched := false 73 74 for pk := range o.PatternProperties { 75 if matches, _ := regexp.MatchString(pk, k); matches { 76 matched = true 77 break 78 } 79 } 80 81 if !regularProperty && !matched { 82 // Special properties "$schema" and "id" are ignored 83 res.AddErrors(errors.PropertyNotAllowed(o.Path, o.In, k)) 84 } 85 } 86 } else { 87 // Cases: no additionalProperties (implying: true), or additionalProperties: true, or additionalProperties: { <<schema>> } 88 for key, value := range val { 89 _, regularProperty := o.Properties[key] 90 91 // Validates property against "patternProperties" if applicable 92 // BUG(fredbi): succeededOnce is always false 93 94 // NOTE: how about regular properties which do not match patternProperties? 95 matched, succeededOnce, _ := o.validatePatternProperty(key, value, res) 96 97 if !(regularProperty || matched || succeededOnce) { 98 99 // Cases: properties which are not regular properties and have not been matched by the PatternProperties validator 100 if o.AdditionalProperties != nil && o.AdditionalProperties.Schema != nil { 101 // AdditionalProperties as Schema 102 res.Merge(o.Options.NewValidatorForField(key, o.AdditionalProperties.Schema, o.Root, o.Path+"."+key, o.KnownFormats, o.Options.Options()...).Validate(value)) 103 } else if regularProperty && !(matched || succeededOnce) { 104 // TODO: this is dead code since regularProperty=false here 105 res.AddErrors(errors.FailedAllPatternProperties(o.Path, o.In, key)) 106 } 107 } 108 } 109 // Valid cases: additionalProperties: true or undefined 110 } 111 112 createdFromDefaults := map[string]bool{} 113 114 // Property types: 115 // - regular Property 116 for pName, pSchema := range o.Properties { 117 rName := pName 118 if o.Path != "" { 119 rName = o.Path + "." + pName 120 } 121 122 // Recursively validates each property against its schema 123 if v, ok := val[pName]; ok { 124 r := o.Options.NewValidatorForField(pName, &pSchema, o.Root, rName, o.KnownFormats, o.Options.Options()...).Validate(v) 125 res.Merge(r) 126 } 127 } 128 129 // Check required properties 130 if len(o.Required) > 0 { 131 for _, k := range o.Required { 132 if _, ok := val[k]; !ok && !createdFromDefaults[k] { 133 res.AddErrors(errors.Required(o.Path+"."+k, o.In)) 134 continue 135 } 136 } 137 } 138 139 // Check patternProperties 140 // TODO: it looks like we have done that twice in many cases 141 for key, value := range val { 142 _, regularProperty := o.Properties[key] 143 matched, _ /*succeededOnce*/, patterns := o.validatePatternProperty(key, value, res) 144 if !regularProperty && (matched /*|| succeededOnce*/) { 145 for _, pName := range patterns { 146 if v, ok := o.PatternProperties[pName]; ok { 147 res.Merge(o.Options.NewValidatorForField(key, &v, o.Root, o.Path+"."+key, o.KnownFormats, o.Options.Options()...).Validate(value)) 148 } 149 } 150 } 151 } 152 return res 153 } 154 155 // TODO: succeededOnce is not used anywhere 156 func (o *objectValidator) validatePatternProperty(key string, value interface{}, result *Result) (bool, bool, []string) { 157 matched := false 158 succeededOnce := false 159 var patterns []string 160 161 for k, schema := range o.PatternProperties { 162 sch := schema 163 if match, _ := regexp.MatchString(k, key); match { 164 patterns = append(patterns, k) 165 matched = true 166 validator := o.Options.NewValidatorForField(key, &sch, o.Root, o.Path+"."+key, o.KnownFormats, o.Options.Options()...) 167 168 res := validator.Validate(value) 169 result.Merge(res) 170 } 171 } 172 173 // BUG(fredbi): can't get to here. Should remove dead code (commented out). 174 175 //if succeededOnce { 176 // result.Inc() 177 //} 178 179 return matched, succeededOnce, patterns 180 }