Skip to content

Commit

Permalink
Improve Map and Slice to support scalar elements
Browse files Browse the repository at this point in the history
  • Loading branch information
RussellLuo committed Jun 25, 2024
1 parent 600d973 commit c97eada
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 25 deletions.
25 changes: 18 additions & 7 deletions builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,16 @@ func Nested[T any](f func(T) Validator) Validator {

// 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[T map[K]V, K comparable, V any](f func(T) map[K]Schema) Validator {
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")
}

schemas := f(v)
for k, s := range schemas {
validators := f(v)
for k, validator := range validators {
s := toSchema(v[k], validator)
err := validateSchema(s, field, func(name string) string {
return name + fmt.Sprintf("[%v]", k)
})
Expand All @@ -72,15 +73,16 @@ func Map[T map[K]V, K comparable, V any](f func(T) map[K]Schema) 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 {
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")
}

schemas := f(v)
for i, s := range schemas {
validators := f(v)
for i, validator := range validators {
s := toSchema(v[i], validator)
err := validateSchema(s, field, func(name string) string {
return name + "[" + strconv.Itoa(i) + "]"
})
Expand All @@ -93,7 +95,7 @@ func Slice[T ~[]E, E any](f func(T) []Schema) Validator {
}

// Array is an alias of Slice.
func Array[T ~[]E, E any](f func(T) []Schema) Validator {
func Array[T ~[]E, E any](f func(T) []Validator) Validator {
return Slice[T](f)
}

Expand Down Expand Up @@ -561,6 +563,15 @@ func Match(re *regexp.Regexp) (mv *MessageValidator) {
return
}

// toSchema converts the given validator to a Schema if it's not already.
func toSchema(value any, validator Validator) Schema {
s, ok := validator.(Schema)
if !ok {
s = Value(value, validator)
}
return s
}

// validateSchema do the validation per the given schema, which is associated
// with the given field.
func validateSchema(schema Schema, field *Field, prefixFunc func(string) string) (errs Errors) {
Expand Down
47 changes: 39 additions & 8 deletions builtin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func TestMap(t *testing.T) {
{
name: "nil map",
value: map[string]Stat(nil),
validator: v.Map(func(m map[string]Stat) map[string]v.Schema {
validator: v.Map(func(m map[string]Stat) map[string]v.Validator {
return nil
}),
errs: nil,
Expand All @@ -84,8 +84,8 @@ func TestMap(t *testing.T) {
"visitor": {Count: 0},
"visit": {Count: 0},
},
validator: v.Map(func(m map[string]Stat) map[string]v.Schema {
schemas := make(map[string]v.Schema)
validator: v.Map(func(m map[string]Stat) map[string]v.Validator {
schemas := make(map[string]v.Validator)
for k, s := range m {
schemas[k] = v.Schema{
v.F("count", s.Count): v.Nonzero[int](),
Expand All @@ -104,8 +104,8 @@ func TestMap(t *testing.T) {
"visitor": {Count: 1},
"visit": {Count: 2},
},
validator: v.Map(func(m map[string]Stat) map[string]v.Schema {
schemas := make(map[string]v.Schema)
validator: v.Map(func(m map[string]Stat) map[string]v.Validator {
schemas := make(map[string]v.Validator)
for k, s := range m {
schemas[k] = v.Schema{
v.F("count", s.Count): v.Nonzero[int](),
Expand All @@ -115,6 +115,24 @@ func TestMap(t *testing.T) {
}),
errs: nil,
},
{
name: "int map",
value: map[string]int{
"k1": 1,
"k2": 2,
"k3": 3,
},
validator: v.Map(func(m map[string]int) map[string]v.Validator {
schemas := make(map[string]v.Validator)
for k := range m {
schemas[k] = v.Range[int](1, 2)
}
return schemas
}),
errs: v.Errors{
v.NewError("stats[k3]", v.ErrInvalid, "is not between the given range"),
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
Expand Down Expand Up @@ -143,7 +161,7 @@ func TestSlice(t *testing.T) {
{
name: "nil slice",
value: []Comment(nil),
validator: v.Slice(func(s []Comment) (schemas []v.Schema) {
validator: v.Slice(func(s []Comment) (schemas []v.Validator) {
return nil
}),
errs: nil,
Expand All @@ -154,7 +172,7 @@ func TestSlice(t *testing.T) {
{Content: "", CreatedAt: time.Time{}},
},

validator: v.Slice(func(s []Comment) (schemas []v.Schema) {
validator: v.Slice(func(s []Comment) (schemas []v.Validator) {
for _, c := range s {
schemas = append(schemas, v.Schema{
v.F("content", c.Content): v.Nonzero[string](),
Expand All @@ -171,7 +189,7 @@ func TestSlice(t *testing.T) {
{
name: "nil slice",
value: []Comment(nil),
validator: v.Slice(func(s []Comment) (schemas []v.Schema) {
validator: v.Slice(func(s []Comment) (schemas []v.Validator) {
for _, c := range s {
schemas = append(schemas, v.Schema{
v.F("content", c.Content): v.Nonzero[string](),
Expand All @@ -182,6 +200,19 @@ func TestSlice(t *testing.T) {
}),
errs: nil,
},
{
name: "int slice",
value: []int{1, 2, 3},
validator: v.Slice(func(s []int) (schemas []v.Validator) {
for range s {
schemas = append(schemas, v.Range[int](1, 2))
}
return
}),
errs: v.Errors{
v.NewError("comments[2]", v.ErrInvalid, "is not between the given range"),
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
Expand Down
4 changes: 2 additions & 2 deletions example_nested_struct_map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ func makeSchema1(p *Person1) v.Schema {
return v.Schema{
v.F("name", p.Name): v.LenString(1, 5),
v.F("age", p.Age): v.Nonzero[int](),
v.F("family", p.Family): v.Map(func(m map[string]*Member) map[string]v.Schema {
schemas := make(map[string]v.Schema)
v.F("family", p.Family): v.Map(func(m map[string]*Member) map[string]v.Validator {
schemas := make(map[string]v.Validator)
for relation, member := range m {
schemas[relation] = v.Schema{
v.F("name", member.Name): v.LenString(10, 15).Msg("is too long"),
Expand Down
2 changes: 1 addition & 1 deletion example_nested_struct_slice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func makeSchema4(p *Person4) v.Schema {
return v.Schema{
v.F("name", p.Name): v.LenString(1, 5),
v.F("age", p.Age): v.Nonzero[int](),
v.F("phones", p.Phones): v.Slice(func(s []*Phone) (schemas []v.Schema) {
v.F("phones", p.Phones): v.Slice(func(s []*Phone) (schemas []v.Validator) {
for _, phone := range s {
schemas = append(schemas, v.Schema{
v.F("number", phone.Number): v.Nonzero[string](),
Expand Down
8 changes: 4 additions & 4 deletions example_simple_map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ func Example_simpleMap() {
"foo": 0,
"bar": 1,
}
err := v.Validate(v.Value(ages, v.Map(func(m map[string]int) map[string]v.Schema {
schemas := make(map[string]v.Schema)
for name, age := range m {
schemas[name] = v.Value(age, v.Nonzero[int]())
err := v.Validate(v.Value(ages, v.Map(func(m map[string]int) map[string]v.Validator {
schemas := make(map[string]v.Validator)
for name := range m {
schemas[name] = v.Nonzero[int]()
}
return schemas
})))
Expand Down
6 changes: 3 additions & 3 deletions example_simple_slice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import (

func Example_simpleSlice() {
names := []string{"", "foo"}
err := v.Validate(v.Value(names, v.Slice(func(s []string) (schemas []v.Schema) {
for _, name := range s {
schemas = append(schemas, v.Value(name, v.Nonzero[string]()))
err := v.Validate(v.Value(names, v.Slice(func(s []string) (schemas []v.Validator) {
for range s {
schemas = append(schemas, v.Nonzero[string]())
}
return schemas
})))
Expand Down

0 comments on commit c97eada

Please sign in to comment.