Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
641 lines
18 KiB
641 lines
18 KiB
// 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 |
|
} |
|
|
|
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)) |
|
|
|
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)) |
|
} |
|
} |
|
} |
|
|
|
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)) |
|
} |
|
} |
|
} |
|
|
|
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, "string", 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 |
|
}
|
|
|