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