-
Notifications
You must be signed in to change notification settings - Fork 5
/
validation.go
280 lines (246 loc) · 8.18 KB
/
validation.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
// Copyright 2016 Qiang Xue. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package validation provides configurable and extensible rules for validating data of various types.
package validation
import (
"context"
"fmt"
"reflect"
"strconv"
)
type (
// Validatable is the interface indicating the type implementing it supports data validation.
Validatable interface {
// Validate validates the data and returns an error if validation fails.
Validate() error
}
// ValidatableWithContext is the interface indicating the type implementing it supports context-aware data validation.
ValidatableWithContext interface {
// ValidateWithContext validates the data with the given context and returns an error if validation fails.
ValidateWithContext(ctx context.Context) error
}
// Rule represents a validation rule.
Rule interface {
// Validate validates a value and returns a value if validation fails.
Validate(value interface{}) error
}
// RuleWithContext represents a context-aware validation rule.
RuleWithContext interface {
// ValidateWithContext validates a value and returns a value if validation fails.
ValidateWithContext(ctx context.Context, value interface{}) error
}
// RuleFunc represents a validator function.
// You may wrap it as a Rule by calling By().
RuleFunc func(value interface{}) error
// RuleWithContextFunc represents a validator function that is context-aware.
// You may wrap it as a Rule by calling WithContext().
RuleWithContextFunc func(ctx context.Context, value interface{}) error
)
var (
// ErrorTag is the struct tag name used to customize the error field name for a struct field.
ErrorTag = "json"
// Skip is a special validation rule that indicates all rules following it should be skipped.
Skip = skipRule{skip: true}
validatableType = reflect.TypeOf((*Validatable)(nil)).Elem()
validatableWithContextType = reflect.TypeOf((*ValidatableWithContext)(nil)).Elem()
)
// Validate validates the given value and returns the validation error, if any.
//
// Validate performs validation using the following steps:
// 1. For each rule, call its `Validate()` to validate the value. Return if any error is found.
// 2. If the value being validated implements `Validatable`, call the value's `Validate()`.
// Return with the validation result.
// 3. If the value being validated is a map/slice/array, and the element type implements `Validatable`,
// for each element call the element value's `Validate()`. Return with the validation result.
func Validate(value interface{}, rules ...Rule) error {
for _, rule := range rules {
if s, ok := rule.(skipRule); ok && s.skip {
return nil
}
if err := rule.Validate(value); err != nil {
return err
}
}
rv := reflect.ValueOf(value)
if (rv.Kind() == reflect.Ptr || rv.Kind() == reflect.Interface) && rv.IsNil() {
return nil
}
if v, ok := value.(Validatable); ok {
return v.Validate()
}
switch rv.Kind() {
case reflect.Map:
if rv.Type().Elem().Implements(validatableType) {
return validateMap(rv)
}
case reflect.Slice, reflect.Array:
if rv.Type().Elem().Implements(validatableType) {
return validateSlice(rv)
}
case reflect.Ptr, reflect.Interface:
return Validate(rv.Elem().Interface())
}
return nil
}
// ValidateWithContext validates the given value with the given context and returns the validation error, if any.
//
// ValidateWithContext performs validation using the following steps:
// 1. For each rule, call its `ValidateWithContext()` to validate the value if the rule implements `RuleWithContext`.
// Otherwise call `Validate()` of the rule. Return if any error is found.
// 2. If the value being validated implements `ValidatableWithContext`, call the value's `ValidateWithContext()`
// and return with the validation result.
// 3. If the value being validated implements `Validatable`, call the value's `Validate()`
// and return with the validation result.
// 4. If the value being validated is a map/slice/array, and the element type implements `ValidatableWithContext`,
// for each element call the element value's `ValidateWithContext()`. Return with the validation result.
// 5. If the value being validated is a map/slice/array, and the element type implements `Validatable`,
// for each element call the element value's `Validate()`. Return with the validation result.
func ValidateWithContext(ctx context.Context, value interface{}, rules ...Rule) error {
for _, rule := range rules {
if s, ok := rule.(skipRule); ok && s.skip {
return nil
}
if rc, ok := rule.(RuleWithContext); ok {
if err := rc.ValidateWithContext(ctx, value); err != nil {
return err
}
} else if err := rule.Validate(value); err != nil {
return err
}
}
rv := reflect.ValueOf(value)
if (rv.Kind() == reflect.Ptr || rv.Kind() == reflect.Interface) && rv.IsNil() {
return nil
}
if v, ok := value.(ValidatableWithContext); ok {
return v.ValidateWithContext(ctx)
}
if v, ok := value.(Validatable); ok {
return v.Validate()
}
switch rv.Kind() {
case reflect.Map:
if rv.Type().Elem().Implements(validatableWithContextType) {
return validateMapWithContext(ctx, rv)
}
if rv.Type().Elem().Implements(validatableType) {
return validateMap(rv)
}
case reflect.Slice, reflect.Array:
if rv.Type().Elem().Implements(validatableWithContextType) {
return validateSliceWithContext(ctx, rv)
}
if rv.Type().Elem().Implements(validatableType) {
return validateSlice(rv)
}
case reflect.Ptr, reflect.Interface:
return ValidateWithContext(ctx, rv.Elem().Interface())
}
return nil
}
// validateMap validates a map of validatable elements
func validateMap(rv reflect.Value) error {
errs := Errors{}
for _, key := range rv.MapKeys() {
if mv := rv.MapIndex(key).Interface(); mv != nil {
if err := mv.(Validatable).Validate(); err != nil {
errs[fmt.Sprintf("%v", key.Interface())] = err
}
}
}
if len(errs) > 0 {
return errs
}
return nil
}
// validateMapWithContext validates a map of validatable elements with the given context.
func validateMapWithContext(ctx context.Context, rv reflect.Value) error {
errs := Errors{}
for _, key := range rv.MapKeys() {
if mv := rv.MapIndex(key).Interface(); mv != nil {
if err := mv.(ValidatableWithContext).ValidateWithContext(ctx); err != nil {
errs[fmt.Sprintf("%v", key.Interface())] = err
}
}
}
if len(errs) > 0 {
return errs
}
return nil
}
// validateSlice validates a slice/array of validatable elements
func validateSlice(rv reflect.Value) error {
errs := Errors{}
l := rv.Len()
for i := 0; i < l; i++ {
v := rv.Index(i)
if v.Kind() == reflect.Ptr && v.IsNil() {
continue
}
if ev := v.Interface(); ev != nil {
if err := ev.(Validatable).Validate(); err != nil {
errs[strconv.Itoa(i)] = err
}
}
}
if len(errs) > 0 {
return errs
}
return nil
}
// validateSliceWithContext validates a slice/array of validatable elements with the given context.
func validateSliceWithContext(ctx context.Context, rv reflect.Value) error {
errs := Errors{}
l := rv.Len()
for i := 0; i < l; i++ {
v := rv.Index(i)
if v.Kind() == reflect.Ptr && v.IsNil() {
continue
}
if ev := v.Interface(); ev != nil {
if err := ev.(ValidatableWithContext).ValidateWithContext(ctx); err != nil {
errs[strconv.Itoa(i)] = err
}
}
}
if len(errs) > 0 {
return errs
}
return nil
}
type skipRule struct {
skip bool
}
func (r skipRule) Validate(interface{}) error {
return nil
}
// When determines if all rules following it should be skipped.
func (r skipRule) When(condition bool) skipRule {
r.skip = condition
return r
}
type inlineRule struct {
f RuleFunc
fc RuleWithContextFunc
}
func (r *inlineRule) Validate(value interface{}) error {
if r.f == nil {
return r.fc(context.Background(), value)
}
return r.f(value)
}
func (r *inlineRule) ValidateWithContext(ctx context.Context, value interface{}) error {
if r.fc == nil {
return r.f(value)
}
return r.fc(ctx, value)
}
// By wraps a RuleFunc into a Rule.
func By(f RuleFunc) Rule {
return &inlineRule{f: f}
}
// WithContext wraps a RuleWithContextFunc into a context-aware Rule.
func WithContext(f RuleWithContextFunc) Rule {
return &inlineRule{fc: f}
}