k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/validation/validate/type.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  	"strings"
    20  
    21  	"github.com/go-openapi/swag"
    22  	"k8s.io/kube-openapi/pkg/validation/errors"
    23  	"k8s.io/kube-openapi/pkg/validation/spec"
    24  	"k8s.io/kube-openapi/pkg/validation/strfmt"
    25  )
    26  
    27  type typeValidator struct {
    28  	Type     spec.StringOrArray
    29  	Nullable bool
    30  	Format   string
    31  	In       string
    32  	Path     string
    33  }
    34  
    35  func (t *typeValidator) schemaInfoForType(data interface{}) (string, string) {
    36  	// internal type to JSON type with swagger 2.0 format (with go-openapi/strfmt extensions),
    37  	// see https://github.com/go-openapi/strfmt/blob/master/README.md
    38  	// TODO: this switch really is some sort of reverse lookup for formats. It should be provided by strfmt.
    39  	switch data.(type) {
    40  	case []byte, strfmt.Base64, *strfmt.Base64:
    41  		return stringType, stringFormatByte
    42  	case strfmt.CreditCard, *strfmt.CreditCard:
    43  		return stringType, stringFormatCreditCard
    44  	case strfmt.Date, *strfmt.Date:
    45  		return stringType, stringFormatDate
    46  	case strfmt.DateTime, *strfmt.DateTime:
    47  		return stringType, stringFormatDateTime
    48  	case strfmt.Duration, *strfmt.Duration:
    49  		return stringType, stringFormatDuration
    50  	case strfmt.Email, *strfmt.Email:
    51  		return stringType, stringFormatEmail
    52  	case strfmt.HexColor, *strfmt.HexColor:
    53  		return stringType, stringFormatHexColor
    54  	case strfmt.Hostname, *strfmt.Hostname:
    55  		return stringType, stringFormatHostname
    56  	case strfmt.IPv4, *strfmt.IPv4:
    57  		return stringType, stringFormatIPv4
    58  	case strfmt.IPv6, *strfmt.IPv6:
    59  		return stringType, stringFormatIPv6
    60  	case strfmt.ISBN, *strfmt.ISBN:
    61  		return stringType, stringFormatISBN
    62  	case strfmt.ISBN10, *strfmt.ISBN10:
    63  		return stringType, stringFormatISBN10
    64  	case strfmt.ISBN13, *strfmt.ISBN13:
    65  		return stringType, stringFormatISBN13
    66  	case strfmt.MAC, *strfmt.MAC:
    67  		return stringType, stringFormatMAC
    68  	case strfmt.Password, *strfmt.Password:
    69  		return stringType, stringFormatPassword
    70  	case strfmt.RGBColor, *strfmt.RGBColor:
    71  		return stringType, stringFormatRGBColor
    72  	case strfmt.SSN, *strfmt.SSN:
    73  		return stringType, stringFormatSSN
    74  	case strfmt.URI, *strfmt.URI:
    75  		return stringType, stringFormatURI
    76  	case strfmt.UUID, *strfmt.UUID:
    77  		return stringType, stringFormatUUID
    78  	case strfmt.UUID3, *strfmt.UUID3:
    79  		return stringType, stringFormatUUID3
    80  	case strfmt.UUID4, *strfmt.UUID4:
    81  		return stringType, stringFormatUUID4
    82  	case strfmt.UUID5, *strfmt.UUID5:
    83  		return stringType, stringFormatUUID5
    84  	// TODO: missing binary (io.ReadCloser)
    85  	// TODO: missing json.Number
    86  	default:
    87  		val := reflect.ValueOf(data)
    88  		tpe := val.Type()
    89  		switch tpe.Kind() {
    90  		case reflect.Bool:
    91  			return booleanType, ""
    92  		case reflect.String:
    93  			return stringType, ""
    94  		case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint8, reflect.Uint16, reflect.Uint32:
    95  			// NOTE: that is the spec. With go-openapi, is that not uint32 for unsigned integers?
    96  			return integerType, integerFormatInt32
    97  		case reflect.Int, reflect.Int64, reflect.Uint, reflect.Uint64:
    98  			return integerType, integerFormatInt64
    99  		case reflect.Float32:
   100  			// NOTE: is that not numberFormatFloat?
   101  			return numberType, numberFormatFloat32
   102  		case reflect.Float64:
   103  			// NOTE: is that not "double"?
   104  			return numberType, numberFormatFloat64
   105  		// NOTE: go arrays (reflect.Array) are not supported (fixed length)
   106  		case reflect.Slice:
   107  			return arrayType, ""
   108  		case reflect.Map, reflect.Struct:
   109  			return objectType, ""
   110  		case reflect.Interface:
   111  			// What to do here?
   112  			panic("dunno what to do here")
   113  		case reflect.Ptr:
   114  			return t.schemaInfoForType(reflect.Indirect(val).Interface())
   115  		}
   116  	}
   117  	return "", ""
   118  }
   119  
   120  func (t *typeValidator) SetPath(path string) {
   121  	t.Path = path
   122  }
   123  
   124  func (t *typeValidator) Applies(source interface{}, kind reflect.Kind) bool {
   125  	// typeValidator applies to Schema, Parameter and Header objects
   126  	stpe := reflect.TypeOf(source)
   127  	r := (len(t.Type) > 0 || t.Format != "") && stpe == specSchemaType
   128  	debugLog("type validator for %q applies %t for %T (kind: %v)\n", t.Path, r, source, kind)
   129  	return r
   130  }
   131  
   132  func (t *typeValidator) Validate(data interface{}) *Result {
   133  	result := new(Result)
   134  	result.Inc()
   135  	if data == nil {
   136  		// nil or zero value for the passed structure require Type: null
   137  		if len(t.Type) > 0 && !t.Type.Contains(nullType) && !t.Nullable { // TODO: if a property is not required it also passes this
   138  			return errorHelp.sErr(errors.InvalidType(t.Path, t.In, strings.Join(t.Type, ","), nullType))
   139  		}
   140  		return result
   141  	}
   142  
   143  	// check if the type matches, should be used in every validator chain as first item
   144  	val := reflect.Indirect(reflect.ValueOf(data))
   145  	kind := val.Kind()
   146  
   147  	// infer schema type (JSON) and format from passed data type
   148  	schType, format := t.schemaInfoForType(data)
   149  
   150  	debugLog("path: %s, schType: %s,  format: %s, expType: %s, expFmt: %s, kind: %s", t.Path, schType, format, t.Type, t.Format, val.Kind().String())
   151  
   152  	// check numerical types
   153  	// TODO: check unsigned ints
   154  	// TODO: check json.Number (see schema.go)
   155  	isLowerInt := t.Format == integerFormatInt64 && format == integerFormatInt32
   156  	isLowerFloat := t.Format == numberFormatFloat64 && format == numberFormatFloat32
   157  	isFloatInt := schType == numberType && swag.IsFloat64AJSONInteger(val.Float()) && t.Type.Contains(integerType)
   158  	isIntFloat := schType == integerType && t.Type.Contains(numberType)
   159  
   160  	if kind != reflect.String && kind != reflect.Slice && t.Format != "" && !(t.Type.Contains(schType) || format == t.Format || isFloatInt || isIntFloat || isLowerInt || isLowerFloat) {
   161  		// TODO: test case
   162  		return errorHelp.sErr(errors.InvalidType(t.Path, t.In, t.Format, format))
   163  	}
   164  
   165  	if !(t.Type.Contains(numberType) || t.Type.Contains(integerType)) && t.Format != "" && (kind == reflect.String || kind == reflect.Slice) {
   166  		return result
   167  	}
   168  
   169  	if !(t.Type.Contains(schType) || isFloatInt || isIntFloat) {
   170  		return errorHelp.sErr(errors.InvalidType(t.Path, t.In, strings.Join(t.Type, ","), schType))
   171  	}
   172  	return result
   173  }