123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- package valid
- import (
- "context"
- "fmt"
- "reflect"
- "strconv"
- )
- var (
- 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
- }
- // By wraps a RuleFunc into a Rule.
- func By(f RuleFunc) Rule {
- return &inlineRule{f: f}
- }
- // RuleFunc represents a validator function.
- // You may wrap it as a Rule by calling By().
- type RuleFunc func(value interface{}) error
- // WithContext wraps a RuleWithContextFunc into a context-aware Rule.
- func WithContext(f RuleWithContextFunc) Rule {
- return &inlineRule{fc: f}
- }
- // RuleWithContextFunc represents a validator function that is context-aware.
- // You may wrap it as a Rule by calling WithContext().
- type RuleWithContextFunc func(ctx context.Context, value interface{}) error
- // 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++ {
- if ev := rv.Index(i).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++ {
- if ev := rv.Index(i).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 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)
- }
|