map.go raw

   1  package validation
   2  
   3  import (
   4  	"context"
   5  	"errors"
   6  	"fmt"
   7  	"reflect"
   8  )
   9  
  10  var (
  11  	// ErrNotMap is the error that the value being validated is not a map.
  12  	ErrNotMap = errors.New("only a map can be validated")
  13  
  14  	// ErrKeyWrongType is the error returned in case of an incorrect key type.
  15  	ErrKeyWrongType = NewError("validation_key_wrong_type", "key not the correct type")
  16  
  17  	// ErrKeyMissing is the error returned in case of a missing key.
  18  	ErrKeyMissing = NewError("validation_key_missing", "required key is missing")
  19  
  20  	// ErrKeyUnexpected is the error returned in case of an unexpected key.
  21  	ErrKeyUnexpected = NewError("validation_key_unexpected", "key not expected")
  22  )
  23  
  24  type (
  25  	// MapRule represents a rule set associated with a map.
  26  	MapRule struct {
  27  		keys           []*KeyRules
  28  		allowExtraKeys bool
  29  	}
  30  
  31  	// KeyRules represents a rule set associated with a map key.
  32  	KeyRules struct {
  33  		key      interface{}
  34  		optional bool
  35  		rules    []Rule
  36  	}
  37  )
  38  
  39  // Map returns a validation rule that checks the keys and values of a map.
  40  // This rule should only be used for validating maps, or a validation error will be reported.
  41  // Use Key() to specify map keys that need to be validated. Each Key() call specifies a single key which can
  42  // be associated with multiple rules.
  43  // For example,
  44  //    validation.Map(
  45  //        validation.Key("Name", validation.Required),
  46  //        validation.Key("Value", validation.Required, validation.Length(5, 10)),
  47  //    )
  48  //
  49  // A nil value is considered valid. Use the Required rule to make sure a map value is present.
  50  func Map(keys ...*KeyRules) MapRule {
  51  	return MapRule{keys: keys}
  52  }
  53  
  54  // AllowExtraKeys configures the rule to ignore extra keys.
  55  func (r MapRule) AllowExtraKeys() MapRule {
  56  	r.allowExtraKeys = true
  57  	return r
  58  }
  59  
  60  // Validate checks if the given value is valid or not.
  61  func (r MapRule) Validate(m interface{}) error {
  62  	return r.ValidateWithContext(nil, m)
  63  }
  64  
  65  // ValidateWithContext checks if the given value is valid or not.
  66  func (r MapRule) ValidateWithContext(ctx context.Context, m interface{}) error {
  67  	value := reflect.ValueOf(m)
  68  	if value.Kind() == reflect.Ptr {
  69  		value = value.Elem()
  70  	}
  71  	if value.Kind() != reflect.Map {
  72  		// must be a map
  73  		return NewInternalError(ErrNotMap)
  74  	}
  75  	if value.IsNil() {
  76  		// treat a nil map as valid
  77  		return nil
  78  	}
  79  
  80  	errs := Errors{}
  81  	kt := value.Type().Key()
  82  
  83  	var extraKeys map[interface{}]bool
  84  	if !r.allowExtraKeys {
  85  		extraKeys = make(map[interface{}]bool, value.Len())
  86  		for _, k := range value.MapKeys() {
  87  			extraKeys[k.Interface()] = true
  88  		}
  89  	}
  90  
  91  	for _, kr := range r.keys {
  92  		var err error
  93  		if kv := reflect.ValueOf(kr.key); !kt.AssignableTo(kv.Type()) {
  94  			err = ErrKeyWrongType
  95  		} else if vv := value.MapIndex(kv); !vv.IsValid() {
  96  			if !kr.optional {
  97  				err = ErrKeyMissing
  98  			}
  99  		} else if ctx == nil {
 100  			err = Validate(vv.Interface(), kr.rules...)
 101  		} else {
 102  			err = ValidateWithContext(ctx, vv.Interface(), kr.rules...)
 103  		}
 104  		if err != nil {
 105  			if ie, ok := err.(InternalError); ok && ie.InternalError() != nil {
 106  				return err
 107  			}
 108  			errs[getErrorKeyName(kr.key)] = err
 109  		}
 110  		if !r.allowExtraKeys {
 111  			delete(extraKeys, kr.key)
 112  		}
 113  	}
 114  
 115  	if !r.allowExtraKeys {
 116  		for key := range extraKeys {
 117  			errs[getErrorKeyName(key)] = ErrKeyUnexpected
 118  		}
 119  	}
 120  
 121  	if len(errs) > 0 {
 122  		return errs
 123  	}
 124  	return nil
 125  }
 126  
 127  // Key specifies a map key and the corresponding validation rules.
 128  func Key(key interface{}, rules ...Rule) *KeyRules {
 129  	return &KeyRules{
 130  		key:   key,
 131  		rules: rules,
 132  	}
 133  }
 134  
 135  // Optional configures the rule to ignore the key if missing.
 136  func (r *KeyRules) Optional() *KeyRules {
 137  	r.optional = true
 138  	return r
 139  }
 140  
 141  // getErrorKeyName returns the name that should be used to represent the validation error of a map key.
 142  func getErrorKeyName(key interface{}) string {
 143  	return fmt.Sprintf("%v", key)
 144  }
 145