...

Source file src/github.com/go-openapi/validate/type.go

Documentation: github.com/go-openapi/validate

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

View as plain text