validation.go raw

   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