1
by Gustavo Niemeyer
Bootstrapped package. |
1 |
package schema |
2 |
||
1.1.1
by Gustavo Niemeyer
Ported schema verification and coercion logic from Python. (!) |
3 |
import ( |
4 |
"fmt"
|
|
5 |
"os"
|
|
6 |
"reflect"
|
|
7 |
"regexp"
|
|
8 |
"strconv"
|
|
9 |
"strings"
|
|
10 |
)
|
|
11 |
||
2.1.6
by Gustavo Niemeyer
Renamed schema.M/L to schema.Map/ListType |
12 |
// All map types used in the schema package are of type MapType.
|
13 |
type MapType map[interface{}]interface{} |
|
2.1.2
by Gustavo Niemeyer
The schema package now consistently outputs values of type schema.M |
14 |
|
2.1.6
by Gustavo Niemeyer
Renamed schema.M/L to schema.Map/ListType |
15 |
// All the slice types generated in the schema package are of type ListType.
|
16 |
type ListType []interface{} |
|
2.1.2
by Gustavo Niemeyer
The schema package now consistently outputs values of type schema.M |
17 |
|
1.1.1
by Gustavo Niemeyer
Ported schema verification and coercion logic from Python. (!) |
18 |
// The Coerce method of the Checker interface is called recursively when
|
19 |
// v is being validated. If err is nil, newv is used as the new value
|
|
20 |
// at the recursion point. If err is non-nil, v is taken as invalid and
|
|
21 |
// may be either ignored or error out depending on where in the schema
|
|
22 |
// checking process the error happened. Checkers like OneOf may continue
|
|
23 |
// with an alternative, for instance.
|
|
24 |
type Checker interface { |
|
25 |
Coerce(v interface{}, path []string) (newv interface{}, err os.Error) |
|
26 |
}
|
|
27 |
||
28 |
type error struct { |
|
29 |
want string |
|
30 |
got interface{} |
|
31 |
path []string |
|
32 |
}
|
|
33 |
||
34 |
func (e error) String() string { |
|
35 |
var path string |
|
36 |
if e.path[0] == "." { |
|
37 |
path = strings.Join(e.path[1:], "") |
|
38 |
} else { |
|
39 |
path = strings.Join(e.path, "") |
|
40 |
}
|
|
41 |
if e.want == "" { |
|
42 |
return fmt.Sprintf("%s: unsupported value", path) |
|
43 |
}
|
|
44 |
if e.got == nil { |
|
45 |
return fmt.Sprintf("%s: expected %s, got nothing", path, e.want) |
|
46 |
}
|
|
47 |
return fmt.Sprintf("%s: expected %s, got %#v", path, e.want, e.got) |
|
48 |
}
|
|
49 |
||
50 |
// Any returns a Checker that succeeds with any input value and
|
|
51 |
// results in the value itself unprocessed.
|
|
52 |
func Any() Checker { |
|
53 |
return anyC{} |
|
54 |
}
|
|
55 |
||
56 |
type anyC struct{} |
|
57 |
||
58 |
func (c anyC) Coerce(v interface{}, path []string) (interface{}, os.Error) { |
|
59 |
return v, nil |
|
60 |
}
|
|
61 |
||
62 |
||
63 |
// Const returns a Checker that only succeeds if the input matches
|
|
64 |
// value exactly. The value is compared with reflect.DeepEqual.
|
|
65 |
func Const(value interface{}) Checker { |
|
66 |
return constC{value} |
|
67 |
}
|
|
68 |
||
69 |
type constC struct { |
|
70 |
value interface{} |
|
71 |
}
|
|
72 |
||
73 |
func (c constC) Coerce(v interface{}, path []string) (interface{}, os.Error) { |
|
74 |
if reflect.DeepEqual(v, c.value) { |
|
75 |
return v, nil |
|
76 |
}
|
|
77 |
return nil, error{fmt.Sprintf("%#v", c.value), v, path} |
|
78 |
}
|
|
79 |
||
80 |
// OneOf returns a Checker that attempts to Coerce the value with each
|
|
81 |
// of the provided checkers. The value returned by the first checker
|
|
82 |
// that succeeds will be returned by the OneOf checker itself. If no
|
|
83 |
// checker succeeds, OneOf will return an error on coercion.
|
|
84 |
func OneOf(options ...Checker) Checker { |
|
85 |
return oneOfC{options} |
|
86 |
}
|
|
87 |
||
88 |
type oneOfC struct { |
|
89 |
options []Checker |
|
90 |
}
|
|
91 |
||
92 |
func (c oneOfC) Coerce(v interface{}, path []string) (interface{}, os.Error) { |
|
93 |
for _, o := range c.options { |
|
94 |
newv, err := o.Coerce(v, path) |
|
95 |
if err == nil { |
|
96 |
return newv, nil |
|
97 |
}
|
|
98 |
}
|
|
99 |
return nil, error{path: path} |
|
100 |
}
|
|
101 |
||
102 |
// Bool returns a Checker that accepts boolean values only.
|
|
103 |
func Bool() Checker { |
|
104 |
return boolC{} |
|
105 |
}
|
|
106 |
||
107 |
type boolC struct{} |
|
108 |
||
109 |
func (c boolC) Coerce(v interface{}, path []string) (interface{}, os.Error) { |
|
2.1.3
by Gustavo Niemeyer
Fixed and tested schema coercing of nil values in several cases. |
110 |
if v != nil && reflect.TypeOf(v).Kind() == reflect.Bool { |
1.1.1
by Gustavo Niemeyer
Ported schema verification and coercion logic from Python. (!) |
111 |
return v, nil |
112 |
}
|
|
113 |
return nil, error{"bool", v, path} |
|
114 |
}
|
|
115 |
||
116 |
// Int returns a Checker that accepts any integer value, and returns
|
|
2.1.3
by Gustavo Niemeyer
Fixed and tested schema coercing of nil values in several cases. |
117 |
// the same value consistently typed as an int64.
|
1.1.1
by Gustavo Niemeyer
Ported schema verification and coercion logic from Python. (!) |
118 |
func Int() Checker { |
119 |
return intC{} |
|
120 |
}
|
|
121 |
||
122 |
type intC struct{} |
|
123 |
||
124 |
func (c intC) Coerce(v interface{}, path []string) (interface{}, os.Error) { |
|
2.1.3
by Gustavo Niemeyer
Fixed and tested schema coercing of nil values in several cases. |
125 |
if v == nil { |
126 |
return nil, error{"int", v, path} |
|
127 |
}
|
|
1.1.1
by Gustavo Niemeyer
Ported schema verification and coercion logic from Python. (!) |
128 |
switch reflect.TypeOf(v).Kind() { |
129 |
case reflect.Int: |
|
130 |
case reflect.Int8: |
|
131 |
case reflect.Int16: |
|
132 |
case reflect.Int32: |
|
133 |
case reflect.Int64: |
|
134 |
default: |
|
135 |
return nil, error{"int", v, path} |
|
136 |
}
|
|
137 |
return reflect.ValueOf(v).Int(), nil |
|
138 |
}
|
|
139 |
||
140 |
// Int returns a Checker that accepts any float value, and returns
|
|
2.1.3
by Gustavo Niemeyer
Fixed and tested schema coercing of nil values in several cases. |
141 |
// the same value consistently typed as a float64.
|
1.1.1
by Gustavo Niemeyer
Ported schema verification and coercion logic from Python. (!) |
142 |
func Float() Checker { |
143 |
return floatC{} |
|
144 |
}
|
|
145 |
||
146 |
type floatC struct{} |
|
147 |
||
148 |
func (c floatC) Coerce(v interface{}, path []string) (interface{}, os.Error) { |
|
2.1.3
by Gustavo Niemeyer
Fixed and tested schema coercing of nil values in several cases. |
149 |
if v == nil { |
150 |
return nil, error{"float", v, path} |
|
151 |
}
|
|
1.1.1
by Gustavo Niemeyer
Ported schema verification and coercion logic from Python. (!) |
152 |
switch reflect.TypeOf(v).Kind() { |
153 |
case reflect.Float32: |
|
154 |
case reflect.Float64: |
|
155 |
default: |
|
156 |
return nil, error{"float", v, path} |
|
157 |
}
|
|
158 |
return reflect.ValueOf(v).Float(), nil |
|
159 |
}
|
|
160 |
||
161 |
||
162 |
// String returns a Checker that accepts a string value only and returns
|
|
163 |
// it unprocessed.
|
|
164 |
func String() Checker { |
|
165 |
return stringC{} |
|
166 |
}
|
|
167 |
||
168 |
type stringC struct{} |
|
169 |
||
170 |
func (c stringC) Coerce(v interface{}, path []string) (interface{}, os.Error) { |
|
2.1.3
by Gustavo Niemeyer
Fixed and tested schema coercing of nil values in several cases. |
171 |
if v != nil && reflect.TypeOf(v).Kind() == reflect.String { |
1.1.1
by Gustavo Niemeyer
Ported schema verification and coercion logic from Python. (!) |
172 |
return reflect.ValueOf(v).String(), nil |
173 |
}
|
|
174 |
return nil, error{"string", v, path} |
|
175 |
}
|
|
176 |
||
177 |
func SimpleRegexp() Checker { |
|
178 |
return sregexpC{} |
|
179 |
}
|
|
180 |
||
181 |
type sregexpC struct{} |
|
182 |
||
183 |
func (c sregexpC) Coerce(v interface{}, path []string) (interface{}, os.Error) { |
|
184 |
// XXX The regexp package happens to be extremely simple right now.
|
|
185 |
// Once exp/regexp goes mainstream, we'll have to update this
|
|
186 |
// logic to use a more widely accepted regexp subset.
|
|
2.1.3
by Gustavo Niemeyer
Fixed and tested schema coercing of nil values in several cases. |
187 |
if v != nil && reflect.TypeOf(v).Kind() == reflect.String { |
1.1.1
by Gustavo Niemeyer
Ported schema verification and coercion logic from Python. (!) |
188 |
s := reflect.ValueOf(v).String() |
189 |
_, err := regexp.Compile(s) |
|
190 |
if err != nil { |
|
191 |
return nil, error{"valid regexp", s, path} |
|
192 |
}
|
|
193 |
return v, nil |
|
194 |
}
|
|
195 |
return nil, error{"regexp string", v, path} |
|
196 |
}
|
|
197 |
||
2.1.2
by Gustavo Niemeyer
The schema package now consistently outputs values of type schema.M |
198 |
// List returns a Checker that accepts a slice value with values
|
1.1.1
by Gustavo Niemeyer
Ported schema verification and coercion logic from Python. (!) |
199 |
// that are processed with the elem checker. If any element of the
|
200 |
// provided slice value fails to be processed, processing will stop
|
|
201 |
// and return with the obtained error.
|
|
2.1.2
by Gustavo Niemeyer
The schema package now consistently outputs values of type schema.M |
202 |
//
|
2.1.6
by Gustavo Niemeyer
Renamed schema.M/L to schema.Map/ListType |
203 |
// The coerced output value has type schema.ListType.
|
1.1.1
by Gustavo Niemeyer
Ported schema verification and coercion logic from Python. (!) |
204 |
func List(elem Checker) Checker { |
205 |
return listC{elem} |
|
206 |
}
|
|
207 |
||
208 |
type listC struct { |
|
209 |
elem Checker |
|
210 |
}
|
|
211 |
||
212 |
func (c listC) Coerce(v interface{}, path []string) (interface{}, os.Error) { |
|
213 |
rv := reflect.ValueOf(v) |
|
214 |
if rv.Kind() != reflect.Slice { |
|
215 |
return nil, error{"list", v, path} |
|
216 |
}
|
|
217 |
||
218 |
path = append(path, "[", "?", "]") |
|
219 |
||
220 |
l := rv.Len() |
|
2.1.6
by Gustavo Niemeyer
Renamed schema.M/L to schema.Map/ListType |
221 |
out := make(ListType, 0, l) |
1.1.1
by Gustavo Niemeyer
Ported schema verification and coercion logic from Python. (!) |
222 |
for i := 0; i != l; i++ { |
223 |
path[len(path)-2] = strconv.Itoa(i) |
|
224 |
elem, err := c.elem.Coerce(rv.Index(i).Interface(), path) |
|
225 |
if err != nil { |
|
226 |
return nil, err |
|
227 |
}
|
|
228 |
out = append(out, elem) |
|
229 |
}
|
|
230 |
return out, nil |
|
231 |
}
|
|
232 |
||
233 |
// Map returns a Checker that accepts a map value. Every key and value
|
|
234 |
// in the map are processed with the respective checker, and if any
|
|
235 |
// value fails to be coerced, processing stops and returns with the
|
|
236 |
// underlying error.
|
|
2.1.2
by Gustavo Niemeyer
The schema package now consistently outputs values of type schema.M |
237 |
//
|
2.1.6
by Gustavo Niemeyer
Renamed schema.M/L to schema.Map/ListType |
238 |
// The coerced output value has type schema.MapType.
|
1.1.1
by Gustavo Niemeyer
Ported schema verification and coercion logic from Python. (!) |
239 |
func Map(key Checker, value Checker) Checker { |
240 |
return mapC{key, value} |
|
241 |
}
|
|
242 |
||
243 |
type mapC struct { |
|
244 |
key Checker |
|
245 |
value Checker |
|
246 |
}
|
|
247 |
||
248 |
func (c mapC) Coerce(v interface{}, path []string) (interface{}, os.Error) { |
|
249 |
rv := reflect.ValueOf(v) |
|
250 |
if rv.Kind() != reflect.Map { |
|
251 |
return nil, error{"map", v, path} |
|
252 |
}
|
|
253 |
||
254 |
vpath := append(path, ".", "?") |
|
255 |
||
256 |
l := rv.Len() |
|
2.1.6
by Gustavo Niemeyer
Renamed schema.M/L to schema.Map/ListType |
257 |
out := make(MapType, l) |
1.1.1
by Gustavo Niemeyer
Ported schema verification and coercion logic from Python. (!) |
258 |
keys := rv.MapKeys() |
259 |
for i := 0; i != l; i++ { |
|
260 |
k := keys[i] |
|
261 |
newk, err := c.key.Coerce(k.Interface(), path) |
|
262 |
if err != nil { |
|
263 |
return nil, err |
|
264 |
}
|
|
265 |
vpath[len(vpath)-1] = fmt.Sprint(k.Interface()) |
|
266 |
newv, err := c.value.Coerce(rv.MapIndex(k).Interface(), vpath) |
|
267 |
if err != nil { |
|
268 |
return nil, err |
|
269 |
}
|
|
270 |
out[newk] = newv |
|
271 |
}
|
|
272 |
return out, nil |
|
273 |
}
|
|
274 |
||
275 |
type Fields map[string]Checker |
|
276 |
type Optional []string |
|
277 |
||
278 |
// FieldMap returns a Checker that accepts a map value with defined
|
|
279 |
// string keys. Every key has an independent checker associated,
|
|
280 |
// and processing will only succeed if all the values succeed
|
|
281 |
// individually. If a field fails to be processed, processing stops
|
|
282 |
// and returns with the underlying error.
|
|
2.1.2
by Gustavo Niemeyer
The schema package now consistently outputs values of type schema.M |
283 |
//
|
2.1.6
by Gustavo Niemeyer
Renamed schema.M/L to schema.Map/ListType |
284 |
// The coerced output value has type schema.MapType.
|
1.1.1
by Gustavo Niemeyer
Ported schema verification and coercion logic from Python. (!) |
285 |
func FieldMap(fields Fields, optional Optional) Checker { |
286 |
return fieldMapC{fields, optional} |
|
287 |
}
|
|
288 |
||
289 |
type fieldMapC struct { |
|
290 |
fields Fields |
|
291 |
optional []string |
|
292 |
}
|
|
293 |
||
294 |
func (c fieldMapC) isOptional(key string) bool { |
|
295 |
for _, k := range c.optional { |
|
296 |
if k == key { |
|
297 |
return true |
|
298 |
}
|
|
299 |
}
|
|
300 |
return false |
|
301 |
}
|
|
302 |
||
303 |
func (c fieldMapC) Coerce(v interface{}, path []string) (interface{}, os.Error) { |
|
304 |
rv := reflect.ValueOf(v) |
|
305 |
if rv.Kind() != reflect.Map { |
|
306 |
return nil, error{"map", v, path} |
|
307 |
}
|
|
308 |
||
309 |
vpath := append(path, ".", "?") |
|
310 |
||
311 |
l := rv.Len() |
|
2.1.6
by Gustavo Niemeyer
Renamed schema.M/L to schema.Map/ListType |
312 |
out := make(MapType, l) |
1.1.1
by Gustavo Niemeyer
Ported schema verification and coercion logic from Python. (!) |
313 |
for k, checker := range c.fields { |
314 |
vpath[len(vpath)-1] = k |
|
315 |
var value interface{} |
|
316 |
valuev := rv.MapIndex(reflect.ValueOf(k)) |
|
317 |
if valuev.IsValid() { |
|
318 |
value = valuev.Interface() |
|
319 |
} else if c.isOptional(k) { |
|
320 |
continue
|
|
321 |
}
|
|
322 |
newv, err := checker.Coerce(value, vpath) |
|
323 |
if err != nil { |
|
324 |
return nil, err |
|
325 |
}
|
|
326 |
out[k] = newv |
|
327 |
}
|
|
328 |
return out, nil |
|
329 |
}
|
|
330 |
||
331 |
// FieldMapSet returns a Checker that accepts a map value checked
|
|
332 |
// against one of several FieldMap checkers. The actual checker
|
|
333 |
// used is the first one whose checker associated with the selector
|
|
334 |
// field processes the map correctly. If no checker processes
|
|
335 |
// the selector value correctly, an error is returned.
|
|
2.1.2
by Gustavo Niemeyer
The schema package now consistently outputs values of type schema.M |
336 |
//
|
2.1.6
by Gustavo Niemeyer
Renamed schema.M/L to schema.Map/ListType |
337 |
// The coerced output value has type schema.MapType.
|
1.1.1
by Gustavo Niemeyer
Ported schema verification and coercion logic from Python. (!) |
338 |
func FieldMapSet(selector string, maps []Checker) Checker { |
339 |
fmaps := make([]fieldMapC, len(maps)) |
|
340 |
for i, m := range maps { |
|
341 |
if fmap, ok := m.(fieldMapC); ok { |
|
342 |
if checker, _ := fmap.fields[selector]; checker == nil { |
|
343 |
panic("FieldMapSet has a FieldMap with a missing selector") |
|
344 |
}
|
|
345 |
fmaps[i] = fmap |
|
346 |
} else { |
|
347 |
panic("FieldMapSet got a non-FieldMap checker") |
|
348 |
}
|
|
349 |
}
|
|
350 |
return mapSetC{selector, fmaps} |
|
351 |
}
|
|
352 |
||
353 |
type mapSetC struct { |
|
354 |
selector string |
|
355 |
fmaps []fieldMapC |
|
356 |
}
|
|
357 |
||
358 |
func (c mapSetC) Coerce(v interface{}, path []string) (interface{}, os.Error) { |
|
359 |
rv := reflect.ValueOf(v) |
|
360 |
if rv.Kind() != reflect.Map { |
|
361 |
return nil, error{"map", v, path} |
|
362 |
}
|
|
363 |
||
364 |
var selector interface{} |
|
365 |
selectorv := rv.MapIndex(reflect.ValueOf(c.selector)) |
|
366 |
if selectorv.IsValid() { |
|
367 |
selector = selectorv.Interface() |
|
368 |
for _, fmap := range c.fmaps { |
|
369 |
_, err := fmap.fields[c.selector].Coerce(selector, path) |
|
370 |
if err != nil { |
|
371 |
continue
|
|
372 |
}
|
|
373 |
return fmap.Coerce(v, path) |
|
374 |
}
|
|
375 |
}
|
|
376 |
return nil, error{"supported selector", selector, append(path, ".", c.selector)} |
|
377 |
}
|