Skip to content

Commit 25eba0b

Browse files
authored
Make Each and When context aware (#130)
* Minor linting tweaks * Make Each and When context aware
1 parent b589ba5 commit 25eba0b

14 files changed

+148
-58
lines changed

README.md

+10-24
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,6 @@ to achieve this purpose. A single struct can have rules for multiple fields, and
8282
rules. For example,
8383

8484
```go
85-
package main
86-
87-
import (
88-
"fmt"
89-
"regexp"
90-
91-
"github.com/go-ozzo/ozzo-validation/v4"
92-
"github.com/go-ozzo/ozzo-validation/v4/is"
93-
)
94-
9585
type Address struct {
9686
Street string
9787
City string
@@ -112,19 +102,17 @@ func (a Address) Validate() error {
112102
)
113103
}
114104

115-
func main() {
116-
a := Address{
117-
Street: "123",
118-
City: "Unknown",
119-
State: "Virginia",
120-
Zip: "12345",
121-
}
122-
123-
err := a.Validate()
124-
fmt.Println(err)
125-
// Output:
126-
// Street: the length must be between 5 and 50; State: must be in a valid format.
105+
a := Address{
106+
Street: "123",
107+
City: "Unknown",
108+
State: "Virginia",
109+
Zip: "12345",
127110
}
111+
112+
err := a.Validate()
113+
fmt.Println(err)
114+
// Output:
115+
// Street: the length must be between 5 and 50; State: must be in a valid format.
128116
```
129117

130118
Note that when calling `validation.ValidateStruct` to validate a struct, you should pass to the method a pointer
@@ -457,8 +445,6 @@ You can also customize the pre-defined error(s) of a built-in rule such that the
457445
instance of the rule. For example, the `Required` rule uses the pre-defined error `ErrRequired`. You can customize it
458446
during the application initialization:
459447
```go
460-
import "github.com/go-ozzo/ozzo-validation/v4"
461-
462448
validation.ErrRequired = validation.ErrRequired.SetMessage("the value is required")
463449
```
464450

date_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ func TestDateRule_MinMax(t *testing.T) {
8484
if assert.NotNil(t, err) {
8585
assert.Equal(t, "the date is out of range", err.Error())
8686
}
87-
err2 := r2.Validate("2021-01-02")
87+
err = r2.Validate("2021-01-02")
8888
if assert.NotNil(t, err) {
89-
assert.Equal(t, "the date is out of range", err2.Error())
89+
assert.Equal(t, "the date is out of range", err.Error())
9090
}
9191
}

each.go

+20-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package validation
66

77
import (
8+
"context"
89
"errors"
910
"reflect"
1011
"strconv"
@@ -26,21 +27,38 @@ type EachRule struct {
2627

2728
// Validate loops through the given iterable and calls the Ozzo Validate() method for each value.
2829
func (r EachRule) Validate(value interface{}) error {
30+
return r.ValidateWithContext(nil, value)
31+
}
32+
33+
// ValidateWithContext loops through the given iterable and calls the Ozzo ValidateWithContext() method for each value.
34+
func (r EachRule) ValidateWithContext(ctx context.Context, value interface{}) error {
2935
errs := Errors{}
3036

3137
v := reflect.ValueOf(value)
3238
switch v.Kind() {
3339
case reflect.Map:
3440
for _, k := range v.MapKeys() {
3541
val := r.getInterface(v.MapIndex(k))
36-
if err := Validate(val, r.rules...); err != nil {
42+
var err error
43+
if ctx == nil {
44+
err = Validate(val, r.rules...)
45+
} else {
46+
err = ValidateWithContext(ctx, val, r.rules...)
47+
}
48+
if err != nil {
3749
errs[r.getString(k)] = err
3850
}
3951
}
4052
case reflect.Slice, reflect.Array:
4153
for i := 0; i < v.Len(); i++ {
4254
val := r.getInterface(v.Index(i))
43-
if err := Validate(val, r.rules...); err != nil {
55+
var err error
56+
if ctx == nil {
57+
err = Validate(val, r.rules...)
58+
} else {
59+
err = ValidateWithContext(ctx, val, r.rules...)
60+
}
61+
if err != nil {
4462
errs[strconv.Itoa(i)] = err
4563
}
4664
}

each_test.go

+35
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package validation
22

33
import (
4+
"context"
5+
"errors"
6+
"strings"
47
"testing"
58
)
69

@@ -37,3 +40,35 @@ func TestEach(t *testing.T) {
3740
assertError(t, test.err, err, test.tag)
3841
}
3942
}
43+
44+
func TestEachWithContext(t *testing.T) {
45+
rule := Each(WithContext(func(ctx context.Context, value interface{}) error {
46+
if !strings.Contains(value.(string), ctx.Value("contains").(string)) {
47+
return errors.New("unexpected value")
48+
}
49+
return nil
50+
}))
51+
ctx1 := context.WithValue(context.Background(), "contains", "abc")
52+
ctx2 := context.WithValue(context.Background(), "contains", "xyz")
53+
54+
tests := []struct {
55+
tag string
56+
value interface{}
57+
ctx context.Context
58+
err string
59+
}{
60+
{"t1.1", map[string]string{"key": "abc"}, ctx1, ""},
61+
{"t1.2", map[string]string{"key": "abc"}, ctx2, "key: unexpected value."},
62+
{"t1.3", map[string]string{"key": "xyz"}, ctx1, "key: unexpected value."},
63+
{"t1.4", map[string]string{"key": "xyz"}, ctx2, ""},
64+
{"t1.5", []string{"abc"}, ctx1, ""},
65+
{"t1.6", []string{"abc"}, ctx2, "0: unexpected value."},
66+
{"t1.7", []string{"xyz"}, ctx1, "0: unexpected value."},
67+
{"t1.8", []string{"xyz"}, ctx2, ""},
68+
}
69+
70+
for _, test := range tests {
71+
err := ValidateWithContext(test.ctx, test.value, rule)
72+
assertError(t, test.err, err, test.tag)
73+
}
74+
}

error.go

+7-5
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ type (
3333
}
3434

3535
// Errors represents the validation errors that are indexed by struct field names, map or slice keys.
36-
// values are Error or Errors (for map,slice and array error value is Errors).
36+
// values are Error or Errors (for map, slice and array error value is Errors).
3737
Errors map[string]error
3838

3939
// InternalError represents an error that should NOT be treated as a validation error.
@@ -118,9 +118,11 @@ func (es Errors) Error() string {
118118
return ""
119119
}
120120

121-
keys := []string{}
121+
keys := make([]string, len(es))
122+
i := 0
122123
for key := range es {
123-
keys = append(keys, key)
124+
keys[i] = key
125+
i++
124126
}
125127
sort.Strings(keys)
126128

@@ -130,9 +132,9 @@ func (es Errors) Error() string {
130132
s.WriteString("; ")
131133
}
132134
if errs, ok := es[key].(Errors); ok {
133-
fmt.Fprintf(&s, "%v: (%v)", key, errs)
135+
_, _ = fmt.Fprintf(&s, "%v: (%v)", key, errs)
134136
} else {
135-
fmt.Fprintf(&s, "%v: %v", key, es[key].Error())
137+
_, _ = fmt.Fprintf(&s, "%v: %v", key, es[key].Error())
136138
}
137139
}
138140
s.WriteString(".")

is/rules.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
package is
77

88
import (
9-
validation "github.com/go-ozzo/ozzo-validation/v4"
109
"regexp"
1110
"unicode"
1211

1312
"github.com/asaskevich/govalidator"
13+
validation "github.com/go-ozzo/ozzo-validation/v4"
1414
)
1515

1616
var (

multipleof_test.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ import (
1010
"github.com/stretchr/testify/assert"
1111
)
1212

13-
func TestMultipleof(t *testing.T) {
14-
r := MultipleOf(int(10))
15-
assert.Equal(t, "must be multiple of 10", r.Validate(int(11)).Error())
16-
assert.Equal(t, nil, r.Validate(int(20)))
13+
func TestMultipleOf(t *testing.T) {
14+
r := MultipleOf(10)
15+
assert.Equal(t, "must be multiple of 10", r.Validate(11).Error())
16+
assert.Equal(t, nil, r.Validate(20))
1717
assert.Equal(t, "cannot convert float32 to int64", r.Validate(float32(20)).Error())
1818

1919
r2 := MultipleOf("some string ....")
@@ -26,7 +26,7 @@ func TestMultipleof(t *testing.T) {
2626

2727
}
2828

29-
func Test_Multipleof_Error(t *testing.T) {
29+
func Test_MultipleOf_Error(t *testing.T) {
3030
r := MultipleOf(10)
3131
assert.Equal(t, "must be multiple of 10", r.Validate(3).Error())
3232

string_test.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ package validation
66

77
import (
88
"database/sql"
9-
"testing"
10-
119
"reflect"
10+
"testing"
1211

1312
"github.com/stretchr/testify/assert"
1413
)

struct.go

+3-7
Original file line numberDiff line numberDiff line change
@@ -52,26 +52,22 @@ func (e ErrFieldNotFound) Error() string {
5252
// Value string
5353
// }{"name", "demo"}
5454
// err := validation.ValidateStruct(&value,
55-
// validation.Field(&a.Name, validation.Required),
56-
// validation.Field(&a.Value, validation.Required, validation.Length(5, 10)),
55+
// validation.Field(&a.Name, validation.Required),
56+
// validation.Field(&a.Value, validation.Required, validation.Length(5, 10)),
5757
// )
5858
// fmt.Println(err)
5959
// // Value: the length must be between 5 and 10.
6060
//
6161
// An error will be returned if validation fails.
6262
func ValidateStruct(structPtr interface{}, fields ...*FieldRules) error {
63-
return validateStruct(nil, structPtr, fields...)
63+
return ValidateStructWithContext(nil, structPtr, fields...)
6464
}
6565

6666
// ValidateStructWithContext validates a struct with the given context.
6767
// The only difference between ValidateStructWithContext and ValidateStruct is that the former will
6868
// validate struct fields with the provided context.
6969
// Please refer to ValidateStruct for the detailed instructions on how to use this function.
7070
func ValidateStructWithContext(ctx context.Context, structPtr interface{}, fields ...*FieldRules) error {
71-
return validateStruct(ctx, structPtr, fields...)
72-
}
73-
74-
func validateStruct(ctx context.Context, structPtr interface{}, fields ...*FieldRules) error {
7571
value := reflect.ValueOf(structPtr)
7672
if value.Kind() != reflect.Ptr || !value.IsNil() && value.Elem().Kind() != reflect.Struct {
7773
// must be a pointer to a struct

struct_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func TestValidateStruct(t *testing.T) {
8686
{"t2.4", &m1, []*FieldRules{Field(&m1.D, Length(0, 5))}, ""},
8787
{"t2.5", &m1, []*FieldRules{Field(&m1.F, Length(0, 5))}, ""},
8888
{"t2.6", &m1, []*FieldRules{Field(&m1.H, Each(&validateAbc{})), Field(&m1.I, Each(&validateAbc{}))}, ""},
89-
{"t2.6", &m1, []*FieldRules{Field(&m1.H, Each(&validateXyz{})), Field(&m1.I, Each(&validateXyz{}))}, "H: (0: error xyz; 1: error xyz.); I: (foo: error xyz.)."},
89+
{"t2.7", &m1, []*FieldRules{Field(&m1.H, Each(&validateXyz{})), Field(&m1.I, Each(&validateXyz{}))}, "H: (0: error xyz; 1: error xyz.); I: (foo: error xyz.)."},
9090
// non-struct pointer
9191
{"t3.1", m1, []*FieldRules{}, ErrStructPointer.Error()},
9292
{"t3.2", nil, []*FieldRules{}, ErrStructPointer.Error()},
@@ -189,6 +189,7 @@ func TestValidateStructWithContext(t *testing.T) {
189189
assert.Equal(t, "Value: the length must be between 5 and 10.", err.Error())
190190
}
191191
}
192+
192193
func Test_getErrorFieldName(t *testing.T) {
193194
var s1 Struct1
194195
v1 := reflect.ValueOf(&s1).Elem()

util_test.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@
55
package validation
66

77
import (
8+
"database/sql"
89
"testing"
910
"time"
1011

11-
"database/sql"
12-
1312
"github.com/stretchr/testify/assert"
1413
)
1514

@@ -221,12 +220,12 @@ func TestIsEmpty(t *testing.T) {
221220
{"t4.1", false, true},
222221
{"t4.2", true, false},
223222
// int
224-
{"t5.1", int(0), true},
223+
{"t5.1", 0, true},
225224
{"t5.2", int8(0), true},
226225
{"t5.3", int16(0), true},
227226
{"t5.4", int32(0), true},
228227
{"t5.5", int64(0), true},
229-
{"t5.6", int(1), false},
228+
{"t5.6", 1, false},
230229
{"t5.7", int8(1), false},
231230
{"t5.8", int16(1), false},
232231
{"t5.9", int32(1), false},

validation_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ func (v *validateContextAbc) Validate(obj interface{}) error {
156156
return v.ValidateWithContext(context.Background(), obj)
157157
}
158158

159-
func (v *validateContextAbc) ValidateWithContext(ctx context.Context, obj interface{}) error {
159+
func (v *validateContextAbc) ValidateWithContext(_ context.Context, obj interface{}) error {
160160
if !strings.Contains(obj.(string), "abc") {
161161
return errors.New("error abc")
162162
}
@@ -178,7 +178,7 @@ func (v *validateContextXyz) Validate(obj interface{}) error {
178178
return v.ValidateWithContext(context.Background(), obj)
179179
}
180180

181-
func (v *validateContextXyz) ValidateWithContext(ctx context.Context, obj interface{}) error {
181+
func (v *validateContextXyz) ValidateWithContext(_ context.Context, obj interface{}) error {
182182
if !strings.Contains(obj.(string), "xyz") {
183183
return errors.New("error xyz")
184184
}
@@ -262,7 +262,7 @@ func (s StringValidateContext) Validate() error {
262262
return nil
263263
}
264264

265-
func (s StringValidateContext) ValidateWithContext(ctx context.Context) error {
265+
func (s StringValidateContext) ValidateWithContext(context.Context) error {
266266
if string(s) != "abc" {
267267
return errors.New("must be abc with context")
268268
}

when.go

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package validation
22

3+
import "context"
4+
35
// When returns a validation rule that executes the given list of rules when the condition is true.
46
func When(condition bool, rules ...Rule) WhenRule {
57
return WhenRule{
@@ -18,11 +20,24 @@ type WhenRule struct {
1820

1921
// Validate checks if the condition is true and if so, it validates the value using the specified rules.
2022
func (r WhenRule) Validate(value interface{}) error {
23+
return r.ValidateWithContext(nil, value)
24+
}
25+
26+
// ValidateWithContext checks if the condition is true and if so, it validates the value using the specified rules.
27+
func (r WhenRule) ValidateWithContext(ctx context.Context, value interface{}) error {
2128
if r.condition {
22-
return Validate(value, r.rules...)
29+
if ctx == nil {
30+
return Validate(value, r.rules...)
31+
} else {
32+
return ValidateWithContext(ctx, value, r.rules...)
33+
}
2334
}
2435

25-
return Validate(value, r.elseRules...)
36+
if ctx == nil {
37+
return Validate(value, r.elseRules...)
38+
} else {
39+
return ValidateWithContext(ctx, value, r.elseRules...)
40+
}
2641
}
2742

2843
// Else returns a validation rule that executes the given list of rules when the condition is false.

0 commit comments

Comments
 (0)