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