From 7442a1c9b52e02762782b52f092864f3404663db Mon Sep 17 00:00:00 2001 From: Sam Lown Date: Mon, 29 Jul 2024 22:03:12 +0000 Subject: [PATCH] Using generics for NotIn, updating docs --- README.md | 6 +++--- not_in.go | 20 +++++++++++--------- not_in_test.go | 26 ++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 39a7559..3a13881 100644 --- a/README.md +++ b/README.md @@ -607,14 +607,14 @@ When performing context-aware validation, if a rule does not implement `validati The following rules are provided in the `validation` package: -- `In(...interface{})`: checks if a value can be found in the given list of values. -- `NotIn(...interface{})`: checks if a value is NOT among the given list of values. +- `In[T any](values ...T)`: checks if a value can be found in the given list of values. +- `NotIn[T any](values ...T)`: checks if a value is NOT among the given list of values. - `Length(min, max int)`: checks if the length of a value is within the specified range. This rule should only be used for validating strings, slices, maps, and arrays. - `RuneLength(min, max int)`: checks if the length of a string is within the specified range. This rule is similar as `Length` except that when the value being validated is a string, it checks its rune length instead of byte length. -- `Min(min interface{})` and `Max(max interface{})`: checks if a value is within the specified range. +- `Min(min any)` and `Max(max any)`: checks if a value is within the specified range. These two rules should only be used for validating int, uint, float and time.Time types. - `Match(*regexp.Regexp)`: checks if a value matches the specified regular expression. This rule should only be used for strings and byte slices. diff --git a/not_in.go b/not_in.go index 6e35d75..77b3bff 100644 --- a/not_in.go +++ b/not_in.go @@ -4,34 +4,36 @@ package validation +import "reflect" + // ErrNotInInvalid is the error that returns when a value is in a list. var ErrNotInInvalid = NewError("validation_not_in_invalid", "must not be in list") // NotIn returns a validation rule that checks if a value is absent from the given list of values. -// Note that the value being checked and the possible range of values must be of the same type. +// Like with In(), reflect.DeepEqual() will be used to determine if two values are equal. // An empty value is considered valid. Use the Required rule to make sure a value is not empty. -func NotIn(values ...interface{}) NotInRule { - return NotInRule{ +func NotIn[T any](values ...T) NotInRule[T] { + return NotInRule[T]{ elements: values, err: ErrNotInInvalid, } } // NotInRule is a validation rule that checks if a value is absent from the given list of values. -type NotInRule struct { - elements []interface{} +type NotInRule[T any] struct { + elements []T err Error } // Validate checks if the given value is valid or not. -func (r NotInRule) Validate(value interface{}) error { +func (r NotInRule[T]) Validate(value interface{}) error { value, isNil := Indirect(value) if isNil || IsEmpty(value) { return nil } for _, e := range r.elements { - if e == value { + if reflect.DeepEqual(e, value) { return r.err } } @@ -39,13 +41,13 @@ func (r NotInRule) Validate(value interface{}) error { } // Error sets the error message for the rule. -func (r NotInRule) Error(message string) NotInRule { +func (r NotInRule[T]) Error(message string) NotInRule[T] { r.err = r.err.SetMessage(message) return r } // ErrorObject sets the error struct for the rule. -func (r NotInRule) ErrorObject(err Error) NotInRule { +func (r NotInRule[T]) ErrorObject(err Error) NotInRule[T] { r.err = err return r } diff --git a/not_in_test.go b/not_in_test.go index 2b59d3e..ed092d8 100644 --- a/not_in_test.go +++ b/not_in_test.go @@ -36,6 +36,32 @@ func TestNotIn(t *testing.T) { } } +func TestNotIn_Ints(t *testing.T) { + v := 1 + var v2 *int + var tests = []struct { + tag string + values []int + value any + err string + }{ + {"t0", []int{1, 2}, 0, ""}, + {"t1", []int{1, 2}, 1, "must not be in list"}, + {"t2", []int{1, 2}, 2, "must not be in list"}, + {"t3", []int{1, 2}, 3, ""}, + {"t4", []int{}, 3, ""}, + {"t5", []int{1, 2}, "1", ""}, + {"t6", []int{1, 2}, &v, "must not be in list"}, + {"t7", []int{1, 2}, v2, ""}, + } + + for _, test := range tests { + r := NotIn(test.values...) + err := r.Validate(test.value) + assertError(t, test.err, err, test.tag) + } +} + func Test_NotInRule_Error(t *testing.T) { r := NotIn(1, 2, 3) assert.Equal(t, "must not be in list", r.Validate(1).Error())