k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/util/proto/validation/types.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package validation 18 19 import ( 20 "reflect" 21 "sort" 22 23 "k8s.io/kube-openapi/pkg/util/proto" 24 ) 25 26 type validationItem interface { 27 proto.SchemaVisitor 28 29 Errors() []error 30 Path() *proto.Path 31 } 32 33 type baseItem struct { 34 errors errors 35 path proto.Path 36 } 37 38 // Errors returns the list of errors found for this item. 39 func (item *baseItem) Errors() []error { 40 return item.errors.Errors() 41 } 42 43 // AddValidationError wraps the given error into a ValidationError and 44 // attaches it to this item. 45 func (item *baseItem) AddValidationError(err error) { 46 item.errors.AppendErrors(ValidationError{Path: item.path.String(), Err: err}) 47 } 48 49 // AddError adds a regular (non-validation related) error to the list. 50 func (item *baseItem) AddError(err error) { 51 item.errors.AppendErrors(err) 52 } 53 54 // CopyErrors adds a list of errors to this item. This is useful to copy 55 // errors from subitems. 56 func (item *baseItem) CopyErrors(errs []error) { 57 item.errors.AppendErrors(errs...) 58 } 59 60 // Path returns the path of this item, helps print useful errors. 61 func (item *baseItem) Path() *proto.Path { 62 return &item.path 63 } 64 65 // mapItem represents a map entry in the yaml. 66 type mapItem struct { 67 baseItem 68 69 Map map[string]interface{} 70 } 71 72 func (item *mapItem) sortedKeys() []string { 73 sortedKeys := []string{} 74 for key := range item.Map { 75 sortedKeys = append(sortedKeys, key) 76 } 77 sort.Strings(sortedKeys) 78 return sortedKeys 79 } 80 81 var _ validationItem = &mapItem{} 82 83 func (item *mapItem) VisitPrimitive(schema *proto.Primitive) { 84 item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: schema.Type, Actual: "map"}) 85 } 86 87 func (item *mapItem) VisitArray(schema *proto.Array) { 88 item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "array", Actual: "map"}) 89 } 90 91 func (item *mapItem) VisitMap(schema *proto.Map) { 92 for _, key := range item.sortedKeys() { 93 subItem, err := itemFactory(item.Path().FieldPath(key), item.Map[key]) 94 if err != nil { 95 item.AddError(err) 96 continue 97 } 98 schema.SubType.Accept(subItem) 99 item.CopyErrors(subItem.Errors()) 100 } 101 } 102 103 func (item *mapItem) VisitKind(schema *proto.Kind) { 104 // Verify each sub-field. 105 for _, key := range item.sortedKeys() { 106 if item.Map[key] == nil { 107 continue 108 } 109 subItem, err := itemFactory(item.Path().FieldPath(key), item.Map[key]) 110 if err != nil { 111 item.AddError(err) 112 continue 113 } 114 if _, ok := schema.Fields[key]; !ok { 115 item.AddValidationError(UnknownFieldError{Path: schema.GetPath().String(), Field: key}) 116 continue 117 } 118 schema.Fields[key].Accept(subItem) 119 item.CopyErrors(subItem.Errors()) 120 } 121 122 // Verify that all required fields are present. 123 for _, required := range schema.RequiredFields { 124 if v, ok := item.Map[required]; !ok || v == nil { 125 item.AddValidationError(MissingRequiredFieldError{Path: schema.GetPath().String(), Field: required}) 126 } 127 } 128 } 129 130 func (item *mapItem) VisitArbitrary(schema *proto.Arbitrary) { 131 } 132 133 func (item *mapItem) VisitReference(schema proto.Reference) { 134 // passthrough 135 schema.SubSchema().Accept(item) 136 } 137 138 // arrayItem represents a yaml array. 139 type arrayItem struct { 140 baseItem 141 142 Array []interface{} 143 } 144 145 var _ validationItem = &arrayItem{} 146 147 func (item *arrayItem) VisitPrimitive(schema *proto.Primitive) { 148 item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: schema.Type, Actual: "array"}) 149 } 150 151 func (item *arrayItem) VisitArray(schema *proto.Array) { 152 for i, v := range item.Array { 153 path := item.Path().ArrayPath(i) 154 if v == nil { 155 item.AddValidationError(InvalidObjectTypeError{Type: "nil", Path: path.String()}) 156 continue 157 } 158 subItem, err := itemFactory(path, v) 159 if err != nil { 160 item.AddError(err) 161 continue 162 } 163 schema.SubType.Accept(subItem) 164 item.CopyErrors(subItem.Errors()) 165 } 166 } 167 168 func (item *arrayItem) VisitMap(schema *proto.Map) { 169 item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "map", Actual: "array"}) 170 } 171 172 func (item *arrayItem) VisitKind(schema *proto.Kind) { 173 item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "map", Actual: "array"}) 174 } 175 176 func (item *arrayItem) VisitArbitrary(schema *proto.Arbitrary) { 177 } 178 179 func (item *arrayItem) VisitReference(schema proto.Reference) { 180 // passthrough 181 schema.SubSchema().Accept(item) 182 } 183 184 // primitiveItem represents a yaml value. 185 type primitiveItem struct { 186 baseItem 187 188 Value interface{} 189 Kind string 190 } 191 192 var _ validationItem = &primitiveItem{} 193 194 func (item *primitiveItem) VisitPrimitive(schema *proto.Primitive) { 195 // Some types of primitives can match more than one (a number 196 // can be a string, but not the other way around). Return from 197 // the switch if we have a valid possible type conversion 198 // NOTE(apelisse): This logic is blindly copied from the 199 // existing swagger logic, and I'm not sure I agree with it. 200 switch schema.Type { 201 case proto.Boolean: 202 switch item.Kind { 203 case proto.Boolean: 204 return 205 } 206 case proto.Integer: 207 switch item.Kind { 208 case proto.Integer, proto.Number: 209 return 210 } 211 case proto.Number: 212 switch item.Kind { 213 case proto.Integer, proto.Number: 214 return 215 } 216 case proto.String: 217 return 218 } 219 // TODO(wrong): this misses "null" 220 221 item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: schema.Type, Actual: item.Kind}) 222 } 223 224 func (item *primitiveItem) VisitArray(schema *proto.Array) { 225 item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "array", Actual: item.Kind}) 226 } 227 228 func (item *primitiveItem) VisitMap(schema *proto.Map) { 229 item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "map", Actual: item.Kind}) 230 } 231 232 func (item *primitiveItem) VisitKind(schema *proto.Kind) { 233 item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "map", Actual: item.Kind}) 234 } 235 236 func (item *primitiveItem) VisitArbitrary(schema *proto.Arbitrary) { 237 } 238 239 func (item *primitiveItem) VisitReference(schema proto.Reference) { 240 // passthrough 241 schema.SubSchema().Accept(item) 242 } 243 244 // itemFactory creates the relevant item type/visitor based on the current yaml type. 245 func itemFactory(path proto.Path, v interface{}) (validationItem, error) { 246 // We need to special case for no-type fields in yaml (e.g. empty item in list) 247 if v == nil { 248 return nil, InvalidObjectTypeError{Type: "nil", Path: path.String()} 249 } 250 kind := reflect.TypeOf(v).Kind() 251 switch kind { 252 case reflect.Bool: 253 return &primitiveItem{ 254 baseItem: baseItem{path: path}, 255 Value: v, 256 Kind: proto.Boolean, 257 }, nil 258 case reflect.Int, 259 reflect.Int8, 260 reflect.Int16, 261 reflect.Int32, 262 reflect.Int64, 263 reflect.Uint, 264 reflect.Uint8, 265 reflect.Uint16, 266 reflect.Uint32, 267 reflect.Uint64: 268 return &primitiveItem{ 269 baseItem: baseItem{path: path}, 270 Value: v, 271 Kind: proto.Integer, 272 }, nil 273 case reflect.Float32, 274 reflect.Float64: 275 return &primitiveItem{ 276 baseItem: baseItem{path: path}, 277 Value: v, 278 Kind: proto.Number, 279 }, nil 280 case reflect.String: 281 return &primitiveItem{ 282 baseItem: baseItem{path: path}, 283 Value: v, 284 Kind: proto.String, 285 }, nil 286 case reflect.Array, 287 reflect.Slice: 288 return &arrayItem{ 289 baseItem: baseItem{path: path}, 290 Array: v.([]interface{}), 291 }, nil 292 case reflect.Map: 293 return &mapItem{ 294 baseItem: baseItem{path: path}, 295 Map: v.(map[string]interface{}), 296 }, nil 297 } 298 return nil, InvalidObjectTypeError{Type: kind.String(), Path: path.String()} 299 }