each.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
   6  
   7  import (
   8  	"context"
   9  	"errors"
  10  	"reflect"
  11  	"strconv"
  12  )
  13  
  14  // Each returns a validation rule that loops through an iterable (map, slice or array)
  15  // and validates each value inside with the provided rules.
  16  // An empty iterable is considered valid. Use the Required rule to make sure the iterable is not empty.
  17  func Each(rules ...Rule) EachRule {
  18  	return EachRule{
  19  		rules: rules,
  20  	}
  21  }
  22  
  23  // EachRule is a validation rule that validates elements in a map/slice/array using the specified list of rules.
  24  type EachRule struct {
  25  	rules []Rule
  26  }
  27  
  28  // Validate loops through the given iterable and calls the Ozzo Validate() method for each value.
  29  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 {
  35  	errs := Errors{}
  36  
  37  	v := reflect.ValueOf(value)
  38  	switch v.Kind() {
  39  	case reflect.Map:
  40  		for _, k := range v.MapKeys() {
  41  			val := r.getInterface(v.MapIndex(k))
  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 {
  49  				errs[r.getString(k)] = err
  50  			}
  51  		}
  52  	case reflect.Slice, reflect.Array:
  53  		for i := 0; i < v.Len(); i++ {
  54  			val := r.getInterface(v.Index(i))
  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 {
  62  				errs[strconv.Itoa(i)] = err
  63  			}
  64  		}
  65  	default:
  66  		return errors.New("must be an iterable (map, slice or array)")
  67  	}
  68  
  69  	if len(errs) > 0 {
  70  		return errs
  71  	}
  72  	return nil
  73  }
  74  
  75  func (r EachRule) getInterface(value reflect.Value) interface{} {
  76  	switch value.Kind() {
  77  	case reflect.Ptr, reflect.Interface:
  78  		if value.IsNil() {
  79  			return nil
  80  		}
  81  		return value.Elem().Interface()
  82  	default:
  83  		return value.Interface()
  84  	}
  85  }
  86  
  87  func (r EachRule) getString(value reflect.Value) string {
  88  	switch value.Kind() {
  89  	case reflect.Ptr, reflect.Interface:
  90  		if value.IsNil() {
  91  			return ""
  92  		}
  93  		return value.Elem().String()
  94  	default:
  95  		return value.String()
  96  	}
  97  }
  98