Repository navigation
Expand file tree
/
Copy pathmap.go
More file actions
217 lines (190 loc) · 6.48 KB
/
Copy pathmap.go
File metadata and controls
217 lines (190 loc) · 6.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
package validation
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}
}
// DynamicMap returns a validation rule that checks the keys and values of a map.
// The map is allowed to have extra keys by default, compared to original `Map(...)` function that will return an error when unspecified key(s) existed in the 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.DynamicMap(
// 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 DynamicMap(keys ...*KeyRules) MapRule {
return MapRule{keys: keys, allowExtraKeys: true}
}
// 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)
}
// validateMapKey validates a single map key against its rules, returning the
// resulting error, or nil if the key is valid (or optional and missing).
func validateMapKey(ctx context.Context, value reflect.Value, kt reflect.Type, kr *KeyRules) error {
kv := reflect.ValueOf(kr.key)
if !kt.AssignableTo(kv.Type()) {
return ErrKeyWrongType
}
vv := value.MapIndex(kv)
if !vv.IsValid() {
if kr.optional {
return nil
}
return missingKeyError(kr.rules)
}
return validateWithOptionalContext(ctx, vv.Interface(), kr.rules...)
}
// missingKeyErrorer is implemented by the rules whose customized error replaces ErrKeyMissing
// when a required map key is missing. missingKeyError returns nil when the error was not customized.
type missingKeyErrorer interface {
missingKeyError() error
}
// missingKeyError returns the error for a missing required map key. The key's rules are never
// called with a nil value: rules written for the key's type may not expect nil (#222). The first
// Required or NotNil rule with an error set by Error or ErrorObject provides the error (#134);
// otherwise it is ErrKeyMissing.
func missingKeyError(rules []Rule) error {
for _, rule := range rules {
if s, ok := rule.(skipRule); ok && s.skip {
break
}
if r, ok := rule.(missingKeyErrorer); ok {
if err := r.missingKeyError(); err != nil {
return err
}
}
}
return ErrKeyMissing
}
// validateWithOptionalContext calls ValidateWithContext if ctx is set, or Validate otherwise.
func validateWithOptionalContext(ctx context.Context, value interface{}, rules ...Rule) error {
if ctx == nil {
return Validate(value, rules...)
}
return ValidateWithContext(ctx, value, rules...)
}
// 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 {
err := validateMapKey(ctx, value, kt, kr)
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.
//
// If the key is missing, ErrKeyMissing is reported and the rules are not called. A Required or NotNil
// rule among them whose message or error was customized with Error or ErrorObject reports its own
// error instead. A Required nested in When, or a change of the global ErrRequired, does not:
// the error stays ErrKeyMissing.
func Key(key interface{}, rules ...Rule) *KeyRules {
return &KeyRules{
key: key,
rules: rules,
}
}
// OptionalKey specifies a map key and the corresponding validation rules.
// The rule will be ignored if the key is missing.
func OptionalKey(key interface{}, rules ...Rule) *KeyRules {
return &KeyRules{
key: key,
rules: rules,
optional: true,
}
}
// 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)
}