-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathschema.go
140 lines (121 loc) · 3.31 KB
/
schema.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
package subtest
import (
"fmt"
"reflect"
"sort"
"strings"
)
// Fields allow validating each value in a map against a particular check.
type Fields map[interface{}]Check
// Check validates vf against m, expecting vf to return a map.
func (m Fields) Check(vf ValueFunc) error {
s := Schema{
Fields: m,
}
return s.Check(vf)
}
// OrderedKeys returns all keys in m in alphanumerical order.
func (m Fields) OrderedKeys() []interface{} {
keys := make([]interface{}, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Slice(keys, func(i, j int) bool {
return fmt.Sprint(keys[i]) < fmt.Sprint(keys[j])
})
return keys
}
// Schema allow validating each value in a map against a particular check, just
// like the Fields type, but with additional configuration options.
type Schema struct {
// Fields contain a mapping of keys to checks.
Fields Fields
// Required, if set, contain a list of required keys. When the list is
// explicitly defined as an empty list, no keys will be considered required.
// When the field holds a nil value, all keys present in Fields will be
// considered required.
Required []interface{}
// AdditionalFields if set, contain a check used to validate all fields
// where the key is not present in Fields. When the field holds a nil
// value, no additional keys are allowed. To skip validation of additional
// keys, the Any() check can be used.
AdditionalFields Check
}
// Check validates vf against s, expecting vf to return a map.
func (s Schema) Check(vf ValueFunc) error {
if vf == nil {
return FailGot("missing value function", vf)
}
got, err := vf()
if err != nil {
return FailGot("value function returns an error", err)
}
rv := reflect.ValueOf(got)
switch rv.Kind() {
case reflect.Map:
return s.checkMap(got)
// TODO: handle reflect.Struct
default:
return FailGot("not a map", got)
}
}
func (s Schema) checkMap(got interface{}) error {
rv := reflect.ValueOf(got)
if rv.Kind() != reflect.Map {
return FailGot("not a map", got)
}
rKeys := rv.MapKeys()
sort.Slice(rKeys, func(i, j int) bool {
return fmt.Sprint(rKeys[i].Interface()) < fmt.Sprint(rKeys[j].Interface())
})
var errs Errors
var extraKeys []string
var check Check
var vf ValueFunc
var k interface{}
for _, rk := range rKeys {
check = nil
k = rk.Interface()
vf = Value(rv.MapIndex(rk).Interface())
if s.Fields != nil {
check = s.Fields[k]
}
if check == nil {
check = s.AdditionalFields
}
if check == nil {
extraKeys = append(extraKeys, fmt.Sprintf("%#v", k))
continue
}
if err := check.Check(vf); err != nil {
errs = append(errs, KeyError(k, err))
}
}
if len(extraKeys) > 0 {
errs = append(errs, Failf("got additional keys: %v", strings.Join(extraKeys, ", ")))
}
keySet := make(map[interface{}]struct{}, rv.Len())
for _, rk := range rKeys {
keySet[rk.Interface()] = struct{}{}
}
var required []interface{}
if s.Required == nil {
required = s.Fields.OrderedKeys()
} else {
required = s.Required
}
var missingKeys []string
for _, k := range required {
_, ok := keySet[k]
if !ok {
missingKeys = append(missingKeys, fmt.Sprintf("%#v", k))
}
}
if len(missingKeys) > 0 {
errs = append(errs, Failf("missing required keys: %v", strings.Join(missingKeys, ", ")))
}
if len(errs) > 0 {
return fmt.Errorf("%s: %w", msgSchemaMatch, errs)
}
return nil
}