Skip to content

Commit 564491c

Browse files
author
Christian Theilemann
committed
introduce EachUntilFirstError for validating slices and maps with lots of items
1 parent 094faa1 commit 564491c

File tree

2 files changed

+122
-0
lines changed

2 files changed

+122
-0
lines changed

each_until_first_error.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright 2016 Qiang Xue. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package validation
6+
7+
import (
8+
"errors"
9+
"reflect"
10+
"strconv"
11+
)
12+
13+
// EachUntilFirstError is the same as Each but stops early once the first item with a validation error was encountered.
14+
// Use this instead of Each for array's or maps that may potentially contain ten-thousands of erroneous items and
15+
// you want to avoid returning ten-thousands of validation errors (for memory and cpu reasons).
16+
func EachUntilFirstError(rules ...Rule) EachUntilFirstErrorRule {
17+
return EachUntilFirstErrorRule{
18+
rules: rules,
19+
}
20+
}
21+
22+
// EachUntilFirstErrorRule is the same as EachRule but stops early once the first item with a validation error was encountered.
23+
// Use this instead of EachRule for array's or maps that may potentially contain ten-thousands of erroneous items and
24+
// you want to avoid returning ten-thousands of validation errors (for memory and cpu reasons).
25+
type EachUntilFirstErrorRule struct {
26+
rules []Rule
27+
}
28+
29+
// Validate loops through the given iterable and calls the Ozzo Validate() method for each value.
30+
func (r EachUntilFirstErrorRule) Validate(value interface{}) error {
31+
errs := Errors{}
32+
33+
v := reflect.ValueOf(value)
34+
switch v.Kind() {
35+
case reflect.Map:
36+
for _, k := range v.MapKeys() {
37+
val := r.getInterface(v.MapIndex(k))
38+
if err := Validate(val, r.rules...); err != nil {
39+
errs[r.getString(k)] = err
40+
break
41+
}
42+
}
43+
case reflect.Slice, reflect.Array:
44+
for i := 0; i < v.Len(); i++ {
45+
val := r.getInterface(v.Index(i))
46+
if err := Validate(val, r.rules...); err != nil {
47+
errs[strconv.Itoa(i)] = err
48+
break
49+
}
50+
}
51+
default:
52+
return errors.New("must be an iterable (map, slice or array)")
53+
}
54+
55+
if len(errs) > 0 {
56+
return errs
57+
}
58+
return nil
59+
}
60+
61+
func (r EachUntilFirstErrorRule) getInterface(value reflect.Value) interface{} {
62+
switch value.Kind() {
63+
case reflect.Ptr, reflect.Interface:
64+
if value.IsNil() {
65+
return nil
66+
}
67+
return value.Elem().Interface()
68+
default:
69+
return value.Interface()
70+
}
71+
}
72+
73+
func (r EachUntilFirstErrorRule) getString(value reflect.Value) string {
74+
switch value.Kind() {
75+
case reflect.Ptr, reflect.Interface:
76+
if value.IsNil() {
77+
return ""
78+
}
79+
return value.Elem().String()
80+
default:
81+
return value.String()
82+
}
83+
}

each_until_first_error_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package validation
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestEachUntilFirstError(t *testing.T) {
8+
var a *int
9+
var f = func(v string) string { return v }
10+
var c0 chan int
11+
c1 := make(chan int)
12+
13+
tests := []struct {
14+
tag string
15+
value interface{}
16+
err string
17+
}{
18+
{"t1", nil, "must be an iterable (map, slice or array)"},
19+
{"t2", map[string]string{}, ""},
20+
{"t3", map[string]string{"key1": "value1", "key2": "value2"}, ""},
21+
{"t4", map[string]string{"key1": "", "key2": "value2", "key3": ""}, "key1: cannot be blank."},
22+
{"t5", map[string]map[string]string{"key1": {"key1.1": "value1"}, "key2": {"key2.1": "value1"}}, ""},
23+
{"t6", map[string]map[string]string{"": nil}, ": cannot be blank."},
24+
{"t7", map[interface{}]interface{}{}, ""},
25+
{"t8", map[interface{}]interface{}{"key1": struct{ foo string }{"foo"}}, ""},
26+
{"t9", map[interface{}]interface{}{nil: "", "": "", "key1": nil}, ": cannot be blank."},
27+
{"t10", []string{"value1", "value2", "value3"}, ""},
28+
{"t11", []string{"", "value2", ""}, "0: cannot be blank."},
29+
{"t12", []interface{}{struct{ foo string }{"foo"}}, ""},
30+
{"t13", []interface{}{nil, a}, "0: cannot be blank."},
31+
{"t14", []interface{}{c0, c1, f}, "0: cannot be blank."},
32+
}
33+
34+
for _, test := range tests {
35+
r := EachUntilFirstError(Required)
36+
err := r.Validate(test.value)
37+
assertError(t, test.err, err, test.tag)
38+
}
39+
}

0 commit comments

Comments
 (0)