mirror of
https://github.com/kataras/iris.git
synced 2026-01-06 19:47:05 +00:00
improvements to the x/errors pkg
This commit is contained in:
@@ -1,56 +1,32 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"math"
|
||||
"regexp"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AsValidationErrors reports wheether the given "err" is a type of validation error(s).
|
||||
func AsValidationErrors(err error) (ValidationErrors, bool) {
|
||||
if err == nil {
|
||||
return nil, false
|
||||
}
|
||||
// ValidationError is an interface which IF
|
||||
// it custom error types completes, then
|
||||
// it can by mapped to a validation error.
|
||||
//
|
||||
// A validation error(s) can be given by ErrorCodeName's Validation or Err methods.
|
||||
//
|
||||
// Example can be found at:
|
||||
// https://github.com/kataras/iris/tree/master/_examples/routing/http-wire-errors/custom-validation-errors
|
||||
type ValidationError interface {
|
||||
error
|
||||
|
||||
switch e := err.(type) {
|
||||
case ValidationError:
|
||||
return ValidationErrors{e}, true
|
||||
case ValidationErrors:
|
||||
return e, true
|
||||
case *ValidationErrors:
|
||||
return *e, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
GetField() string
|
||||
GetValue() interface{}
|
||||
GetReason() string
|
||||
}
|
||||
|
||||
// ValueValidator is a generic interface which can be used to check if the value is valid for insert (or for comparison inside another validation step).
|
||||
// Useful for enums.
|
||||
// Should return a non-empty string on validation error, that string is the failure reason.
|
||||
type ValueValidator interface {
|
||||
Validate() string
|
||||
}
|
||||
|
||||
// ValidationError describes a field validation error.
|
||||
type ValidationError struct {
|
||||
Field string `json:"field" yaml:"Field"`
|
||||
Value interface{} `json:"value" yaml:"Value"`
|
||||
Reason string `json:"reason" yaml:"Reason"`
|
||||
}
|
||||
|
||||
// Error completes the standard error interface.
|
||||
func (e ValidationError) Error() string {
|
||||
return sprintf("field %q got invalid value of %v: reason: %s", e.Field, e.Value, e.Reason)
|
||||
}
|
||||
|
||||
// ValidationErrors is just a custom type of ValidationError slice.
|
||||
type ValidationErrors []ValidationError
|
||||
|
||||
// Error completes the error interface.
|
||||
func (e ValidationErrors) Error() string {
|
||||
func (errs ValidationErrors) Error() string {
|
||||
var buf strings.Builder
|
||||
for i, err := range e {
|
||||
for i, err := range errs {
|
||||
buf.WriteByte('[')
|
||||
buf.WriteString(strconv.Itoa(i))
|
||||
buf.WriteByte(']')
|
||||
@@ -58,7 +34,7 @@ func (e ValidationErrors) Error() string {
|
||||
|
||||
buf.WriteString(err.Error())
|
||||
|
||||
if i < len(e)-1 {
|
||||
if i < len(errs)-1 {
|
||||
buf.WriteByte(',')
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
@@ -67,115 +43,122 @@ func (e ValidationErrors) Error() string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Is reports whether the given "err" is a type of validation error or validation errors.
|
||||
func (e ValidationErrors) Is(err error) bool {
|
||||
// ValidationErrorMapper is the interface which
|
||||
// custom validation error mappers should complete.
|
||||
type ValidationErrorMapper interface {
|
||||
// The implementation must check the given "err"
|
||||
// and make decision if it's an error of validation
|
||||
// and if so it should return the value (err or another one)
|
||||
// and true as the last output argument.
|
||||
//
|
||||
// Outputs:
|
||||
// 1. the validation error(s) value
|
||||
// 2. true if the interface{} is an array, otherise false
|
||||
// 3. true if it's a validation error or false if not.
|
||||
MapValidationErrors(err error) (interface{}, bool, bool)
|
||||
}
|
||||
|
||||
// ValidationErrorMapperFunc is an "ValidationErrorMapper" but in type of a function.
|
||||
type ValidationErrorMapperFunc func(err error) (interface{}, bool, bool)
|
||||
|
||||
// MapValidationErrors completes the "ValidationErrorMapper" interface.
|
||||
func (v ValidationErrorMapperFunc) MapValidationErrors(err error) (interface{}, bool, bool) {
|
||||
return v(err)
|
||||
}
|
||||
|
||||
// read-only at serve time, holds the validation error mappers.
|
||||
var validationErrorMappers []ValidationErrorMapper = []ValidationErrorMapper{
|
||||
ValidationErrorMapperFunc(func(err error) (interface{}, bool, bool) {
|
||||
switch e := err.(type) {
|
||||
case ValidationError:
|
||||
return e, false, true
|
||||
case ValidationErrors:
|
||||
return e, true, true
|
||||
default:
|
||||
return nil, false, false
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
// RegisterValidationErrorMapper registers a custom
|
||||
// implementation of validation error mapping.
|
||||
// Call it on program initilization, main() or init() functions.
|
||||
func RegisterValidationErrorMapper(m ValidationErrorMapper) {
|
||||
validationErrorMappers = append(validationErrorMappers, m)
|
||||
}
|
||||
|
||||
// RegisterValidationErrorMapperFunc registers a custom
|
||||
// function implementation of validation error mapping.
|
||||
// Call it on program initilization, main() or init() functions.
|
||||
func RegisterValidationErrorMapperFunc(fn func(err error) (interface{}, bool, bool)) {
|
||||
validationErrorMappers = append(validationErrorMappers, ValidationErrorMapperFunc(fn))
|
||||
}
|
||||
|
||||
type validationErrorTypeMapper struct {
|
||||
types []reflect.Type
|
||||
}
|
||||
|
||||
var _ ValidationErrorMapper = (*validationErrorTypeMapper)(nil)
|
||||
|
||||
func (v *validationErrorTypeMapper) MapValidationErrors(err error) (interface{}, bool, bool) {
|
||||
errType := reflect.TypeOf(err)
|
||||
for _, typ := range v.types {
|
||||
if equalTypes(errType, typ) {
|
||||
return err, false, true
|
||||
}
|
||||
|
||||
// a slice is given but the underline type is registered.
|
||||
if errType.Kind() == reflect.Slice {
|
||||
if equalTypes(errType.Elem(), typ) {
|
||||
return err, true, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false, false
|
||||
}
|
||||
|
||||
func equalTypes(err reflect.Type, binding reflect.Type) bool {
|
||||
return err == binding
|
||||
// return binding.AssignableTo(err)
|
||||
}
|
||||
|
||||
// NewValidationErrorTypeMapper returns a validation error mapper
|
||||
// which compares the error with one or more of the given "types",
|
||||
// through reflection. Each of the given types MUST complete the
|
||||
// standard error type, so it can be passed through the error code.
|
||||
func NewValidationErrorTypeMapper(types ...error) ValidationErrorMapper {
|
||||
typs := make([]reflect.Type, 0, len(types))
|
||||
for _, typ := range types {
|
||||
v, ok := typ.(reflect.Type)
|
||||
if !ok {
|
||||
v = reflect.TypeOf(typ)
|
||||
}
|
||||
|
||||
typs = append(typs, v)
|
||||
}
|
||||
|
||||
return &validationErrorTypeMapper{
|
||||
types: typs,
|
||||
}
|
||||
}
|
||||
|
||||
// AsValidationErrors reports wheether the given "err" is a type of validation error(s).
|
||||
// Its behavior can be modified before serve-time
|
||||
// through the "RegisterValidationErrorMapper" function.
|
||||
func AsValidationErrors(err error) (interface{}, bool) {
|
||||
if err == nil {
|
||||
return false
|
||||
return nil, false
|
||||
}
|
||||
|
||||
switch err.(type) {
|
||||
case ValidationError:
|
||||
return true
|
||||
case *ValidationError:
|
||||
return true
|
||||
case ValidationErrors:
|
||||
return true
|
||||
case *ValidationErrors:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
for _, m := range validationErrorMappers {
|
||||
if errs, isArray, ok := m.MapValidationErrors(err); ok {
|
||||
if !isArray { // ensure always-array on Validation field of the http error.
|
||||
return []interface{}{errs}, true
|
||||
}
|
||||
return errs, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add is a helper for appending a validation error.
|
||||
func (e *ValidationErrors) Add(err ValidationError) *ValidationErrors {
|
||||
if err.Field == "" || err.Reason == "" {
|
||||
return e
|
||||
}
|
||||
|
||||
*e = append(*e, err)
|
||||
return e
|
||||
}
|
||||
|
||||
// Join joins an existing Errors to this errors list.
|
||||
func (e *ValidationErrors) Join(errs ValidationErrors) *ValidationErrors {
|
||||
*e = append(*e, errs...)
|
||||
return e
|
||||
}
|
||||
|
||||
// Validate returns the result of the value's Validate method, if exists otherwise
|
||||
// it adds the field and value to the error list and reports false (invalidated).
|
||||
// If reason is empty, means that the field is valid, this method will return true.
|
||||
func (e *ValidationErrors) Validate(field string, value interface{}) bool {
|
||||
var reason string
|
||||
|
||||
if v, ok := value.(ValueValidator); ok {
|
||||
reason = v.Validate()
|
||||
}
|
||||
|
||||
if reason != "" {
|
||||
e.Add(ValidationError{
|
||||
Field: field,
|
||||
Value: value,
|
||||
Reason: reason,
|
||||
})
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// MustBeSatisfiedFunc compares the value with the given "isEqualFunc" function and reports
|
||||
// if it's valid or not. If it's not valid, a new ValidationError is added to the "e" list.
|
||||
func (e *ValidationErrors) MustBeSatisfiedFunc(field string, value string, isEqualFunc func(string) bool) bool {
|
||||
if !isEqualFunc(value) {
|
||||
e.Add(ValidationError{
|
||||
Field: field,
|
||||
Value: value,
|
||||
Reason: "failed to satisfy constraint",
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// MustBeSatisfied compares the value with the given regex and reports
|
||||
// if it's valid or not. If it's not valid, a new ValidationError is added to the "e" list.
|
||||
func (e *ValidationErrors) MustBeSatisfied(field string, value string, regex *regexp.Regexp) bool {
|
||||
return e.MustBeSatisfiedFunc(field, value, regex.MatchString)
|
||||
}
|
||||
|
||||
// MustBeNotEmptyString reports and fails if the given "value" is empty.
|
||||
func (e *ValidationErrors) MustBeNotEmptyString(field string, value string) bool {
|
||||
if strings.TrimSpace(value) == "" {
|
||||
e.Add(ValidationError{
|
||||
Field: field,
|
||||
Value: value,
|
||||
Reason: "must be not an empty string",
|
||||
})
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// MustBeInRangeString reports whether the "value" is in range of min and max.
|
||||
func (e *ValidationErrors) MustBeInRangeString(field string, value string, minIncluding, maxIncluding int) bool {
|
||||
if maxIncluding <= 0 {
|
||||
maxIncluding = math.MaxInt32
|
||||
}
|
||||
|
||||
if len(value) < minIncluding || len(value) > maxIncluding {
|
||||
e.Add(ValidationError{
|
||||
Field: field,
|
||||
Value: value,
|
||||
Reason: sprintf("characters length must be between %d and %d", minIncluding, maxIncluding),
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user