| // 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 ( |
| "fmt" |
| "reflect" |
| "unicode/utf8" |
| |
| "github.com/go-openapi/errors" |
| "github.com/go-openapi/strfmt" |
| "github.com/go-openapi/swag" |
| ) |
| |
| // Enum validates if the data is a member of the enum |
| func Enum(path, in string, data interface{}, enum interface{}) *errors.Validation { |
| val := reflect.ValueOf(enum) |
| if val.Kind() != reflect.Slice { |
| return nil |
| } |
| |
| var values []interface{} |
| for i := 0; i < val.Len(); i++ { |
| ele := val.Index(i) |
| enumValue := ele.Interface() |
| if data != nil { |
| if reflect.DeepEqual(data, enumValue) { |
| return nil |
| } |
| actualType := reflect.TypeOf(enumValue) |
| if actualType == nil { // Safeguard. Frankly, I don't know how we may get a nil |
| continue |
| } |
| expectedValue := reflect.ValueOf(data) |
| if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { |
| // Attempt comparison after type conversion |
| if reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), enumValue) { |
| return nil |
| } |
| } |
| } |
| values = append(values, enumValue) |
| } |
| return errors.EnumFail(path, in, data, values) |
| } |
| |
| // MinItems validates that there are at least n items in a slice |
| func MinItems(path, in string, size, min int64) *errors.Validation { |
| if size < min { |
| return errors.TooFewItems(path, in, min) |
| } |
| return nil |
| } |
| |
| // MaxItems validates that there are at most n items in a slice |
| func MaxItems(path, in string, size, max int64) *errors.Validation { |
| if size > max { |
| return errors.TooManyItems(path, in, max) |
| } |
| return nil |
| } |
| |
| // UniqueItems validates that the provided slice has unique elements |
| func UniqueItems(path, in string, data interface{}) *errors.Validation { |
| val := reflect.ValueOf(data) |
| if val.Kind() != reflect.Slice { |
| return nil |
| } |
| var unique []interface{} |
| for i := 0; i < val.Len(); i++ { |
| v := val.Index(i).Interface() |
| for _, u := range unique { |
| if reflect.DeepEqual(v, u) { |
| return errors.DuplicateItems(path, in) |
| } |
| } |
| unique = append(unique, v) |
| } |
| return nil |
| } |
| |
| // MinLength validates a string for minimum length |
| func MinLength(path, in, data string, minLength int64) *errors.Validation { |
| strLen := int64(utf8.RuneCount([]byte(data))) |
| if strLen < minLength { |
| return errors.TooShort(path, in, minLength) |
| } |
| return nil |
| } |
| |
| // MaxLength validates a string for maximum length |
| func MaxLength(path, in, data string, maxLength int64) *errors.Validation { |
| strLen := int64(utf8.RuneCount([]byte(data))) |
| if strLen > maxLength { |
| return errors.TooLong(path, in, maxLength) |
| } |
| return nil |
| } |
| |
| // Required validates an interface for requiredness |
| func Required(path, in string, data interface{}) *errors.Validation { |
| val := reflect.ValueOf(data) |
| if val.IsValid() { |
| if reflect.DeepEqual(reflect.Zero(val.Type()).Interface(), val.Interface()) { |
| return errors.Required(path, in) |
| } |
| return nil |
| } |
| return errors.Required(path, in) |
| } |
| |
| // RequiredString validates a string for requiredness |
| func RequiredString(path, in, data string) *errors.Validation { |
| if data == "" { |
| return errors.Required(path, in) |
| } |
| return nil |
| } |
| |
| // RequiredNumber validates a number for requiredness |
| func RequiredNumber(path, in string, data float64) *errors.Validation { |
| if data == 0 { |
| return errors.Required(path, in) |
| } |
| return nil |
| } |
| |
| // Pattern validates a string against a regular expression |
| func Pattern(path, in, data, pattern string) *errors.Validation { |
| re, err := compileRegexp(pattern) |
| if err != nil { |
| return errors.FailedPattern(path, in, fmt.Sprintf("%s, but pattern is invalid: %s", pattern, err.Error())) |
| } |
| if !re.MatchString(data) { |
| return errors.FailedPattern(path, in, pattern) |
| } |
| return nil |
| } |
| |
| // MaximumInt validates if a number is smaller than a given maximum |
| func MaximumInt(path, in string, data, max int64, exclusive bool) *errors.Validation { |
| if (!exclusive && data > max) || (exclusive && data >= max) { |
| return errors.ExceedsMaximumInt(path, in, max, exclusive) |
| } |
| return nil |
| } |
| |
| // MaximumUint validates if a number is smaller than a given maximum |
| func MaximumUint(path, in string, data, max uint64, exclusive bool) *errors.Validation { |
| if (!exclusive && data > max) || (exclusive && data >= max) { |
| return errors.ExceedsMaximumUint(path, in, max, exclusive) |
| } |
| return nil |
| } |
| |
| // Maximum validates if a number is smaller than a given maximum |
| func Maximum(path, in string, data, max float64, exclusive bool) *errors.Validation { |
| if (!exclusive && data > max) || (exclusive && data >= max) { |
| return errors.ExceedsMaximum(path, in, max, exclusive) |
| } |
| return nil |
| } |
| |
| // Minimum validates if a number is smaller than a given minimum |
| func Minimum(path, in string, data, min float64, exclusive bool) *errors.Validation { |
| if (!exclusive && data < min) || (exclusive && data <= min) { |
| return errors.ExceedsMinimum(path, in, min, exclusive) |
| } |
| return nil |
| } |
| |
| // MinimumInt validates if a number is smaller than a given minimum |
| func MinimumInt(path, in string, data, min int64, exclusive bool) *errors.Validation { |
| if (!exclusive && data < min) || (exclusive && data <= min) { |
| return errors.ExceedsMinimumInt(path, in, min, exclusive) |
| } |
| return nil |
| } |
| |
| // MinimumUint validates if a number is smaller than a given minimum |
| func MinimumUint(path, in string, data, min uint64, exclusive bool) *errors.Validation { |
| if (!exclusive && data < min) || (exclusive && data <= min) { |
| return errors.ExceedsMinimumUint(path, in, min, exclusive) |
| } |
| return nil |
| } |
| |
| // MultipleOf validates if the provided number is a multiple of the factor |
| func MultipleOf(path, in string, data, factor float64) *errors.Validation { |
| // multipleOf factor must be positive |
| if factor < 0 { |
| return errors.MultipleOfMustBePositive(path, in, factor) |
| } |
| var mult float64 |
| if factor < 1 { |
| mult = 1 / factor * data |
| } else { |
| mult = data / factor |
| } |
| if !swag.IsFloat64AJSONInteger(mult) { |
| return errors.NotMultipleOf(path, in, factor) |
| } |
| return nil |
| } |
| |
| // MultipleOfInt validates if the provided integer is a multiple of the factor |
| func MultipleOfInt(path, in string, data int64, factor int64) *errors.Validation { |
| // multipleOf factor must be positive |
| if factor < 0 { |
| return errors.MultipleOfMustBePositive(path, in, factor) |
| } |
| mult := data / factor |
| if mult*factor != data { |
| return errors.NotMultipleOf(path, in, factor) |
| } |
| return nil |
| } |
| |
| // MultipleOfUint validates if the provided unsigned integer is a multiple of the factor |
| func MultipleOfUint(path, in string, data, factor uint64) *errors.Validation { |
| mult := data / factor |
| if mult*factor != data { |
| return errors.NotMultipleOf(path, in, factor) |
| } |
| return nil |
| } |
| |
| // FormatOf validates if a string matches a format in the format registry |
| func FormatOf(path, in, format, data string, registry strfmt.Registry) *errors.Validation { |
| if registry == nil { |
| registry = strfmt.Default |
| } |
| if ok := registry.ContainsName(format); !ok { |
| return errors.InvalidTypeName(format) |
| } |
| if ok := registry.Validates(format, data); !ok { |
| return errors.InvalidType(path, in, format, data) |
| } |
| return nil |
| } |
| |
| // MaximumNativeType provides native type constraint validation as a facade |
| // to various numeric types versions of Maximum constraint check. |
| // |
| // Assumes that any possible loss conversion during conversion has been |
| // checked beforehand. |
| // |
| // NOTE: currently, the max value is marshalled as a float64, no matter what, |
| // which means there may be a loss during conversions (e.g. for very large integers) |
| // |
| // TODO: Normally, a JSON MAX_SAFE_INTEGER check would ensure conversion remains loss-free |
| func MaximumNativeType(path, in string, val interface{}, max float64, exclusive bool) *errors.Validation { |
| kind := reflect.ValueOf(val).Type().Kind() |
| switch kind { |
| case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
| value := valueHelp.asInt64(val) |
| return MaximumInt(path, in, value, int64(max), exclusive) |
| case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: |
| value := valueHelp.asUint64(val) |
| if max < 0 { |
| return errors.ExceedsMaximum(path, in, max, exclusive) |
| } |
| return MaximumUint(path, in, value, uint64(max), exclusive) |
| case reflect.Float32, reflect.Float64: |
| fallthrough |
| default: |
| value := valueHelp.asFloat64(val) |
| return Maximum(path, in, value, max, exclusive) |
| } |
| } |
| |
| // MinimumNativeType provides native type constraint validation as a facade |
| // to various numeric types versions of Minimum constraint check. |
| // |
| // Assumes that any possible loss conversion during conversion has been |
| // checked beforehand. |
| // |
| // NOTE: currently, the min value is marshalled as a float64, no matter what, |
| // which means there may be a loss during conversions (e.g. for very large integers) |
| // |
| // TODO: Normally, a JSON MAX_SAFE_INTEGER check would ensure conversion remains loss-free |
| func MinimumNativeType(path, in string, val interface{}, min float64, exclusive bool) *errors.Validation { |
| kind := reflect.ValueOf(val).Type().Kind() |
| switch kind { |
| case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
| value := valueHelp.asInt64(val) |
| return MinimumInt(path, in, value, int64(min), exclusive) |
| case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: |
| value := valueHelp.asUint64(val) |
| if min < 0 { |
| return nil |
| } |
| return MinimumUint(path, in, value, uint64(min), exclusive) |
| case reflect.Float32, reflect.Float64: |
| fallthrough |
| default: |
| value := valueHelp.asFloat64(val) |
| return Minimum(path, in, value, min, exclusive) |
| } |
| } |
| |
| // MultipleOfNativeType provides native type constraint validation as a facade |
| // to various numeric types version of MultipleOf constraint check. |
| // |
| // Assumes that any possible loss conversion during conversion has been |
| // checked beforehand. |
| // |
| // NOTE: currently, the multipleOf factor is marshalled as a float64, no matter what, |
| // which means there may be a loss during conversions (e.g. for very large integers) |
| // |
| // TODO: Normally, a JSON MAX_SAFE_INTEGER check would ensure conversion remains loss-free |
| func MultipleOfNativeType(path, in string, val interface{}, multipleOf float64) *errors.Validation { |
| kind := reflect.ValueOf(val).Type().Kind() |
| switch kind { |
| case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
| value := valueHelp.asInt64(val) |
| return MultipleOfInt(path, in, value, int64(multipleOf)) |
| case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: |
| value := valueHelp.asUint64(val) |
| return MultipleOfUint(path, in, value, uint64(multipleOf)) |
| case reflect.Float32, reflect.Float64: |
| fallthrough |
| default: |
| value := valueHelp.asFloat64(val) |
| return MultipleOf(path, in, value, multipleOf) |
| } |
| } |
| |
| // IsValueValidAgainstRange checks that a numeric value is compatible with |
| // the range defined by Type and Format, that is, may be converted without loss. |
| // |
| // NOTE: this check is about type capacity and not formal verification such as: 1.0 != 1L |
| func IsValueValidAgainstRange(val interface{}, typeName, format, prefix, path string) error { |
| kind := reflect.ValueOf(val).Type().Kind() |
| |
| // What is the string representation of val |
| stringRep := "" |
| switch kind { |
| case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: |
| stringRep = swag.FormatUint64(valueHelp.asUint64(val)) |
| case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
| stringRep = swag.FormatInt64(valueHelp.asInt64(val)) |
| case reflect.Float32, reflect.Float64: |
| stringRep = swag.FormatFloat64(valueHelp.asFloat64(val)) |
| default: |
| return fmt.Errorf("%s value number range checking called with invalid (non numeric) val type in %s", prefix, path) |
| } |
| |
| var errVal error |
| |
| switch typeName { |
| case "integer": |
| switch format { |
| case "int32": |
| _, errVal = swag.ConvertInt32(stringRep) |
| case "uint32": |
| _, errVal = swag.ConvertUint32(stringRep) |
| case "uint64": |
| _, errVal = swag.ConvertUint64(stringRep) |
| case "int64": |
| fallthrough |
| default: |
| _, errVal = swag.ConvertInt64(stringRep) |
| } |
| case "number": |
| fallthrough |
| default: |
| switch format { |
| case "float", "float32": |
| _, errVal = swag.ConvertFloat32(stringRep) |
| case "double", "float64": |
| fallthrough |
| default: |
| // No check can be performed here since |
| // no number beyond float64 is supported |
| } |
| } |
| if errVal != nil { // We don't report the actual errVal from strconv |
| if format != "" { |
| errVal = fmt.Errorf("%s value must be of type %s with format %s in %s", prefix, typeName, format, path) |
| } else { |
| errVal = fmt.Errorf("%s value must be of type %s (default format) in %s", prefix, typeName, path) |
| } |
| } |
| return errVal |
| } |