Skip to content

Commit

Permalink
Improve several validator factories
Browse files Browse the repository at this point in the history
- Enhance `Map` and `Slice` by leveraging Go generics
- Add `Nested`, and remove redundant `Lazy`
- Remove `EachMapValue` (use `Map` instead)
- Remove `Each` (use `Slice` instead)
- Remove `Assert` (use `Is` instead)
  • Loading branch information
RussellLuo committed Mar 22, 2022
1 parent a12503a commit 3a2cb10
Show file tree
Hide file tree
Showing 9 changed files with 263 additions and 324 deletions.
11 changes: 4 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,12 @@ A validator factory is a function used to create a validator, which will do the
- [Func](https://pkg.go.dev/github.com/RussellLuo/validating/v3#Func)
- [Schema](https://pkg.go.dev/github.com/RussellLuo/validating/v3#Schema)
- [Value](https://pkg.go.dev/github.com/RussellLuo/validating/v3#Value)
- [Slice/Array](https://pkg.go.dev/github.com/RussellLuo/validating/v3#Slice)
- [Nested](https://pkg.go.dev/github.com/RussellLuo/validating/v3#Nested)
- [Map](https://pkg.go.dev/github.com/RussellLuo/validating/v3#Map)
- [Each](https://pkg.go.dev/github.com/RussellLuo/validating/v3#Each)
- [EachMapValue](https://pkg.go.dev/github.com/RussellLuo/validating/v3#EachMapValue)
- [Slice/Array](https://pkg.go.dev/github.com/RussellLuo/validating/v3#Slice)
- [All/And](https://pkg.go.dev/github.com/RussellLuo/validating/v3#All)
- [Any/Or](https://pkg.go.dev/github.com/RussellLuo/validating/v3#Any)
- [Not](https://pkg.go.dev/github.com/RussellLuo/validating/v3#Not)
- [Lazy](https://pkg.go.dev/github.com/RussellLuo/validating/v3#Lazy)
- [Assert](https://pkg.go.dev/github.com/RussellLuo/validating/v3#Assert)
- [Is](https://pkg.go.dev/github.com/RussellLuo/validating/v3#Is)
- [Nonzero](https://pkg.go.dev/github.com/RussellLuo/validating/v3#Nonzero)
- [Zero](https://pkg.go.dev/github.com/RussellLuo/validating/v3#Zero)
Expand All @@ -75,8 +72,8 @@ A validator factory is a function used to create a validator, which will do the

### Validator customizations

- [From a boolean expression](example_nested_struct_pointer_test.go#L24)
- [From a function](example_customizations_test.go#L30-L32)
- [From a boolean function](example_nested_struct_pointer_test.go#L24)
- [From a normal function](example_customizations_test.go#L30-L32)
- [From a struct](example_customizations_test.go#L35-L37)


Expand Down
112 changes: 34 additions & 78 deletions builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,34 +35,32 @@ func Value(value interface{}, validator Validator) Schema {
}
}

// Slice is a composite validator factory used to create a validator, which will
// do the validation per the schemas associated with a slice.
func Slice(f func() []Schema) Validator {
schemas := f()
return Func(func(field *Field) (errs Errors) {
for i, s := range schemas {
err := validateSchema(s, field, func(name string) string {
return name + "[" + strconv.Itoa(i) + "]"
})
if err != nil {
errs.Append(err...)
}
// Nested is a composite validator factory used to create a validator, which will
// delegate the actual validation to the schema returned by f.
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")
}
return

return f(v).Validate(field)
})
}

// Array is an alias of Slice.
var Array = Slice

// Map is a composite validator factory used to create a validator, which will
// do the validation per the schemas associated with a map.
func Map(f func() map[string]Schema) Validator {
schemas := f()
// do the validation per the key/value schemas associated with a map.
func Map[T map[K]V, K comparable, V any](f func(T) map[K]Schema) Validator {
return Func(func(field *Field) (errs Errors) {
v, ok := field.Value.(T)
if !ok {
return NewUnsupportedErrors(field, "Map")
}

schemas := f(v)
for k, s := range schemas {
err := validateSchema(s, field, func(name string) string {
return name + "[" + k + "]"
return name + fmt.Sprintf("[%v]", k)
})
if err != nil {
errs.Append(err...)
Expand All @@ -72,49 +70,31 @@ func Map(f func() map[string]Schema) Validator {
})
}

// Each is a composite validator factory used to create a validator, which will
// succeed only when validator succeeds on all elements of the slice field.
func Each[T ~[]E, E any](validator Validator) Validator {
// Slice is a composite validator factory used to create a validator, which will
// do the validation per the schemas associated with a slice.
func Slice[T ~[]E, E any](f func(T) []Schema) Validator {
return Func(func(field *Field) (errs Errors) {
v, ok := field.Value.(T)
if !ok {
return NewUnsupportedErrors(field, "Each")
return NewUnsupportedErrors(field, "Slice")
}

for i, vv := range v {
schema := Value((interface{})(vv), validator)
err := validateSchema(schema, field, func(name string) string {
schemas := f(v)
for i, s := range schemas {
err := validateSchema(s, field, func(name string) string {
return name + "[" + strconv.Itoa(i) + "]"
})
if err != nil {
errs.Append(err...)
}
}

return
})
}

// EachMapValue is a composite validator factory used to create a validator, which will
// succeed only when validator succeeds on all values of the map field.
func EachMapValue[T map[K]V, K comparable, V any](validator Validator) Validator {
return Func(func(field *Field) (errs Errors) {
m, ok := field.Value.(T)
if !ok {
return NewUnsupportedErrors(field, "EachKeyValue")
}

for k, v := range m {
schema := Value((interface{})(v), validator)
err := validateSchema(schema, field, func(name string) string {
return name + fmt.Sprintf("[%v]", k)
})
if err != nil {
errs.Append(err...)
}
}
return
})
// Array is an alias of Slice.
func Array[T ~[]E, E any](f func(T) []Schema) Validator {
return Slice[T](f)
}

// MessageValidator is a validator that allows users to customize the INVALID
Expand Down Expand Up @@ -219,30 +199,6 @@ func Not(validator Validator) (mv *MessageValidator) {
return
}

// Lazy is a composite validator factory used to create a validator, which will
// call f only as needed, to delegate the actual validation to
// the validator returned by f.
func Lazy(f func() Validator) Validator {
return Func(func(field *Field) Errors {
return f().Validate(field)
})
}

// Assert is a leaf validator factory used to create a validator, which will
// succeed only when the boolean expression evaluates to true.
func Assert(b bool) (mv *MessageValidator) {
mv = &MessageValidator{
Message: "is invalid",
Validator: Func(func(field *Field) Errors {
if !b {
return NewErrors(field.Name, ErrInvalid, mv.Message)
}
return nil
}),
}
return
}

// Is is a leaf validator factory used to create a validator, which will
// succeed when the predicate function f returns true for the field's value.
func Is[T any](f func(T) bool) (mv *MessageValidator) {
Expand Down Expand Up @@ -363,12 +319,12 @@ func RuneCount(min, max int) (mv *MessageValidator) {
Validator: Func(func(field *Field) Errors {
valid := false

switch t := field.Value.(type) {
switch v := field.Value.(type) {
case string:
l := utf8.RuneCountInString(t)
l := utf8.RuneCountInString(v)
valid = l >= min && l <= max
case []byte:
l := utf8.RuneCount(t)
l := utf8.RuneCount(v)
valid = l >= min && l <= max
default:
return NewUnsupportedErrors(field, "RuneCount")
Expand Down Expand Up @@ -587,11 +543,11 @@ func Match(re *regexp.Regexp) (mv *MessageValidator) {
Validator: Func(func(field *Field) Errors {
valid := false

switch t := field.Value.(type) {
switch v := field.Value.(type) {
case string:
valid = re.MatchString(t)
valid = re.MatchString(v)
case []byte:
valid = re.Match(t)
valid = re.Match(v)
default:
return NewUnsupportedErrors(field, "Match")
}
Expand Down
Loading

0 comments on commit 3a2cb10

Please sign in to comment.