// 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)
}