// 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" "github.com/go-openapi/errors" "github.com/go-openapi/spec" "github.com/go-openapi/strfmt" ) // An EntityValidator is an interface for things that can validate entities type EntityValidator interface { Validate(interface{}) *Result } type valueValidator interface { SetPath(path string) Applies(interface{}, reflect.Kind) bool Validate(interface{}) *Result } type itemsValidator struct { items *spec.Items root interface{} path string in string validators [6]valueValidator KnownFormats strfmt.Registry Options *SchemaValidatorOptions } func newItemsValidator(path, in string, items *spec.Items, root interface{}, formats strfmt.Registry, opts *SchemaValidatorOptions) *itemsValidator { if opts == nil { opts = new(SchemaValidatorOptions) } var iv *itemsValidator if opts.recycleValidators { iv = pools.poolOfItemsValidators.BorrowValidator() } else { iv = new(itemsValidator) } iv.path = path iv.in = in iv.items = items iv.root = root iv.KnownFormats = formats iv.Options = opts iv.validators = [6]valueValidator{ iv.typeValidator(), iv.stringValidator(), iv.formatValidator(), iv.numberValidator(), iv.sliceValidator(), iv.commonValidator(), } return iv } func (i *itemsValidator) Validate(index int, data interface{}) *Result { if i.Options.recycleValidators { defer func() { i.redeemChildren() i.redeem() }() } tpe := reflect.TypeOf(data) kind := tpe.Kind() var result *Result if i.Options.recycleResult { result = pools.poolOfResults.BorrowResult() } else { result = new(Result) } path := fmt.Sprintf("%s.%d", i.path, index) for idx, validator := range i.validators { if !validator.Applies(i.root, kind) { if i.Options.recycleValidators { // Validate won't be called, so relinquish this validator if redeemableChildren, ok := validator.(interface{ redeemChildren() }); ok { redeemableChildren.redeemChildren() } if redeemable, ok := validator.(interface{ redeem() }); ok { redeemable.redeem() } i.validators[idx] = nil // prevents further (unsafe) usage } continue } validator.SetPath(path) err := validator.Validate(data) if i.Options.recycleValidators { i.validators[idx] = nil // prevents further (unsafe) usage } if err != nil { result.Inc() if err.HasErrors() { result.Merge(err) break } result.Merge(err) } } return result } func (i *itemsValidator) typeValidator() valueValidator { return newTypeValidator( i.path, i.in, spec.StringOrArray([]string{i.items.Type}), i.items.Nullable, i.items.Format, i.Options, ) } func (i *itemsValidator) commonValidator() valueValidator { return newBasicCommonValidator( "", i.in, i.items.Default, i.items.Enum, i.Options, ) } func (i *itemsValidator) sliceValidator() valueValidator { return newBasicSliceValidator( "", i.in, i.items.Default, i.items.MaxItems, i.items.MinItems, i.items.UniqueItems, i.items.Items, i.root, i.KnownFormats, i.Options, ) } func (i *itemsValidator) numberValidator() valueValidator { return newNumberValidator( "", i.in, i.items.Default, i.items.MultipleOf, i.items.Maximum, i.items.ExclusiveMaximum, i.items.Minimum, i.items.ExclusiveMinimum, i.items.Type, i.items.Format, i.Options, ) } func (i *itemsValidator) stringValidator() valueValidator { return newStringValidator( "", i.in, i.items.Default, false, // Required false, // AllowEmpty i.items.MaxLength, i.items.MinLength, i.items.Pattern, i.Options, ) } func (i *itemsValidator) formatValidator() valueValidator { return newFormatValidator( "", i.in, i.items.Format, i.KnownFormats, i.Options, ) } func (i *itemsValidator) redeem() { pools.poolOfItemsValidators.RedeemValidator(i) } func (i *itemsValidator) redeemChildren() { for idx, validator := range i.validators { if validator == nil { continue } if redeemableChildren, ok := validator.(interface{ redeemChildren() }); ok { redeemableChildren.redeemChildren() } if redeemable, ok := validator.(interface{ redeem() }); ok { redeemable.redeem() } i.validators[idx] = nil // free up allocated children if not in pool } } type basicCommonValidator struct { Path string In string Default interface{} Enum []interface{} Options *SchemaValidatorOptions } func newBasicCommonValidator(path, in string, def interface{}, enum []interface{}, opts *SchemaValidatorOptions) *basicCommonValidator { if opts == nil { opts = new(SchemaValidatorOptions) } var b *basicCommonValidator if opts.recycleValidators { b = pools.poolOfBasicCommonValidators.BorrowValidator() } else { b = new(basicCommonValidator) } b.Path = path b.In = in b.Default = def b.Enum = enum b.Options = opts return b } func (b *basicCommonValidator) SetPath(path string) { b.Path = path } func (b *basicCommonValidator) Applies(source interface{}, _ reflect.Kind) bool { switch source.(type) { case *spec.Parameter, *spec.Schema, *spec.Header: return true default: return false } } func (b *basicCommonValidator) Validate(data interface{}) (res *Result) { if b.Options.recycleValidators { defer func() { b.redeem() }() } if len(b.Enum) == 0 { return nil } for _, enumValue := range b.Enum { actualType := reflect.TypeOf(enumValue) if actualType == nil { // Safeguard continue } expectedValue := reflect.ValueOf(data) if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) && reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), enumValue) { return nil } } return errorHelp.sErr(errors.EnumFail(b.Path, b.In, data, b.Enum), b.Options.recycleResult) } func (b *basicCommonValidator) redeem() { pools.poolOfBasicCommonValidators.RedeemValidator(b) } // A HeaderValidator has very limited subset of validations to apply type HeaderValidator struct { name string header *spec.Header validators [6]valueValidator KnownFormats strfmt.Registry Options *SchemaValidatorOptions } // NewHeaderValidator creates a new header validator object func NewHeaderValidator(name string, header *spec.Header, formats strfmt.Registry, options ...Option) *HeaderValidator { opts := new(SchemaValidatorOptions) for _, o := range options { o(opts) } return newHeaderValidator(name, header, formats, opts) } func newHeaderValidator(name string, header *spec.Header, formats strfmt.Registry, opts *SchemaValidatorOptions) *HeaderValidator { if opts == nil { opts = new(SchemaValidatorOptions) } var p *HeaderValidator if opts.recycleValidators { p = pools.poolOfHeaderValidators.BorrowValidator() } else { p = new(HeaderValidator) } p.name = name p.header = header p.KnownFormats = formats p.Options = opts p.validators = [6]valueValidator{ newTypeValidator( name, "header", spec.StringOrArray([]string{header.Type}), header.Nullable, header.Format, p.Options, ), p.stringValidator(), p.formatValidator(), p.numberValidator(), p.sliceValidator(), p.commonValidator(), } return p } // Validate the value of the header against its schema func (p *HeaderValidator) Validate(data interface{}) *Result { if p.Options.recycleValidators { defer func() { p.redeemChildren() p.redeem() }() } if data == nil { return nil } var result *Result if p.Options.recycleResult { result = pools.poolOfResults.BorrowResult() } else { result = new(Result) } tpe := reflect.TypeOf(data) kind := tpe.Kind() for idx, validator := range p.validators { if !validator.Applies(p.header, kind) { if p.Options.recycleValidators { // Validate won't be called, so relinquish this validator if redeemableChildren, ok := validator.(interface{ redeemChildren() }); ok { redeemableChildren.redeemChildren() } if redeemable, ok := validator.(interface{ redeem() }); ok { redeemable.redeem() } p.validators[idx] = nil // prevents further (unsafe) usage } continue } err := validator.Validate(data) if p.Options.recycleValidators { p.validators[idx] = nil // prevents further (unsafe) usage } if err != nil { if err.HasErrors() { result.Merge(err) break } result.Merge(err) } } return result } func (p *HeaderValidator) commonValidator() valueValidator { return newBasicCommonValidator( p.name, "response", p.header.Default, p.header.Enum, p.Options, ) } func (p *HeaderValidator) sliceValidator() valueValidator { return newBasicSliceValidator( p.name, "response", p.header.Default, p.header.MaxItems, p.header.MinItems, p.header.UniqueItems, p.header.Items, p.header, p.KnownFormats, p.Options, ) } func (p *HeaderValidator) numberValidator() valueValidator { return newNumberValidator( p.name, "response", p.header.Default, p.header.MultipleOf, p.header.Maximum, p.header.ExclusiveMaximum, p.header.Minimum, p.header.ExclusiveMinimum, p.header.Type, p.header.Format, p.Options, ) } func (p *HeaderValidator) stringValidator() valueValidator { return newStringValidator( p.name, "response", p.header.Default, true, false, p.header.MaxLength, p.header.MinLength, p.header.Pattern, p.Options, ) } func (p *HeaderValidator) formatValidator() valueValidator { return newFormatValidator( p.name, "response", p.header.Format, p.KnownFormats, p.Options, ) } func (p *HeaderValidator) redeem() { pools.poolOfHeaderValidators.RedeemValidator(p) } func (p *HeaderValidator) redeemChildren() { for idx, validator := range p.validators { if validator == nil { continue } if redeemableChildren, ok := validator.(interface{ redeemChildren() }); ok { redeemableChildren.redeemChildren() } if redeemable, ok := validator.(interface{ redeem() }); ok { redeemable.redeem() } p.validators[idx] = nil // free up allocated children if not in pool } } // A ParamValidator has very limited subset of validations to apply type ParamValidator struct { param *spec.Parameter validators [6]valueValidator KnownFormats strfmt.Registry Options *SchemaValidatorOptions } // NewParamValidator creates a new param validator object func NewParamValidator(param *spec.Parameter, formats strfmt.Registry, options ...Option) *ParamValidator { opts := new(SchemaValidatorOptions) for _, o := range options { o(opts) } return newParamValidator(param, formats, opts) } func newParamValidator(param *spec.Parameter, formats strfmt.Registry, opts *SchemaValidatorOptions) *ParamValidator { if opts == nil { opts = new(SchemaValidatorOptions) } var p *ParamValidator if opts.recycleValidators { p = pools.poolOfParamValidators.BorrowValidator() } else { p = new(ParamValidator) } p.param = param p.KnownFormats = formats p.Options = opts p.validators = [6]valueValidator{ newTypeValidator( param.Name, param.In, spec.StringOrArray([]string{param.Type}), param.Nullable, param.Format, p.Options, ), p.stringValidator(), p.formatValidator(), p.numberValidator(), p.sliceValidator(), p.commonValidator(), } return p } // Validate the data against the description of the parameter func (p *ParamValidator) Validate(data interface{}) *Result { if data == nil { return nil } var result *Result if p.Options.recycleResult { result = pools.poolOfResults.BorrowResult() } else { result = new(Result) } tpe := reflect.TypeOf(data) kind := tpe.Kind() if p.Options.recycleValidators { defer func() { p.redeemChildren() p.redeem() }() } // TODO: validate type for idx, validator := range p.validators { if !validator.Applies(p.param, kind) { if p.Options.recycleValidators { // Validate won't be called, so relinquish this validator if redeemableChildren, ok := validator.(interface{ redeemChildren() }); ok { redeemableChildren.redeemChildren() } if redeemable, ok := validator.(interface{ redeem() }); ok { redeemable.redeem() } p.validators[idx] = nil // prevents further (unsafe) usage } continue } err := validator.Validate(data) if p.Options.recycleValidators { p.validators[idx] = nil // prevents further (unsafe) usage } if err != nil { if err.HasErrors() { result.Merge(err) break } result.Merge(err) } } return result } func (p *ParamValidator) commonValidator() valueValidator { return newBasicCommonValidator( p.param.Name, p.param.In, p.param.Default, p.param.Enum, p.Options, ) } func (p *ParamValidator) sliceValidator() valueValidator { return newBasicSliceValidator( p.param.Name, p.param.In, p.param.Default, p.param.MaxItems, p.param.MinItems, p.param.UniqueItems, p.param.Items, p.param, p.KnownFormats, p.Options, ) } func (p *ParamValidator) numberValidator() valueValidator { return newNumberValidator( p.param.Name, p.param.In, p.param.Default, p.param.MultipleOf, p.param.Maximum, p.param.ExclusiveMaximum, p.param.Minimum, p.param.ExclusiveMinimum, p.param.Type, p.param.Format, p.Options, ) } func (p *ParamValidator) stringValidator() valueValidator { return newStringValidator( p.param.Name, p.param.In, p.param.Default, p.param.Required, p.param.AllowEmptyValue, p.param.MaxLength, p.param.MinLength, p.param.Pattern, p.Options, ) } func (p *ParamValidator) formatValidator() valueValidator { return newFormatValidator( p.param.Name, p.param.In, p.param.Format, p.KnownFormats, p.Options, ) } func (p *ParamValidator) redeem() { pools.poolOfParamValidators.RedeemValidator(p) } func (p *ParamValidator) redeemChildren() { for idx, validator := range p.validators { if validator == nil { continue } if redeemableChildren, ok := validator.(interface{ redeemChildren() }); ok { redeemableChildren.redeemChildren() } if redeemable, ok := validator.(interface{ redeem() }); ok { redeemable.redeem() } p.validators[idx] = nil // free up allocated children if not in pool } } type basicSliceValidator struct { Path string In string Default interface{} MaxItems *int64 MinItems *int64 UniqueItems bool Items *spec.Items Source interface{} KnownFormats strfmt.Registry Options *SchemaValidatorOptions } func newBasicSliceValidator( path, in string, def interface{}, maxItems, minItems *int64, uniqueItems bool, items *spec.Items, source interface{}, formats strfmt.Registry, opts *SchemaValidatorOptions) *basicSliceValidator { if opts == nil { opts = new(SchemaValidatorOptions) } var s *basicSliceValidator if opts.recycleValidators { s = pools.poolOfBasicSliceValidators.BorrowValidator() } else { s = new(basicSliceValidator) } s.Path = path s.In = in s.Default = def s.MaxItems = maxItems s.MinItems = minItems s.UniqueItems = uniqueItems s.Items = items s.Source = source s.KnownFormats = formats s.Options = opts return s } func (s *basicSliceValidator) SetPath(path string) { s.Path = path } func (s *basicSliceValidator) Applies(source interface{}, kind reflect.Kind) bool { switch source.(type) { case *spec.Parameter, *spec.Items, *spec.Header: return kind == reflect.Slice default: return false } } func (s *basicSliceValidator) Validate(data interface{}) *Result { if s.Options.recycleValidators { defer func() { s.redeem() }() } val := reflect.ValueOf(data) size := int64(val.Len()) if s.MinItems != nil { if err := MinItems(s.Path, s.In, size, *s.MinItems); err != nil { return errorHelp.sErr(err, s.Options.recycleResult) } } if s.MaxItems != nil { if err := MaxItems(s.Path, s.In, size, *s.MaxItems); err != nil { return errorHelp.sErr(err, s.Options.recycleResult) } } if s.UniqueItems { if err := UniqueItems(s.Path, s.In, data); err != nil { return errorHelp.sErr(err, s.Options.recycleResult) } } if s.Items == nil { return nil } for i := 0; i < int(size); i++ { itemsValidator := newItemsValidator(s.Path, s.In, s.Items, s.Source, s.KnownFormats, s.Options) ele := val.Index(i) if err := itemsValidator.Validate(i, ele.Interface()); err != nil { if err.HasErrors() { return err } if err.wantsRedeemOnMerge { pools.poolOfResults.RedeemResult(err) } } } return nil } func (s *basicSliceValidator) redeem() { pools.poolOfBasicSliceValidators.RedeemValidator(s) } type numberValidator struct { Path string In string Default interface{} MultipleOf *float64 Maximum *float64 ExclusiveMaximum bool Minimum *float64 ExclusiveMinimum bool // Allows for more accurate behavior regarding integers Type string Format string Options *SchemaValidatorOptions } func newNumberValidator( path, in string, def interface{}, multipleOf, maximum *float64, exclusiveMaximum bool, minimum *float64, exclusiveMinimum bool, typ, format string, opts *SchemaValidatorOptions) *numberValidator { if opts == nil { opts = new(SchemaValidatorOptions) } var n *numberValidator if opts.recycleValidators { n = pools.poolOfNumberValidators.BorrowValidator() } else { n = new(numberValidator) } n.Path = path n.In = in n.Default = def n.MultipleOf = multipleOf n.Maximum = maximum n.ExclusiveMaximum = exclusiveMaximum n.Minimum = minimum n.ExclusiveMinimum = exclusiveMinimum n.Type = typ n.Format = format n.Options = opts return n } func (n *numberValidator) SetPath(path string) { n.Path = path } func (n *numberValidator) Applies(source interface{}, kind reflect.Kind) bool { switch source.(type) { case *spec.Parameter, *spec.Schema, *spec.Items, *spec.Header: isInt := kind >= reflect.Int && kind <= reflect.Uint64 isFloat := kind == reflect.Float32 || kind == reflect.Float64 return isInt || isFloat default: return false } } // Validate provides a validator for generic JSON numbers, // // By default, numbers are internally represented as float64. // Formats float, or float32 may alter this behavior by mapping to float32. // A special validation process is followed for integers, with optional "format": // this is an attempt to provide a validation with native types. // // NOTE: since the constraint specified (boundary, multipleOf) is unmarshalled // as float64, loss of information remains possible (e.g. on very large integers). // // Since this value directly comes from the unmarshalling, it is not possible // at this stage of processing to check further and guarantee the correctness of such values. // // Normally, the JSON Number.MAX_SAFE_INTEGER (resp. Number.MIN_SAFE_INTEGER) // would check we do not get such a loss. // // If this is the case, replace AddErrors() by AddWarnings() and IsValid() by !HasWarnings(). // // TODO: consider replacing boundary check errors by simple warnings. // // TODO: default boundaries with MAX_SAFE_INTEGER are not checked (specific to json.Number?) func (n *numberValidator) Validate(val interface{}) *Result { if n.Options.recycleValidators { defer func() { n.redeem() }() } var res, resMultiple, resMinimum, resMaximum *Result if n.Options.recycleResult { res = pools.poolOfResults.BorrowResult() } else { res = new(Result) } // Used only to attempt to validate constraint on value, // even though value or constraint specified do not match type and format data := valueHelp.asFloat64(val) // Is the provided value within the range of the specified numeric type and format? res.AddErrors(IsValueValidAgainstRange(val, n.Type, n.Format, "Checked", n.Path)) if n.MultipleOf != nil { resMultiple = pools.poolOfResults.BorrowResult() // Is the constraint specifier within the range of the specific numeric type and format? resMultiple.AddErrors(IsValueValidAgainstRange(*n.MultipleOf, n.Type, n.Format, "MultipleOf", n.Path)) if resMultiple.IsValid() { // Constraint validated with compatible types if err := MultipleOfNativeType(n.Path, n.In, val, *n.MultipleOf); err != nil { resMultiple.Merge(errorHelp.sErr(err, n.Options.recycleResult)) } } else { // Constraint nevertheless validated, converted as general number if err := MultipleOf(n.Path, n.In, data, *n.MultipleOf); err != nil { resMultiple.Merge(errorHelp.sErr(err, n.Options.recycleResult)) } } } if n.Maximum != nil { resMaximum = pools.poolOfResults.BorrowResult() // Is the constraint specifier within the range of the specific numeric type and format? resMaximum.AddErrors(IsValueValidAgainstRange(*n.Maximum, n.Type, n.Format, "Maximum boundary", n.Path)) if resMaximum.IsValid() { // Constraint validated with compatible types if err := MaximumNativeType(n.Path, n.In, val, *n.Maximum, n.ExclusiveMaximum); err != nil { resMaximum.Merge(errorHelp.sErr(err, n.Options.recycleResult)) } } else { // Constraint nevertheless validated, converted as general number if err := Maximum(n.Path, n.In, data, *n.Maximum, n.ExclusiveMaximum); err != nil { resMaximum.Merge(errorHelp.sErr(err, n.Options.recycleResult)) } } } if n.Minimum != nil { resMinimum = pools.poolOfResults.BorrowResult() // Is the constraint specifier within the range of the specific numeric type and format? resMinimum.AddErrors(IsValueValidAgainstRange(*n.Minimum, n.Type, n.Format, "Minimum boundary", n.Path)) if resMinimum.IsValid() { // Constraint validated with compatible types if err := MinimumNativeType(n.Path, n.In, val, *n.Minimum, n.ExclusiveMinimum); err != nil { resMinimum.Merge(errorHelp.sErr(err, n.Options.recycleResult)) } } else { // Constraint nevertheless validated, converted as general number if err := Minimum(n.Path, n.In, data, *n.Minimum, n.ExclusiveMinimum); err != nil { resMinimum.Merge(errorHelp.sErr(err, n.Options.recycleResult)) } } } res.Merge(resMultiple, resMinimum, resMaximum) res.Inc() return res } func (n *numberValidator) redeem() { pools.poolOfNumberValidators.RedeemValidator(n) } type stringValidator struct { Path string In string Default interface{} Required bool AllowEmptyValue bool MaxLength *int64 MinLength *int64 Pattern string Options *SchemaValidatorOptions } func newStringValidator( path, in string, def interface{}, required, allowEmpty bool, maxLength, minLength *int64, pattern string, opts *SchemaValidatorOptions) *stringValidator { if opts == nil { opts = new(SchemaValidatorOptions) } var s *stringValidator if opts.recycleValidators { s = pools.poolOfStringValidators.BorrowValidator() } else { s = new(stringValidator) } s.Path = path s.In = in s.Default = def s.Required = required s.AllowEmptyValue = allowEmpty s.MaxLength = maxLength s.MinLength = minLength s.Pattern = pattern s.Options = opts return s } func (s *stringValidator) SetPath(path string) { s.Path = path } func (s *stringValidator) Applies(source interface{}, kind reflect.Kind) bool { switch source.(type) { case *spec.Parameter, *spec.Schema, *spec.Items, *spec.Header: return kind == reflect.String default: return false } } func (s *stringValidator) Validate(val interface{}) *Result { if s.Options.recycleValidators { defer func() { s.redeem() }() } data, ok := val.(string) if !ok { return errorHelp.sErr(errors.InvalidType(s.Path, s.In, stringType, val), s.Options.recycleResult) } if s.Required && !s.AllowEmptyValue && (s.Default == nil || s.Default == "") { if err := RequiredString(s.Path, s.In, data); err != nil { return errorHelp.sErr(err, s.Options.recycleResult) } } if s.MaxLength != nil { if err := MaxLength(s.Path, s.In, data, *s.MaxLength); err != nil { return errorHelp.sErr(err, s.Options.recycleResult) } } if s.MinLength != nil { if err := MinLength(s.Path, s.In, data, *s.MinLength); err != nil { return errorHelp.sErr(err, s.Options.recycleResult) } } if s.Pattern != "" { if err := Pattern(s.Path, s.In, data, s.Pattern); err != nil { return errorHelp.sErr(err, s.Options.recycleResult) } } return nil } func (s *stringValidator) redeem() { pools.poolOfStringValidators.RedeemValidator(s) }