2022-02-24 22:49:46 +01:00
|
|
|
package errors
|
|
|
|
|
|
|
|
import (
|
2022-03-03 19:55:28 +01:00
|
|
|
"reflect"
|
2022-02-24 22:49:46 +01:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2022-03-03 19:55:28 +01:00
|
|
|
// 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:
|
2022-06-17 21:03:18 +02:00
|
|
|
//
|
|
|
|
// https://github.com/kataras/iris/tree/master/_examples/routing/http-wire-errors/custom-validation-errors
|
2022-03-03 19:55:28 +01:00
|
|
|
type ValidationError interface {
|
|
|
|
error
|
|
|
|
|
|
|
|
GetField() string
|
|
|
|
GetValue() interface{}
|
|
|
|
GetReason() string
|
2022-02-24 22:49:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type ValidationErrors []ValidationError
|
|
|
|
|
2022-03-03 19:55:28 +01:00
|
|
|
func (errs ValidationErrors) Error() string {
|
2022-02-24 22:49:46 +01:00
|
|
|
var buf strings.Builder
|
2022-03-03 19:55:28 +01:00
|
|
|
for i, err := range errs {
|
2022-02-24 22:49:46 +01:00
|
|
|
buf.WriteByte('[')
|
|
|
|
buf.WriteString(strconv.Itoa(i))
|
|
|
|
buf.WriteByte(']')
|
|
|
|
buf.WriteByte(' ')
|
|
|
|
|
|
|
|
buf.WriteString(err.Error())
|
|
|
|
|
2022-03-03 19:55:28 +01:00
|
|
|
if i < len(errs)-1 {
|
2022-02-24 22:49:46 +01:00
|
|
|
buf.WriteByte(',')
|
|
|
|
buf.WriteByte(' ')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf.String()
|
|
|
|
}
|
|
|
|
|
2022-03-03 19:55:28 +01:00
|
|
|
// 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)
|
2022-02-24 22:49:46 +01:00
|
|
|
}
|
|
|
|
|
2022-03-03 19:55:28 +01:00
|
|
|
// ValidationErrorMapperFunc is an "ValidationErrorMapper" but in type of a function.
|
|
|
|
type ValidationErrorMapperFunc func(err error) (interface{}, bool, bool)
|
2022-02-24 22:49:46 +01:00
|
|
|
|
2022-03-03 19:55:28 +01:00
|
|
|
// MapValidationErrors completes the "ValidationErrorMapper" interface.
|
|
|
|
func (v ValidationErrorMapperFunc) MapValidationErrors(err error) (interface{}, bool, bool) {
|
|
|
|
return v(err)
|
2022-02-24 22:49:46 +01:00
|
|
|
}
|
|
|
|
|
2022-03-03 19:55:28 +01:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}),
|
2022-02-24 22:49:46 +01:00
|
|
|
}
|
|
|
|
|
2022-03-03 19:55:28 +01:00
|
|
|
// 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)
|
|
|
|
}
|
2022-02-24 22:49:46 +01:00
|
|
|
|
2022-03-03 19:55:28 +01:00
|
|
|
// 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))
|
|
|
|
}
|
2022-02-24 22:49:46 +01:00
|
|
|
|
2022-03-03 19:55:28 +01:00
|
|
|
type validationErrorTypeMapper struct {
|
|
|
|
types []reflect.Type
|
|
|
|
}
|
2022-02-24 22:49:46 +01:00
|
|
|
|
2022-03-03 19:55:28 +01:00
|
|
|
var _ ValidationErrorMapper = (*validationErrorTypeMapper)(nil)
|
2022-02-24 22:49:46 +01:00
|
|
|
|
2022-03-03 19:55:28 +01:00
|
|
|
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
|
|
|
|
}
|
2022-02-24 22:49:46 +01:00
|
|
|
|
2022-03-03 19:55:28 +01:00
|
|
|
// a slice is given but the underline type is registered.
|
|
|
|
if errType.Kind() == reflect.Slice {
|
|
|
|
if equalTypes(errType.Elem(), typ) {
|
|
|
|
return err, true, true
|
|
|
|
}
|
|
|
|
}
|
2022-02-24 22:49:46 +01:00
|
|
|
}
|
|
|
|
|
2022-03-03 19:55:28 +01:00
|
|
|
return nil, false, false
|
2022-02-24 22:49:46 +01:00
|
|
|
}
|
|
|
|
|
2022-03-03 19:55:28 +01:00
|
|
|
func equalTypes(err reflect.Type, binding reflect.Type) bool {
|
|
|
|
return err == binding
|
|
|
|
// return binding.AssignableTo(err)
|
2022-02-24 22:49:46 +01:00
|
|
|
}
|
|
|
|
|
2022-03-03 19:55:28 +01:00
|
|
|
// 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)
|
|
|
|
}
|
2022-02-24 22:49:46 +01:00
|
|
|
|
2022-03-03 19:55:28 +01:00
|
|
|
typs = append(typs, v)
|
2022-02-24 22:49:46 +01:00
|
|
|
}
|
|
|
|
|
2022-03-03 19:55:28 +01:00
|
|
|
return &validationErrorTypeMapper{
|
|
|
|
types: typs,
|
|
|
|
}
|
2022-02-24 22:49:46 +01:00
|
|
|
}
|
|
|
|
|
2022-03-03 19:55:28 +01:00
|
|
|
// 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 nil, false
|
2022-02-24 22:49:46 +01:00
|
|
|
}
|
|
|
|
|
2022-03-03 19:55:28 +01:00
|
|
|
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
|
|
|
|
}
|
2022-02-24 22:49:46 +01:00
|
|
|
}
|
|
|
|
|
2022-03-03 19:55:28 +01:00
|
|
|
return nil, false
|
2022-02-24 22:49:46 +01:00
|
|
|
}
|