forked from go-ozzo/ozzo-validation
-
Notifications
You must be signed in to change notification settings - Fork 0
/
map.go
144 lines (125 loc) · 3.71 KB
/
map.go
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
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}
}
// 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)
}