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  }