123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- package valid
- import (
- "context"
- "errors"
- "fmt"
- "reflect"
- )
- var (
- // ErrNotMap is the error that the value being validated is not a map.
- ErrNotMap = errors.New("only a map can be validated")
- // ErrKeyWrongType is the error returned in case of an incorrect key type.
- ErrKeyWrongType = NewError("validation_key_wrong_type", "key not the correct type")
- // ErrKeyMissing is the error returned in case of a missing key.
- ErrKeyMissing = NewError("validation_key_missing", "required key is missing")
- // ErrKeyUnexpected is the error returned in case of an unexpected key.
- ErrKeyUnexpected = NewError("validation_key_unexpected", "key not expected")
- )
- type (
- // MapRule represents a rule set associated with a map.
- MapRule struct {
- keys []*KeyRules
- allowExtraKeys bool
- }
- // KeyRules represents a rule set associated with a map key.
- KeyRules struct {
- key interface{}
- optional bool
- rules []Rule
- }
- )
- // Map returns a validation rule that checks the keys and values of a map.
- // This rule should only be used for validating maps, or a validation error will be reported.
- // Use Key() to specify map keys that need to be validated. Each Key() call specifies a single key which can
- // be associated with multiple rules.
- // For example,
- // validation.Map(
- // validation.Key("Name", validation.Required),
- // validation.Key("Value", validation.Required, validation.Length(5, 10)),
- // )
- //
- // A nil value is considered valid. Use the Required rule to make sure a map value is present.
- func Map(keys ...*KeyRules) MapRule {
- return MapRule{keys: keys}
- }
- // AllowExtraKeys configures the rule to ignore extra keys.
- func (r MapRule) AllowExtraKeys() MapRule {
- r.allowExtraKeys = true
- return r
- }
- // Validate checks if the given value is valid or not.
- func (r MapRule) Validate(m interface{}) error {
- return r.ValidateWithContext(nil, m)
- }
- // ValidateWithContext checks if the given value is valid or not.
- func (r MapRule) ValidateWithContext(ctx context.Context, m interface{}) error {
- value := reflect.ValueOf(m)
- if value.Kind() == reflect.Ptr {
- value = value.Elem()
- }
- if value.Kind() != reflect.Map {
- // must be a map
- return NewInternalError(ErrNotMap)
- }
- if value.IsNil() {
- // treat a nil map as valid
- return nil
- }
- errs := Errors{}
- kt := value.Type().Key()
- var extraKeys map[interface{}]bool
- if !r.allowExtraKeys {
- extraKeys = make(map[interface{}]bool, value.Len())
- for _, k := range value.MapKeys() {
- extraKeys[k.Interface()] = true
- }
- }
- for _, kr := range r.keys {
- var err error
- if kv := reflect.ValueOf(kr.key); !kt.AssignableTo(kv.Type()) {
- err = ErrKeyWrongType
- } else if vv := value.MapIndex(kv); !vv.IsValid() {
- if !kr.optional {
- err = ErrKeyMissing
- }
- } else if ctx == nil {
- err = Validate(vv.Interface(), kr.rules...)
- } else {
- err = ValidateWithContext(ctx, vv.Interface(), kr.rules...)
- }
- if err != nil {
- if ie, ok := err.(InternalError); ok && ie.InternalError() != nil {
- return err
- }
- errs[getErrorKeyName(kr.key)] = err
- }
- if !r.allowExtraKeys {
- delete(extraKeys, kr.key)
- }
- }
- if !r.allowExtraKeys {
- for key := range extraKeys {
- errs[getErrorKeyName(key)] = ErrKeyUnexpected
- }
- }
- if len(errs) > 0 {
- return errs
- }
- return nil
- }
- // Key specifies a map key and the corresponding validation rules.
- func Key(key interface{}, rules ...Rule) *KeyRules {
- return &KeyRules{
- key: key,
- rules: rules,
- }
- }
- // Optional configures the rule to ignore the key if missing.
- func (r *KeyRules) Optional() *KeyRules {
- r.optional = true
- return r
- }
- // getErrorKeyName returns the name that should be used to represent the validation error of a map key.
- func getErrorKeyName(key interface{}) string {
- return fmt.Sprintf("%v", key)
- }
|