// 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 []valueValidator KnownFormats strfmt.Registry } func newItemsValidator(path, in string, items *spec.Items, root interface{}, formats strfmt.Registry) *itemsValidator { iv := &itemsValidator{path: path, in: in, items: items, root: root, KnownFormats: formats} iv.validators = []valueValidator{ &typeValidator{ Type: spec.StringOrArray([]string{items.Type}), Nullable: items.Nullable, Format: items.Format, In: in, Path: path, }, iv.stringValidator(), iv.formatValidator(), iv.numberValidator(), iv.sliceValidator(), iv.commonValidator(), } return iv } func (i *itemsValidator) Validate(index int, data interface{}) *Result { tpe := reflect.TypeOf(data) kind := tpe.Kind() mainResult := new(Result) path := fmt.Sprintf("%s.%d", i.path, index) for _, validator := range i.validators { validator.SetPath(path) if validator.Applies(i.root, kind) { result := validator.Validate(data) mainResult.Merge(result) mainResult.Inc() if result != nil && result.HasErrors() { return mainResult } } } return mainResult } func (i *itemsValidator) commonValidator() valueValidator { return &basicCommonValidator{ In: i.in, Default: i.items.Default, Enum: i.items.Enum, } } func (i *itemsValidator) sliceValidator() valueValidator { return &basicSliceValidator{ In: i.in, Default: i.items.Default, MaxItems: i.items.MaxItems, MinItems: i.items.MinItems, UniqueItems: i.items.UniqueItems, Source: i.root, Items: i.items.Items, KnownFormats: i.KnownFormats, } } func (i *itemsValidator) numberValidator() valueValidator { return &numberValidator{ In: i.in, Default: i.items.Default, MultipleOf: i.items.MultipleOf, Maximum: i.items.Maximum, ExclusiveMaximum: i.items.ExclusiveMaximum, Minimum: i.items.Minimum, ExclusiveMinimum: i.items.ExclusiveMinimum, Type: i.items.Type, Format: i.items.Format, } } func (i *itemsValidator) stringValidator() valueValidator { return &stringValidator{ In: i.in, Default: i.items.Default, MaxLength: i.items.MaxLength, MinLength: i.items.MinLength, Pattern: i.items.Pattern, AllowEmptyValue: false, } } func (i *itemsValidator) formatValidator() valueValidator { return &formatValidator{ In: i.in, //Default: i.items.Default, Format: i.items.Format, KnownFormats: i.KnownFormats, } } type basicCommonValidator struct { Path string In string Default interface{} Enum []interface{} } func (b *basicCommonValidator) SetPath(path string) { b.Path = path } func (b *basicCommonValidator) Applies(source interface{}, kind reflect.Kind) bool { switch source.(type) { case *spec.Parameter, *spec.Schema, *spec.Header: return true } return false } func (b *basicCommonValidator) Validate(data interface{}) (res *Result) { if len(b.Enum) > 0 { for _, enumValue := range b.Enum { actualType := reflect.TypeOf(enumValue) if actualType != nil { // Safeguard expectedValue := reflect.ValueOf(data) if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { if reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), enumValue) { return nil } } } } return errorHelp.sErr(errors.EnumFail(b.Path, b.In, data, b.Enum)) } return nil } // A HeaderValidator has very limited subset of validations to apply type HeaderValidator struct { name string header *spec.Header validators []valueValidator KnownFormats strfmt.Registry } // NewHeaderValidator creates a new header validator object func NewHeaderValidator(name string, header *spec.Header, formats strfmt.Registry) *HeaderValidator { p := &HeaderValidator{name: name, header: header, KnownFormats: formats} p.validators = []valueValidator{ &typeValidator{ Type: spec.StringOrArray([]string{header.Type}), Nullable: header.Nullable, Format: header.Format, In: "header", Path: name, }, 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 { result := new(Result) tpe := reflect.TypeOf(data) kind := tpe.Kind() for _, validator := range p.validators { if validator.Applies(p.header, kind) { if err := validator.Validate(data); err != nil { result.Merge(err) if err.HasErrors() { return result } } } } return nil } func (p *HeaderValidator) commonValidator() valueValidator { return &basicCommonValidator{ Path: p.name, In: "response", Default: p.header.Default, Enum: p.header.Enum, } } func (p *HeaderValidator) sliceValidator() valueValidator { return &basicSliceValidator{ Path: p.name, In: "response", Default: p.header.Default, MaxItems: p.header.MaxItems, MinItems: p.header.MinItems, UniqueItems: p.header.UniqueItems, Items: p.header.Items, Source: p.header, KnownFormats: p.KnownFormats, } } func (p *HeaderValidator) numberValidator() valueValidator { return &numberValidator{ Path: p.name, In: "response", Default: p.header.Default, MultipleOf: p.header.MultipleOf, Maximum: p.header.Maximum, ExclusiveMaximum: p.header.ExclusiveMaximum, Minimum: p.header.Minimum, ExclusiveMinimum: p.header.ExclusiveMinimum, Type: p.header.Type, Format: p.header.Format, } } func (p *HeaderValidator) stringValidator() valueValidator { return &stringValidator{ Path: p.name, In: "response", Default: p.header.Default, Required: true, MaxLength: p.header.MaxLength, MinLength: p.header.MinLength, Pattern: p.header.Pattern, AllowEmptyValue: false, } } func (p *HeaderValidator) formatValidator() valueValidator { return &formatValidator{ Path: p.name, In: "response", //Default: p.header.Default, Format: p.header.Format, KnownFormats: p.KnownFormats, } } // A ParamValidator has very limited subset of validations to apply type ParamValidator struct { param *spec.Parameter validators []valueValidator KnownFormats strfmt.Registry } // NewParamValidator creates a new param validator object func NewParamValidator(param *spec.Parameter, formats strfmt.Registry) *ParamValidator { p := &ParamValidator{param: param, KnownFormats: formats} p.validators = []valueValidator{ &typeValidator{ Type: spec.StringOrArray([]string{param.Type}), Nullable: param.Nullable, Format: param.Format, In: param.In, Path: param.Name, }, 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 { result := new(Result) tpe := reflect.TypeOf(data) kind := tpe.Kind() // TODO: validate type for _, validator := range p.validators { if validator.Applies(p.param, kind) { if err := validator.Validate(data); err != nil { result.Merge(err) if err.HasErrors() { return result } } } } return nil } func (p *ParamValidator) commonValidator() valueValidator { return &basicCommonValidator{ Path: p.param.Name, In: p.param.In, Default: p.param.Default, Enum: p.param.Enum, } } func (p *ParamValidator) sliceValidator() valueValidator { return &basicSliceValidator{ Path: p.param.Name, In: p.param.In, Default: p.param.Default, MaxItems: p.param.MaxItems, MinItems: p.param.MinItems, UniqueItems: p.param.UniqueItems, Items: p.param.Items, Source: p.param, KnownFormats: p.KnownFormats, } } func (p *ParamValidator) numberValidator() valueValidator { return &numberValidator{ Path: p.param.Name, In: p.param.In, Default: p.param.Default, MultipleOf: p.param.MultipleOf, Maximum: p.param.Maximum, ExclusiveMaximum: p.param.ExclusiveMaximum, Minimum: p.param.Minimum, ExclusiveMinimum: p.param.ExclusiveMinimum, Type: p.param.Type, Format: p.param.Format, } } func (p *ParamValidator) stringValidator() valueValidator { return &stringValidator{ Path: p.param.Name, In: p.param.In, Default: p.param.Default, AllowEmptyValue: p.param.AllowEmptyValue, Required: p.param.Required, MaxLength: p.param.MaxLength, MinLength: p.param.MinLength, Pattern: p.param.Pattern, } } func (p *ParamValidator) formatValidator() valueValidator { return &formatValidator{ Path: p.param.Name, In: p.param.In, //Default: p.param.Default, Format: p.param.Format, KnownFormats: p.KnownFormats, } } type basicSliceValidator struct { Path string In string Default interface{} MaxItems *int64 MinItems *int64 UniqueItems bool Items *spec.Items Source interface{} itemsValidator *itemsValidator KnownFormats strfmt.Registry } 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 } return false } func (s *basicSliceValidator) Validate(data interface{}) *Result { 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) } } if s.MaxItems != nil { if err := MaxItems(s.Path, s.In, size, *s.MaxItems); err != nil { return errorHelp.sErr(err) } } if s.UniqueItems { if err := UniqueItems(s.Path, s.In, data); err != nil { return errorHelp.sErr(err) } } if s.itemsValidator == nil && s.Items != nil { s.itemsValidator = newItemsValidator(s.Path, s.In, s.Items, s.Source, s.KnownFormats) } if s.itemsValidator != nil { for i := 0; i < int(size); i++ { ele := val.Index(i) if err := s.itemsValidator.Validate(i, ele.Interface()); err != nil && err.HasErrors() { return err } } } return nil } /* unused func (s *basicSliceValidator) hasDuplicates(value reflect.Value, size int) bool { dict := make(map[interface{}]struct{}) for i := 0; i < size; i++ { ele := value.Index(i) if _, ok := dict[ele.Interface()]; ok { return true } dict[ele.Interface()] = struct{}{} } return false } */ 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 } 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 r := isInt || isFloat debugLog("schema props validator for %q applies %t for %T (kind: %v) isInt=%t, isFloat=%t\n", n.Path, r, source, kind, isInt, isFloat) return r } debugLog("schema props validator for %q applies %t for %T (kind: %v)\n", n.Path, false, source, kind) 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 { res := new(Result) resMultiple := new(Result) resMinimum := new(Result) resMaximum := 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)) // nolint: dupl if n.MultipleOf != nil { // 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)) } } 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)) } } } // nolint: dupl if n.Maximum != nil { // 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)) } } 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)) } } } // nolint: dupl if n.Minimum != nil { // 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)) } } 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)) } } } res.Merge(resMultiple, resMinimum, resMaximum) res.Inc() return res } type stringValidator struct { Default interface{} Required bool AllowEmptyValue bool MaxLength *int64 MinLength *int64 Pattern string Path string In string } 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: r := kind == reflect.String debugLog("string validator for %q applies %t for %T (kind: %v)\n", s.Path, r, source, kind) return r } debugLog("string validator for %q applies %t for %T (kind: %v)\n", s.Path, false, source, kind) return false } func (s *stringValidator) Validate(val interface{}) *Result { data, ok := val.(string) if !ok { return errorHelp.sErr(errors.InvalidType(s.Path, s.In, stringType, val)) } if s.Required && !s.AllowEmptyValue && (s.Default == nil || s.Default == "") { if err := RequiredString(s.Path, s.In, data); err != nil { return errorHelp.sErr(err) } } if s.MaxLength != nil { if err := MaxLength(s.Path, s.In, data, *s.MaxLength); err != nil { return errorHelp.sErr(err) } } if s.MinLength != nil { if err := MinLength(s.Path, s.In, data, *s.MinLength); err != nil { return errorHelp.sErr(err) } } if s.Pattern != "" { if err := Pattern(s.Path, s.In, data, s.Pattern); err != nil { return errorHelp.sErr(err) } } return nil }