Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package validator

import (
"bytes"
"errors"
"fmt"
"reflect"
"strings"
Expand Down Expand Up @@ -50,6 +51,20 @@ func (ve ValidationErrors) Error() string {
return strings.TrimSpace(buff.String())
}

// Unwrap returns the nested errors reported by individual field validations.
func (ve ValidationErrors) Unwrap() []error {
errs := make([]error, 0, len(ve))
for _, fe := range ve {
if err := errors.Unwrap(fe); err != nil {
errs = append(errs, err)
}
}
if len(errs) == 0 {
return nil
}
return errs
}

// Translate translates all of the ValidationErrors
func (ve ValidationErrors) Translate(ut ut.Translator) ValidationErrorsTranslations {
trans := make(ValidationErrorsTranslations)
Expand Down Expand Up @@ -174,6 +189,7 @@ type fieldError struct {
param string
kind reflect.Kind
typ reflect.Type
err error
}

// Tag returns the validation tag that failed.
Expand Down Expand Up @@ -248,6 +264,11 @@ func (fe *fieldError) Error() string {
return fmt.Sprintf(fieldErrMsg, fe.ns, fe.Field(), fe.tag)
}

// Unwrap returns the underlying error reported by the validation, if any.
func (fe *fieldError) Unwrap() error {
return fe.err
}

// Translate returns the FieldError's translated error
// from the provided 'ut.Translator' and registered 'TranslationFunc'
//
Expand Down
9 changes: 8 additions & 1 deletion no_validate_fn.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ func isValidateFn(fl FieldLevel) bool {

ok, err := tryCallValidateFn(field, validateFn)
if err != nil {
if v, ok := fl.(*validate); ok {
v.fieldErr = err
}
return false
}

Expand Down Expand Up @@ -48,7 +51,11 @@ func tryCallValidateFn(field reflect.Value, validateFn string) (bool, error) {
errorType := reflect.TypeOf((*error)(nil)).Elem()

if firstReturnValue.Type().Implements(errorType) {
return firstReturnValue.IsNil(), nil
if firstReturnValue.IsNil() {
return true, nil
}

return false, firstReturnValue.Interface().(error)
}

return false, fmt.Errorf("unable to use result of method %q on type %q: %w (got interface %v expect error)",
Expand Down
3 changes: 3 additions & 0 deletions validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type validate struct {
errs ValidationErrors
includeExclude map[string]struct{} // reset only if StructPartial or StructExcept are called, no need otherwise
ffn FilterFunc
fieldErr error
slflParent reflect.Value // StructLevel & FieldLevel
slCurrent reflect.Value // StructLevel & FieldLevel
flField reflect.Value // StructLevel & FieldLevel
Expand Down Expand Up @@ -462,6 +463,7 @@ OUTER:
default:

// set Field Level fields
v.fieldErr = nil
v.slflParent = parent
v.flField = current
v.cf = cf
Expand Down Expand Up @@ -489,6 +491,7 @@ OUTER:
param: ct.param,
kind: kind,
typ: typ,
err: v.fieldErr,
},
)

Expand Down
30 changes: 30 additions & 0 deletions validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15644,6 +15644,14 @@ func (r NotRed) DoNothing() {}

func (r NotRed) String() string { return "not red instance" }

var errValidateFnFailed = errors.New("validate function failed")

type ValidateFnFailed struct{}

func (ValidateFnFailed) Validate() error {
return errValidateFnFailed
}

func TestValidateFn(t *testing.T) {
t.Run("using pointer", func(t *testing.T) {
validate := New()
Expand Down Expand Up @@ -15733,6 +15741,28 @@ func TestValidateFn(t *testing.T) {
Equal(t, fe.Tag(), "validateFn")
})

t.Run("returned error can be unwrapped", func(t *testing.T) {
validate := New()

type Test struct {
Inner ValidateFnFailed `validate:"validateFn"`
}

err := validate.Struct(&Test{})
NotEqual(t, err, nil)
Equal(t, errors.Is(err, errValidateFnFailed), true)

errs := err.(ValidationErrors)
Equal(t, len(errs), 1)

fe := errs[0]
Equal(t, fe.Field(), "Inner")
Equal(t, fe.Namespace(), "Test.Inner")
Equal(t, fe.Tag(), "validateFn")
Equal(t, errors.Is(fe, errValidateFnFailed), true)
Equal(t, errors.Unwrap(fe), errValidateFnFailed)
})

t.Run("try validate method with wrong signature or not existent", func(t *testing.T) {
validate := New()

Expand Down
Loading