- // Copyright 2015 go-swagger maintainers
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
-
- package validate
-
- import (
- "reflect"
- "strings"
-
- "github.com/go-openapi/errors"
- "github.com/go-openapi/runtime"
- "github.com/go-openapi/spec"
- "github.com/go-openapi/strfmt"
- "github.com/go-openapi/swag"
- )
-
- type typeValidator struct {
- Type spec.StringOrArray
- Nullable bool
- Format string
- In string
- Path string
- }
-
- func (t *typeValidator) schemaInfoForType(data interface{}) (string, string) {
- // internal type to JSON type with swagger 2.0 format (with go-openapi/strfmt extensions),
- // see https://github.com/go-openapi/strfmt/blob/master/README.md
- // TODO: this switch really is some sort of reverse lookup for formats. It should be provided by strfmt.
- switch data.(type) {
- case []byte, strfmt.Base64, *strfmt.Base64:
- return stringType, stringFormatByte
- case strfmt.CreditCard, *strfmt.CreditCard:
- return stringType, stringFormatCreditCard
- case strfmt.Date, *strfmt.Date:
- return stringType, stringFormatDate
- case strfmt.DateTime, *strfmt.DateTime:
- return stringType, stringFormatDateTime
- case strfmt.Duration, *strfmt.Duration:
- return stringType, stringFormatDuration
- case runtime.File, *runtime.File:
- return fileType, ""
- case strfmt.Email, *strfmt.Email:
- return stringType, stringFormatEmail
- case strfmt.HexColor, *strfmt.HexColor:
- return stringType, stringFormatHexColor
- case strfmt.Hostname, *strfmt.Hostname:
- return stringType, stringFormatHostname
- case strfmt.IPv4, *strfmt.IPv4:
- return stringType, stringFormatIPv4
- case strfmt.IPv6, *strfmt.IPv6:
- return stringType, stringFormatIPv6
- case strfmt.ISBN, *strfmt.ISBN:
- return stringType, stringFormatISBN
- case strfmt.ISBN10, *strfmt.ISBN10:
- return stringType, stringFormatISBN10
- case strfmt.ISBN13, *strfmt.ISBN13:
- return stringType, stringFormatISBN13
- case strfmt.MAC, *strfmt.MAC:
- return stringType, stringFormatMAC
- case strfmt.ObjectId, *strfmt.ObjectId:
- return stringType, stringFormatBSONObjectID
- case strfmt.Password, *strfmt.Password:
- return stringType, stringFormatPassword
- case strfmt.RGBColor, *strfmt.RGBColor:
- return stringType, stringFormatRGBColor
- case strfmt.SSN, *strfmt.SSN:
- return stringType, stringFormatSSN
- case strfmt.URI, *strfmt.URI:
- return stringType, stringFormatURI
- case strfmt.UUID, *strfmt.UUID:
- return stringType, stringFormatUUID
- case strfmt.UUID3, *strfmt.UUID3:
- return stringType, stringFormatUUID3
- case strfmt.UUID4, *strfmt.UUID4:
- return stringType, stringFormatUUID4
- case strfmt.UUID5, *strfmt.UUID5:
- return stringType, stringFormatUUID5
- // TODO: missing binary (io.ReadCloser)
- // TODO: missing json.Number
- default:
- val := reflect.ValueOf(data)
- tpe := val.Type()
- switch tpe.Kind() {
- case reflect.Bool:
- return booleanType, ""
- case reflect.String:
- return stringType, ""
- case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint8, reflect.Uint16, reflect.Uint32:
- // NOTE: that is the spec. With go-openapi, is that not uint32 for unsigned integers?
- return integerType, integerFormatInt32
- case reflect.Int, reflect.Int64, reflect.Uint, reflect.Uint64:
- return integerType, integerFormatInt64
- case reflect.Float32:
- // NOTE: is that not numberFormatFloat?
- return numberType, numberFormatFloat32
- case reflect.Float64:
- // NOTE: is that not "double"?
- return numberType, numberFormatFloat64
- // NOTE: go arrays (reflect.Array) are not supported (fixed length)
- case reflect.Slice:
- return arrayType, ""
- case reflect.Map, reflect.Struct:
- return objectType, ""
- case reflect.Interface:
- // What to do here?
- panic("dunno what to do here")
- case reflect.Ptr:
- return t.schemaInfoForType(reflect.Indirect(val).Interface())
- }
- }
- return "", ""
- }
-
- func (t *typeValidator) SetPath(path string) {
- t.Path = path
- }
-
- func (t *typeValidator) Applies(source interface{}, kind reflect.Kind) bool {
- // typeValidator applies to Schema, Parameter and Header objects
- stpe := reflect.TypeOf(source)
- r := (len(t.Type) > 0 || t.Format != "") && (stpe == specSchemaType || stpe == specParameterType || stpe == specHeaderType)
- debugLog("type validator for %q applies %t for %T (kind: %v)\n", t.Path, r, source, kind)
- return r
- }
-
- func (t *typeValidator) Validate(data interface{}) *Result {
- result := new(Result)
- result.Inc()
- if data == nil || reflect.DeepEqual(reflect.Zero(reflect.TypeOf(data)), reflect.ValueOf(data)) {
- // nil or zero value for the passed structure require Type: null
- if len(t.Type) > 0 && !t.Type.Contains(nullType) && !t.Nullable { // TODO: if a property is not required it also passes this
- return errorHelp.sErr(errors.InvalidType(t.Path, t.In, strings.Join(t.Type, ","), nullType))
- }
- return result
- }
-
- // check if the type matches, should be used in every validator chain as first item
- val := reflect.Indirect(reflect.ValueOf(data))
- kind := val.Kind()
-
- // infer schema type (JSON) and format from passed data type
- schType, format := t.schemaInfoForType(data)
-
- debugLog("path: %s, schType: %s, format: %s, expType: %s, expFmt: %s, kind: %s", t.Path, schType, format, t.Type, t.Format, val.Kind().String())
-
- // check numerical types
- // TODO: check unsigned ints
- // TODO: check json.Number (see schema.go)
- isLowerInt := t.Format == integerFormatInt64 && format == integerFormatInt32
- isLowerFloat := t.Format == numberFormatFloat64 && format == numberFormatFloat32
- isFloatInt := schType == numberType && swag.IsFloat64AJSONInteger(val.Float()) && t.Type.Contains(integerType)
- isIntFloat := schType == integerType && t.Type.Contains(numberType)
-
- if kind != reflect.String && kind != reflect.Slice && t.Format != "" && !(t.Type.Contains(schType) || format == t.Format || isFloatInt || isIntFloat || isLowerInt || isLowerFloat) {
- // TODO: test case
- return errorHelp.sErr(errors.InvalidType(t.Path, t.In, t.Format, format))
- }
-
- if !(t.Type.Contains(numberType) || t.Type.Contains(integerType)) && t.Format != "" && (kind == reflect.String || kind == reflect.Slice) {
- return result
- }
-
- if !(t.Type.Contains(schType) || isFloatInt || isIntFloat) {
- return errorHelp.sErr(errors.InvalidType(t.Path, t.In, strings.Join(t.Type, ","), schType))
- }
- return result
- }
|