From bb8261801fbc84f6a85d20407a512b49b375f016 Mon Sep 17 00:00:00 2001 From: RussellLuo Date: Wed, 26 Jun 2024 14:19:58 +0800 Subject: [PATCH] Improve error messages --- builtin.go | 95 +++++++++++++++++------------ builtin_test.go | 10 +-- errors.go | 17 +++++- example_customizations_test.go | 3 +- example_nested_struct_map_test.go | 1 + example_nested_struct_slice_test.go | 1 + 6 files changed, 81 insertions(+), 46 deletions(-) diff --git a/builtin.go b/builtin.go index 35214db..b072a8a 100644 --- a/builtin.go +++ b/builtin.go @@ -41,7 +41,8 @@ func Nested[T any](f func(T) Validator) Validator { return Func(func(field *Field) Errors { v, ok := field.Value.(T) if !ok { - return NewUnsupportedErrors(field, "Nested") + var want T + return NewUnsupportedErrors("Nested", field, want) } return f(v).Validate(field) @@ -58,7 +59,8 @@ func EachMap[T map[K]V, K comparable, V any](validator Validator) Validator { return Func(func(field *Field) (errs Errors) { v, ok := field.Value.(T) if !ok { - return NewUnsupportedErrors(field, "EachMap") + var want T + return NewUnsupportedErrors("EachMap", field, want) } for k := range v { @@ -84,7 +86,8 @@ func EachSlice[T ~[]E, E any](validator Validator) Validator { return Func(func(field *Field) (errs Errors) { v, ok := field.Value.(T) if !ok { - return NewUnsupportedErrors(field, "EachSlice") + var want T + return NewUnsupportedErrors("EachSlice", field, want) } for i := range v { @@ -106,7 +109,8 @@ func Map[T map[K]V, K comparable, V any](f func(T) map[K]Validator) Validator { return Func(func(field *Field) (errs Errors) { v, ok := field.Value.(T) if !ok { - return NewUnsupportedErrors(field, "Map") + var want T + return NewUnsupportedErrors("Map", field, want) } validators := f(v) @@ -129,7 +133,8 @@ func Slice[T ~[]E, E any](f func(T) []Validator) Validator { return Func(func(field *Field) (errs Errors) { v, ok := field.Value.(T) if !ok { - return NewUnsupportedErrors(field, "Slice") + var want T + return NewUnsupportedErrors("Slice", field, want) } validators := f(v) @@ -237,7 +242,7 @@ func Not(validator Validator) (mv *MessageValidator) { Validator: Func(func(field *Field) Errors { errs := validator.Validate(field) if len(errs) == 0 { - return NewErrors(field.Name, ErrInvalid, mv.Message) + return NewInvalidErrors(field, mv.Message) } var newErrs Errors @@ -261,11 +266,12 @@ func Is[T any](f func(T) bool) (mv *MessageValidator) { Validator: Func(func(field *Field) Errors { v, ok := field.Value.(T) if !ok { - return NewUnsupportedErrors(field, "Is") + var want T + return NewUnsupportedErrors("Is", field, want) } if !f(v) { - return NewErrors(field.Name, ErrInvalid, mv.Message) + return NewInvalidErrors(field, mv.Message) } return nil }), @@ -281,12 +287,13 @@ func Nonzero[T comparable]() (mv *MessageValidator) { Validator: Func(func(field *Field) Errors { v, ok := field.Value.(T) if !ok { - return NewUnsupportedErrors(field, "Nonzero") + var want T + return NewUnsupportedErrors("Nonzero", field, want) } var zero T if v == zero { - return NewErrors(field.Name, ErrInvalid, mv.Message) + return NewInvalidErrors(field, mv.Message) } return nil }), @@ -302,12 +309,13 @@ func Zero[T comparable]() (mv *MessageValidator) { Validator: Func(func(field *Field) Errors { v, ok := field.Value.(T) if !ok { - return NewUnsupportedErrors(field, "Nonzero") + var want T + return NewUnsupportedErrors("Zero", field, want) } var zero T if v != zero { - return NewErrors(field.Name, ErrInvalid, mv.Message) + return NewInvalidErrors(field, mv.Message) } return nil }), @@ -331,12 +339,13 @@ func LenString(min, max int) (mv *MessageValidator) { Validator: Func(func(field *Field) Errors { v, ok := field.Value.(string) if !ok { - return NewUnsupportedErrors(field, "LenString") + var want string + return NewUnsupportedErrors("LenString", field, want) } l := len(v) if l < min || l > max { - return NewErrors(field.Name, ErrInvalid, mv.Message) + return NewInvalidErrors(field, mv.Message) } return nil }), @@ -352,12 +361,13 @@ func LenSlice[T ~[]E, E any](min, max int) (mv *MessageValidator) { Validator: Func(func(field *Field) Errors { v, ok := field.Value.(T) if !ok { - return NewUnsupportedErrors(field, "LenSlice") + var want T + return NewUnsupportedErrors("LenSlice", field, want) } l := len(v) if l < min || l > max { - return NewErrors(field.Name, ErrInvalid, mv.Message) + return NewInvalidErrors(field, mv.Message) } return nil }), @@ -381,11 +391,11 @@ func RuneCount(min, max int) (mv *MessageValidator) { l := utf8.RuneCount(v) valid = l >= min && l <= max default: - return NewUnsupportedErrors(field, "RuneCount") + return NewUnsupportedErrors("RuneCount", field, "", []byte(nil)) } if !valid { - return NewErrors(field.Name, ErrInvalid, mv.Message) + return NewInvalidErrors(field, mv.Message) } return nil }), @@ -401,11 +411,12 @@ func Eq[T comparable](value T) (mv *MessageValidator) { Validator: Func(func(field *Field) Errors { v, ok := field.Value.(T) if !ok { - return NewUnsupportedErrors(field, "Eq") + var want T + return NewUnsupportedErrors("Eq", field, want) } if v != value { - return NewErrors(field.Name, ErrInvalid, mv.Message) + return NewInvalidErrors(field, mv.Message) } return nil }), @@ -421,11 +432,12 @@ func Ne[T comparable](value T) (mv *MessageValidator) { Validator: Func(func(field *Field) Errors { v, ok := field.Value.(T) if !ok { - return NewUnsupportedErrors(field, "Ne") + var want T + return NewUnsupportedErrors("Ne", field, want) } if v == value { - return NewErrors(field.Name, ErrInvalid, mv.Message) + return NewInvalidErrors(field, mv.Message) } return nil }), @@ -441,11 +453,12 @@ func Gt[T constraints.Ordered](value T) (mv *MessageValidator) { Validator: Func(func(field *Field) Errors { v, ok := field.Value.(T) if !ok { - return NewUnsupportedErrors(field, "Gt") + var want T + return NewUnsupportedErrors("Gt", field, want) } if v <= value { - return NewErrors(field.Name, ErrInvalid, mv.Message) + return NewInvalidErrors(field, mv.Message) } return nil }), @@ -461,11 +474,12 @@ func Gte[T constraints.Ordered](value T) (mv *MessageValidator) { Validator: Func(func(field *Field) Errors { v, ok := field.Value.(T) if !ok { - return NewUnsupportedErrors(field, "Gte") + var want T + return NewUnsupportedErrors("Gte", field, want) } if v < value { - return NewErrors(field.Name, ErrInvalid, mv.Message) + return NewInvalidErrors(field, mv.Message) } return nil }), @@ -481,11 +495,12 @@ func Lt[T constraints.Ordered](value T) (mv *MessageValidator) { Validator: Func(func(field *Field) Errors { v, ok := field.Value.(T) if !ok { - return NewUnsupportedErrors(field, "Lt") + var want T + return NewUnsupportedErrors("Lt", field, want) } if v >= value { - return NewErrors(field.Name, ErrInvalid, mv.Message) + return NewInvalidErrors(field, mv.Message) } return nil }), @@ -501,11 +516,12 @@ func Lte[T constraints.Ordered](value T) (mv *MessageValidator) { Validator: Func(func(field *Field) Errors { v, ok := field.Value.(T) if !ok { - return NewUnsupportedErrors(field, "Lte") + var want T + return NewUnsupportedErrors("Lte", field, want) } if v > value { - return NewErrors(field.Name, ErrInvalid, mv.Message) + return NewInvalidErrors(field, mv.Message) } return nil }), @@ -521,11 +537,12 @@ func Range[T constraints.Ordered](min, max T) (mv *MessageValidator) { Validator: Func(func(field *Field) Errors { v, ok := field.Value.(T) if !ok { - return NewUnsupportedErrors(field, "Range") + var want T + return NewUnsupportedErrors("Range", field, want) } if v < min || v > max { - return NewErrors(field.Name, ErrInvalid, mv.Message) + return NewInvalidErrors(field, mv.Message) } return nil }), @@ -541,7 +558,8 @@ func In[T comparable](values ...T) (mv *MessageValidator) { Validator: Func(func(field *Field) Errors { v, ok := field.Value.(T) if !ok { - return NewUnsupportedErrors(field, "In") + var want T + return NewUnsupportedErrors("In", field, want) } valid := false @@ -553,7 +571,7 @@ func In[T comparable](values ...T) (mv *MessageValidator) { } if !valid { - return NewErrors(field.Name, ErrInvalid, mv.Message) + return NewInvalidErrors(field, mv.Message) } return nil }), @@ -569,7 +587,8 @@ func Nin[T comparable](values ...T) (mv *MessageValidator) { Validator: Func(func(field *Field) Errors { v, ok := field.Value.(T) if !ok { - return NewUnsupportedErrors(field, "Nin") + var want T + return NewUnsupportedErrors("Nin", field, want) } valid := true @@ -581,7 +600,7 @@ func Nin[T comparable](values ...T) (mv *MessageValidator) { } if !valid { - return NewErrors(field.Name, ErrInvalid, mv.Message) + return NewInvalidErrors(field, mv.Message) } return nil }), @@ -603,11 +622,11 @@ func Match(re *regexp.Regexp) (mv *MessageValidator) { case []byte: valid = re.Match(v) default: - return NewUnsupportedErrors(field, "Match") + return NewUnsupportedErrors("Match", field, "", []byte(nil)) } if !valid { - return NewErrors(field.Name, ErrInvalid, mv.Message) + return NewInvalidErrors(field, mv.Message) } return nil }), diff --git a/builtin_test.go b/builtin_test.go index 91d956f..e84dbea 100644 --- a/builtin_test.go +++ b/builtin_test.go @@ -469,7 +469,7 @@ func TestNot(t *testing.T) { v.Schema{ v.F("value", []string{"foo"}): v.Not(v.Eq("foo")), }, - v.NewErrors("value", v.ErrUnsupported, "cannot use validator `Eq` on type []string"), + v.NewErrors("value", v.ErrUnsupported, "Eq expected string but got []string"), }, { v.Schema{ @@ -691,7 +691,7 @@ func TestLenString(t *testing.T) { { value: 0, validator: v.LenString(1, 2), - errs: v.NewErrors("value", v.ErrUnsupported, "cannot use validator `LenString` on type int"), + errs: v.NewErrors("value", v.ErrUnsupported, "LenString expected string but got int"), }, { value: "", @@ -728,7 +728,7 @@ func TestLenSlice(t *testing.T) { { value: "", validator: v.LenSlice[[]string](1, 2), - errs: v.NewErrors("value", v.ErrUnsupported, "cannot use validator `LenSlice` on type string"), + errs: v.NewErrors("value", v.ErrUnsupported, "LenSlice expected []string but got string"), }, { value: []int(nil), @@ -780,7 +780,7 @@ func TestRuneCount(t *testing.T) { { value: 0, validator: v.RuneCount(1, 2), - errs: v.NewErrors("value", v.ErrUnsupported, "cannot use validator `RuneCount` on type int"), + errs: v.NewErrors("value", v.ErrUnsupported, "RuneCount expected string or []byte but got int"), }, { value: "", @@ -1241,7 +1241,7 @@ func TestMatch(t *testing.T) { { value: 0, validator: v.Match(regexp.MustCompile(``)), - errs: v.NewErrors("value", v.ErrUnsupported, "cannot use validator `Match` on type int"), + errs: v.NewErrors("value", v.ErrUnsupported, "Match expected string or []byte but got int"), }, { value: "x13012345678", diff --git a/errors.go b/errors.go index b56ddae..f436c1a 100644 --- a/errors.go +++ b/errors.go @@ -23,8 +23,21 @@ func NewErrors(field, kind, message string) Errors { return []Error{NewError(field, kind, message)} } -func NewUnsupportedErrors(field *Field, validatorName string) Errors { - return NewErrors(field.Name, ErrUnsupported, fmt.Sprintf("cannot use validator `%s` on type %T", validatorName, field.Value)) +func NewUnsupportedErrors(validatorName string, field *Field, want ...any) Errors { + var wantTypes []string + for _, w := range want { + t := fmt.Sprintf("%T", w) + if t == "[]uint8" { + t = "[]byte" // []uint8 => []byte + } + wantTypes = append(wantTypes, t) + } + expected := strings.Join(wantTypes, " or ") + return NewErrors(field.Name, ErrUnsupported, fmt.Sprintf("%s expected %s but got %T", validatorName, expected, field.Value)) +} + +func NewInvalidErrors(field *Field, msg string) Errors { + return NewErrors(field.Name, ErrInvalid, msg) } func (e *Errors) Append(errs ...Error) { diff --git a/example_customizations_test.go b/example_customizations_test.go index fda6326..229f925 100644 --- a/example_customizations_test.go +++ b/example_customizations_test.go @@ -10,7 +10,8 @@ import ( func mapNonzero(field *v.Field) v.Errors { value, ok := field.Value.(map[string]time.Time) if !ok { - return v.NewUnsupportedErrors(field, "mapNonzero") + var want map[string]time.Time + return v.NewUnsupportedErrors("mapNonzero", field, want) } if len(value) == 0 { return v.NewErrors(field.Name, v.ErrInvalid, "is zero valued") diff --git a/example_nested_struct_map_test.go b/example_nested_struct_map_test.go index 397f171..f3dd210 100644 --- a/example_nested_struct_map_test.go +++ b/example_nested_struct_map_test.go @@ -29,6 +29,7 @@ func makeSchema1(p *Person1) v.Schema { } // The equivalent implementation using Map. +// //nolint:golint,unused func makeSchema1_Map(p *Person1) v.Schema { return v.Schema{ diff --git a/example_nested_struct_slice_test.go b/example_nested_struct_slice_test.go index ccdefab..67e2460 100644 --- a/example_nested_struct_slice_test.go +++ b/example_nested_struct_slice_test.go @@ -30,6 +30,7 @@ func makeSchema4(p *Person4) v.Schema { } // The equivalent implementation using Slice. +// //nolint:golint,unused func makeSchema4_Slice(p *Person4) v.Schema { return v.Schema{