Merge lp:~niemeyer/pyjuju/go-iface-schema into lp:pyjuju/go
- go-iface-schema
- Merge into go
Proposed by
Gustavo Niemeyer
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Kapil Thangavelu | ||||
Approved revision: | 6 | ||||
Merged at revision: | 3 | ||||
Proposed branch: | lp:~niemeyer/pyjuju/go-iface-schema | ||||
Merge into: | lp:pyjuju/go | ||||
Diff against target: |
562 lines (+304/-15) 6 files modified
formula/Makefile (+23/-0) formula/export_test.go (+11/-0) formula/formula.go (+101/-0) formula/formula_test.go (+86/-0) schema/schema.go (+29/-9) schema/schema_test.go (+54/-6) |
||||
To merge this branch: | bzr merge lp:~niemeyer/pyjuju/go-iface-schema | ||||
Related bugs: |
|
||||
Related blueprints: |
The Formula Store
(Undefined)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Kapil Thangavelu (community) | Approve | ||
William Reade (community) | Approve | ||
Review via email: mp+73099@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Kapil Thangavelu (hazmat) wrote : | # |
LGTM, thanks for addressing the naming as well in the subsequent branch. as well as the help in getting the tests running.
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added directory 'formula' | |||
2 | === added file 'formula/Makefile' | |||
3 | --- formula/Makefile 1970-01-01 00:00:00 +0000 | |||
4 | +++ formula/Makefile 2011-08-26 20:38:37 +0000 | |||
5 | @@ -0,0 +1,23 @@ | |||
6 | 1 | include $(GOROOT)/src/Make.inc | ||
7 | 2 | |||
8 | 3 | all: package | ||
9 | 4 | |||
10 | 5 | TARG=launchpad.net/ensemble/go/formula | ||
11 | 6 | |||
12 | 7 | GOFILES=\ | ||
13 | 8 | formula.go\ | ||
14 | 9 | |||
15 | 10 | GOFMT=gofmt | ||
16 | 11 | BADFMT:=$(shell $(GOFMT) -l $(GOFILES) $(CGOFILES) $(wildcard *_test.go)) | ||
17 | 12 | |||
18 | 13 | gofmt: $(BADFMT) | ||
19 | 14 | @for F in $(BADFMT); do $(GOFMT) -w $$F && echo $$F; done | ||
20 | 15 | |||
21 | 16 | ifneq ($(BADFMT),) | ||
22 | 17 | ifneq ($(MAKECMDGOALS),gofmt) | ||
23 | 18 | $(warning WARNING: make gofmt: $(BADFMT)) | ||
24 | 19 | endif | ||
25 | 20 | endif | ||
26 | 21 | |||
27 | 22 | include $(GOROOT)/src/Make.pkg | ||
28 | 23 | |||
29 | 0 | 24 | ||
30 | === added file 'formula/export_test.go' | |||
31 | --- formula/export_test.go 1970-01-01 00:00:00 +0000 | |||
32 | +++ formula/export_test.go 2011-08-26 20:38:37 +0000 | |||
33 | @@ -0,0 +1,11 @@ | |||
34 | 1 | package formula | ||
35 | 2 | |||
36 | 3 | import ( | ||
37 | 4 | "launchpad.net/ensemble/go/schema" | ||
38 | 5 | ) | ||
39 | 6 | |||
40 | 7 | // Export meaningful bits for tests only. | ||
41 | 8 | |||
42 | 9 | func IfaceExpander(limit interface{}) schema.Checker { | ||
43 | 10 | return ifaceExpander(limit) | ||
44 | 11 | } | ||
45 | 0 | 12 | ||
46 | === added file 'formula/formula.go' | |||
47 | --- formula/formula.go 1970-01-01 00:00:00 +0000 | |||
48 | +++ formula/formula.go 2011-08-26 20:38:37 +0000 | |||
49 | @@ -0,0 +1,101 @@ | |||
50 | 1 | package formula | ||
51 | 2 | |||
52 | 3 | import ( | ||
53 | 4 | "fmt" | ||
54 | 5 | "launchpad.net/ensemble/go/schema" | ||
55 | 6 | "os" | ||
56 | 7 | "strconv" | ||
57 | 8 | "strings" | ||
58 | 9 | ) | ||
59 | 10 | |||
60 | 11 | func errorf(format string, args ...interface{}) os.Error { | ||
61 | 12 | return os.NewError(fmt.Sprintf(format, args...)) | ||
62 | 13 | } | ||
63 | 14 | |||
64 | 15 | // ParseId splits a formula identifier into its constituting parts. | ||
65 | 16 | func ParseId(id string) (namespace string, name string, rev int, err os.Error) { | ||
66 | 17 | colon := strings.Index(id, ":") | ||
67 | 18 | if colon == -1 { | ||
68 | 19 | err = errorf("Missing formula namespace: %q", id) | ||
69 | 20 | return | ||
70 | 21 | } | ||
71 | 22 | dash := strings.LastIndex(id, "-") | ||
72 | 23 | if dash != -1 { | ||
73 | 24 | rev, err = strconv.Atoi(id[dash+1:]) | ||
74 | 25 | } | ||
75 | 26 | if dash == -1 || err != nil { | ||
76 | 27 | err = errorf("Missing formula revision: %q", id) | ||
77 | 28 | return | ||
78 | 29 | } | ||
79 | 30 | namespace = id[:colon] | ||
80 | 31 | name = id[colon+1 : dash] | ||
81 | 32 | return | ||
82 | 33 | } | ||
83 | 34 | |||
84 | 35 | var ifaceSchema = schema.FieldMap(schema.Fields{ | ||
85 | 36 | "interface": schema.String(), | ||
86 | 37 | "limit": schema.OneOf(schema.Const(nil), schema.Int()), | ||
87 | 38 | "optional": schema.Bool(), | ||
88 | 39 | }, nil) | ||
89 | 40 | |||
90 | 41 | // Schema coercer that expands the interface shorthand notation. | ||
91 | 42 | // A consistent format is easier to work with than considering the | ||
92 | 43 | // potential difference everywhere. | ||
93 | 44 | // | ||
94 | 45 | // Supports the following variants:: | ||
95 | 46 | // | ||
96 | 47 | // provides: | ||
97 | 48 | // server: riak | ||
98 | 49 | // admin: http | ||
99 | 50 | // foobar: | ||
100 | 51 | // interface: blah | ||
101 | 52 | // | ||
102 | 53 | // provides: | ||
103 | 54 | // server: | ||
104 | 55 | // interface: mysql | ||
105 | 56 | // limit: | ||
106 | 57 | // optional: false | ||
107 | 58 | // | ||
108 | 59 | // In all input cases, the output is the fully specified interface | ||
109 | 60 | // representation as seen in the mysql interface description above. | ||
110 | 61 | func ifaceExpander(limit interface{}) schema.Checker { | ||
111 | 62 | return ifaceExpC{limit} | ||
112 | 63 | } | ||
113 | 64 | |||
114 | 65 | type ifaceExpC struct { | ||
115 | 66 | limit interface{} | ||
116 | 67 | } | ||
117 | 68 | |||
118 | 69 | var ( | ||
119 | 70 | stringC = schema.String() | ||
120 | 71 | mapC = schema.Map(schema.String(), schema.Any()) | ||
121 | 72 | ) | ||
122 | 73 | |||
123 | 74 | func (c ifaceExpC) Coerce(v interface{}, path []string) (newv interface{}, err os.Error) { | ||
124 | 75 | s, err := stringC.Coerce(v, path) | ||
125 | 76 | if err == nil { | ||
126 | 77 | newv = schema.M{ | ||
127 | 78 | "interface": s, | ||
128 | 79 | "limit": c.limit, | ||
129 | 80 | "optional": false, | ||
130 | 81 | } | ||
131 | 82 | return | ||
132 | 83 | } | ||
133 | 84 | |||
134 | 85 | // Optional values are context-sensitive and/or have | ||
135 | 86 | // defaults, which is different than what KeyDict can | ||
136 | 87 | // readily support. So just do it here first, then | ||
137 | 88 | // coerce to the real schema. | ||
138 | 89 | v, err = mapC.Coerce(v, path) | ||
139 | 90 | if err != nil { | ||
140 | 91 | return | ||
141 | 92 | } | ||
142 | 93 | m := v.(schema.M) | ||
143 | 94 | if _, ok := m["limit"]; !ok { | ||
144 | 95 | m["limit"] = c.limit | ||
145 | 96 | } | ||
146 | 97 | if _, ok := m["optional"]; !ok { | ||
147 | 98 | m["optional"] = false | ||
148 | 99 | } | ||
149 | 100 | return ifaceSchema.Coerce(m, path) | ||
150 | 101 | } | ||
151 | 0 | 102 | ||
152 | === added file 'formula/formula_test.go' | |||
153 | --- formula/formula_test.go 1970-01-01 00:00:00 +0000 | |||
154 | +++ formula/formula_test.go 2011-08-26 20:38:37 +0000 | |||
155 | @@ -0,0 +1,86 @@ | |||
156 | 1 | package formula_test | ||
157 | 2 | |||
158 | 3 | import ( | ||
159 | 4 | "testing" | ||
160 | 5 | . "launchpad.net/gocheck" | ||
161 | 6 | "launchpad.net/ensemble/go/formula" | ||
162 | 7 | "launchpad.net/ensemble/go/schema" | ||
163 | 8 | ) | ||
164 | 9 | |||
165 | 10 | func Test(t *testing.T) { | ||
166 | 11 | TestingT(t) | ||
167 | 12 | } | ||
168 | 13 | |||
169 | 14 | type S struct{} | ||
170 | 15 | |||
171 | 16 | var _ = Suite(&S{}) | ||
172 | 17 | |||
173 | 18 | func (s *S) TestParseId(c *C) { | ||
174 | 19 | namespace, name, rev, err := formula.ParseId("local:mysql-21") | ||
175 | 20 | c.Assert(err, IsNil) | ||
176 | 21 | c.Assert(namespace, Equals, "local") | ||
177 | 22 | c.Assert(name, Equals, "mysql") | ||
178 | 23 | c.Assert(rev, Equals, 21) | ||
179 | 24 | |||
180 | 25 | namespace, name, rev, err = formula.ParseId("local:mysql-cluster-21") | ||
181 | 26 | c.Assert(err, IsNil) | ||
182 | 27 | c.Assert(namespace, Equals, "local") | ||
183 | 28 | c.Assert(name, Equals, "mysql-cluster") | ||
184 | 29 | c.Assert(rev, Equals, 21) | ||
185 | 30 | |||
186 | 31 | _, _, _, err = formula.ParseId("foo") | ||
187 | 32 | c.Assert(err, Matches, `Missing formula namespace: "foo"`) | ||
188 | 33 | |||
189 | 34 | _, _, _, err = formula.ParseId("local:foo-x") | ||
190 | 35 | c.Assert(err, Matches, `Missing formula revision: "local:foo-x"`) | ||
191 | 36 | } | ||
192 | 37 | |||
193 | 38 | // Test rewriting of a given interface specification into long form. | ||
194 | 39 | // | ||
195 | 40 | // InterfaceExpander uses `coerce` to do one of two things: | ||
196 | 41 | // | ||
197 | 42 | // - Rewrite shorthand to the long form used for actual storage | ||
198 | 43 | // - Fills in defaults, including a configurable `limit` | ||
199 | 44 | // | ||
200 | 45 | // This test ensures test coverage on each of these branches, along | ||
201 | 46 | // with ensuring the conversion object properly raises SchemaError | ||
202 | 47 | // exceptions on invalid data. | ||
203 | 48 | func (s *S) TestIfaceExpander(c *C) { | ||
204 | 49 | e := formula.IfaceExpander(nil) | ||
205 | 50 | |||
206 | 51 | path := []string{"<pa", "th>"} | ||
207 | 52 | |||
208 | 53 | // Shorthand is properly rewritten | ||
209 | 54 | v, err := e.Coerce("http", path) | ||
210 | 55 | c.Assert(err, IsNil) | ||
211 | 56 | c.Assert(v, Equals, schema.M{"interface": "http", "limit": nil, "optional": false}) | ||
212 | 57 | |||
213 | 58 | // Defaults are properly applied | ||
214 | 59 | v, err = e.Coerce(schema.M{"interface": "http"}, path) | ||
215 | 60 | c.Assert(err, IsNil) | ||
216 | 61 | c.Assert(v, Equals, schema.M{"interface": "http", "limit": nil, "optional": false}) | ||
217 | 62 | |||
218 | 63 | v, err = e.Coerce(schema.M{"interface": "http", "limit": 2}, path) | ||
219 | 64 | c.Assert(err, IsNil) | ||
220 | 65 | c.Assert(v, Equals, schema.M{"interface": "http", "limit": int64(2), "optional": false}) | ||
221 | 66 | |||
222 | 67 | v, err = e.Coerce(schema.M{"interface": "http", "optional": true}, path) | ||
223 | 68 | c.Assert(err, IsNil) | ||
224 | 69 | c.Assert(v, Equals, schema.M{"interface": "http", "limit": nil, "optional": true}) | ||
225 | 70 | |||
226 | 71 | // Invalid data raises an error. | ||
227 | 72 | v, err = e.Coerce(42, path) | ||
228 | 73 | c.Assert(err, Matches, "<path>: expected map, got 42") | ||
229 | 74 | |||
230 | 75 | v, err = e.Coerce(schema.M{"interface": "http", "optional": nil}, path) | ||
231 | 76 | c.Assert(err, Matches, "<path>.optional: expected bool, got nothing") | ||
232 | 77 | |||
233 | 78 | v, err = e.Coerce(schema.M{"interface": "http", "limit": "none, really"}, path) | ||
234 | 79 | c.Assert(err, Matches, "<path>.limit: unsupported value") | ||
235 | 80 | |||
236 | 81 | // Can change default limit | ||
237 | 82 | e = formula.IfaceExpander(1) | ||
238 | 83 | v, err = e.Coerce(schema.M{"interface": "http"}, path) | ||
239 | 84 | c.Assert(err, IsNil) | ||
240 | 85 | c.Assert(v, Equals, schema.M{"interface": "http", "limit": int64(1), "optional": false}) | ||
241 | 86 | } | ||
242 | 0 | 87 | ||
243 | === modified file 'schema/schema.go' | |||
244 | --- schema/schema.go 2011-08-24 23:55:56 +0000 | |||
245 | +++ schema/schema.go 2011-08-26 20:38:37 +0000 | |||
246 | @@ -9,6 +9,12 @@ | |||
247 | 9 | "strings" | 9 | "strings" |
248 | 10 | ) | 10 | ) |
249 | 11 | 11 | ||
250 | 12 | // All map types used in the schema package are of type M. | ||
251 | 13 | type M map[interface{}]interface{} | ||
252 | 14 | |||
253 | 15 | // All the slice types generated in the schema package are of type L. | ||
254 | 16 | type L []interface{} | ||
255 | 17 | |||
256 | 12 | // The Coerce method of the Checker interface is called recursively when | 18 | // The Coerce method of the Checker interface is called recursively when |
257 | 13 | // v is being validated. If err is nil, newv is used as the new value | 19 | // v is being validated. If err is nil, newv is used as the new value |
258 | 14 | // at the recursion point. If err is non-nil, v is taken as invalid and | 20 | // at the recursion point. If err is non-nil, v is taken as invalid and |
259 | @@ -101,14 +107,14 @@ | |||
260 | 101 | type boolC struct{} | 107 | type boolC struct{} |
261 | 102 | 108 | ||
262 | 103 | func (c boolC) Coerce(v interface{}, path []string) (interface{}, os.Error) { | 109 | func (c boolC) Coerce(v interface{}, path []string) (interface{}, os.Error) { |
264 | 104 | if reflect.TypeOf(v).Kind() == reflect.Bool { | 110 | if v != nil && reflect.TypeOf(v).Kind() == reflect.Bool { |
265 | 105 | return v, nil | 111 | return v, nil |
266 | 106 | } | 112 | } |
267 | 107 | return nil, error{"bool", v, path} | 113 | return nil, error{"bool", v, path} |
268 | 108 | } | 114 | } |
269 | 109 | 115 | ||
270 | 110 | // Int returns a Checker that accepts any integer value, and returns | 116 | // Int returns a Checker that accepts any integer value, and returns |
272 | 111 | // the same value typed as an int64. | 117 | // the same value consistently typed as an int64. |
273 | 112 | func Int() Checker { | 118 | func Int() Checker { |
274 | 113 | return intC{} | 119 | return intC{} |
275 | 114 | } | 120 | } |
276 | @@ -116,6 +122,9 @@ | |||
277 | 116 | type intC struct{} | 122 | type intC struct{} |
278 | 117 | 123 | ||
279 | 118 | func (c intC) Coerce(v interface{}, path []string) (interface{}, os.Error) { | 124 | func (c intC) Coerce(v interface{}, path []string) (interface{}, os.Error) { |
280 | 125 | if v == nil { | ||
281 | 126 | return nil, error{"int", v, path} | ||
282 | 127 | } | ||
283 | 119 | switch reflect.TypeOf(v).Kind() { | 128 | switch reflect.TypeOf(v).Kind() { |
284 | 120 | case reflect.Int: | 129 | case reflect.Int: |
285 | 121 | case reflect.Int8: | 130 | case reflect.Int8: |
286 | @@ -129,7 +138,7 @@ | |||
287 | 129 | } | 138 | } |
288 | 130 | 139 | ||
289 | 131 | // Int returns a Checker that accepts any float value, and returns | 140 | // Int returns a Checker that accepts any float value, and returns |
291 | 132 | // the same value typed as a float64. | 141 | // the same value consistently typed as a float64. |
292 | 133 | func Float() Checker { | 142 | func Float() Checker { |
293 | 134 | return floatC{} | 143 | return floatC{} |
294 | 135 | } | 144 | } |
295 | @@ -137,6 +146,9 @@ | |||
296 | 137 | type floatC struct{} | 146 | type floatC struct{} |
297 | 138 | 147 | ||
298 | 139 | func (c floatC) Coerce(v interface{}, path []string) (interface{}, os.Error) { | 148 | func (c floatC) Coerce(v interface{}, path []string) (interface{}, os.Error) { |
299 | 149 | if v == nil { | ||
300 | 150 | return nil, error{"float", v, path} | ||
301 | 151 | } | ||
302 | 140 | switch reflect.TypeOf(v).Kind() { | 152 | switch reflect.TypeOf(v).Kind() { |
303 | 141 | case reflect.Float32: | 153 | case reflect.Float32: |
304 | 142 | case reflect.Float64: | 154 | case reflect.Float64: |
305 | @@ -156,7 +168,7 @@ | |||
306 | 156 | type stringC struct{} | 168 | type stringC struct{} |
307 | 157 | 169 | ||
308 | 158 | func (c stringC) Coerce(v interface{}, path []string) (interface{}, os.Error) { | 170 | func (c stringC) Coerce(v interface{}, path []string) (interface{}, os.Error) { |
310 | 159 | if reflect.TypeOf(v).Kind() == reflect.String { | 171 | if v != nil && reflect.TypeOf(v).Kind() == reflect.String { |
311 | 160 | return reflect.ValueOf(v).String(), nil | 172 | return reflect.ValueOf(v).String(), nil |
312 | 161 | } | 173 | } |
313 | 162 | return nil, error{"string", v, path} | 174 | return nil, error{"string", v, path} |
314 | @@ -172,7 +184,7 @@ | |||
315 | 172 | // XXX The regexp package happens to be extremely simple right now. | 184 | // XXX The regexp package happens to be extremely simple right now. |
316 | 173 | // Once exp/regexp goes mainstream, we'll have to update this | 185 | // Once exp/regexp goes mainstream, we'll have to update this |
317 | 174 | // logic to use a more widely accepted regexp subset. | 186 | // logic to use a more widely accepted regexp subset. |
319 | 175 | if reflect.TypeOf(v).Kind() == reflect.String { | 187 | if v != nil && reflect.TypeOf(v).Kind() == reflect.String { |
320 | 176 | s := reflect.ValueOf(v).String() | 188 | s := reflect.ValueOf(v).String() |
321 | 177 | _, err := regexp.Compile(s) | 189 | _, err := regexp.Compile(s) |
322 | 178 | if err != nil { | 190 | if err != nil { |
323 | @@ -183,10 +195,12 @@ | |||
324 | 183 | return nil, error{"regexp string", v, path} | 195 | return nil, error{"regexp string", v, path} |
325 | 184 | } | 196 | } |
326 | 185 | 197 | ||
328 | 186 | // String returns a Checker that accepts a slice value with values | 198 | // List returns a Checker that accepts a slice value with values |
329 | 187 | // that are processed with the elem checker. If any element of the | 199 | // that are processed with the elem checker. If any element of the |
330 | 188 | // provided slice value fails to be processed, processing will stop | 200 | // provided slice value fails to be processed, processing will stop |
331 | 189 | // and return with the obtained error. | 201 | // and return with the obtained error. |
332 | 202 | // | ||
333 | 203 | // The coerced output value has type schema.L. | ||
334 | 190 | func List(elem Checker) Checker { | 204 | func List(elem Checker) Checker { |
335 | 191 | return listC{elem} | 205 | return listC{elem} |
336 | 192 | } | 206 | } |
337 | @@ -204,7 +218,7 @@ | |||
338 | 204 | path = append(path, "[", "?", "]") | 218 | path = append(path, "[", "?", "]") |
339 | 205 | 219 | ||
340 | 206 | l := rv.Len() | 220 | l := rv.Len() |
342 | 207 | out := make([]interface{}, 0, l) | 221 | out := make(L, 0, l) |
343 | 208 | for i := 0; i != l; i++ { | 222 | for i := 0; i != l; i++ { |
344 | 209 | path[len(path)-2] = strconv.Itoa(i) | 223 | path[len(path)-2] = strconv.Itoa(i) |
345 | 210 | elem, err := c.elem.Coerce(rv.Index(i).Interface(), path) | 224 | elem, err := c.elem.Coerce(rv.Index(i).Interface(), path) |
346 | @@ -220,6 +234,8 @@ | |||
347 | 220 | // in the map are processed with the respective checker, and if any | 234 | // in the map are processed with the respective checker, and if any |
348 | 221 | // value fails to be coerced, processing stops and returns with the | 235 | // value fails to be coerced, processing stops and returns with the |
349 | 222 | // underlying error. | 236 | // underlying error. |
350 | 237 | // | ||
351 | 238 | // The coerced output value has type schema.M. | ||
352 | 223 | func Map(key Checker, value Checker) Checker { | 239 | func Map(key Checker, value Checker) Checker { |
353 | 224 | return mapC{key, value} | 240 | return mapC{key, value} |
354 | 225 | } | 241 | } |
355 | @@ -238,7 +254,7 @@ | |||
356 | 238 | vpath := append(path, ".", "?") | 254 | vpath := append(path, ".", "?") |
357 | 239 | 255 | ||
358 | 240 | l := rv.Len() | 256 | l := rv.Len() |
360 | 241 | out := make(map[interface{}]interface{}, l) | 257 | out := make(M, l) |
361 | 242 | keys := rv.MapKeys() | 258 | keys := rv.MapKeys() |
362 | 243 | for i := 0; i != l; i++ { | 259 | for i := 0; i != l; i++ { |
363 | 244 | k := keys[i] | 260 | k := keys[i] |
364 | @@ -264,6 +280,8 @@ | |||
365 | 264 | // and processing will only succeed if all the values succeed | 280 | // and processing will only succeed if all the values succeed |
366 | 265 | // individually. If a field fails to be processed, processing stops | 281 | // individually. If a field fails to be processed, processing stops |
367 | 266 | // and returns with the underlying error. | 282 | // and returns with the underlying error. |
368 | 283 | // | ||
369 | 284 | // The coerced output value has type schema.M. | ||
370 | 267 | func FieldMap(fields Fields, optional Optional) Checker { | 285 | func FieldMap(fields Fields, optional Optional) Checker { |
371 | 268 | return fieldMapC{fields, optional} | 286 | return fieldMapC{fields, optional} |
372 | 269 | } | 287 | } |
373 | @@ -291,7 +309,7 @@ | |||
374 | 291 | vpath := append(path, ".", "?") | 309 | vpath := append(path, ".", "?") |
375 | 292 | 310 | ||
376 | 293 | l := rv.Len() | 311 | l := rv.Len() |
378 | 294 | out := make(map[string]interface{}, l) | 312 | out := make(M, l) |
379 | 295 | for k, checker := range c.fields { | 313 | for k, checker := range c.fields { |
380 | 296 | vpath[len(vpath)-1] = k | 314 | vpath[len(vpath)-1] = k |
381 | 297 | var value interface{} | 315 | var value interface{} |
382 | @@ -315,6 +333,8 @@ | |||
383 | 315 | // used is the first one whose checker associated with the selector | 333 | // used is the first one whose checker associated with the selector |
384 | 316 | // field processes the map correctly. If no checker processes | 334 | // field processes the map correctly. If no checker processes |
385 | 317 | // the selector value correctly, an error is returned. | 335 | // the selector value correctly, an error is returned. |
386 | 336 | // | ||
387 | 337 | // The coerced output value has type schema.M. | ||
388 | 318 | func FieldMapSet(selector string, maps []Checker) Checker { | 338 | func FieldMapSet(selector string, maps []Checker) Checker { |
389 | 319 | fmaps := make([]fieldMapC, len(maps)) | 339 | fmaps := make([]fieldMapC, len(maps)) |
390 | 320 | for i, m := range maps { | 340 | for i, m := range maps { |
391 | 321 | 341 | ||
392 | === modified file 'schema/schema_test.go' | |||
393 | --- schema/schema_test.go 2011-08-24 23:55:56 +0000 | |||
394 | +++ schema/schema_test.go 2011-08-26 20:38:37 +0000 | |||
395 | @@ -33,6 +33,10 @@ | |||
396 | 33 | out, err = sch.Coerce(42, aPath) | 33 | out, err = sch.Coerce(42, aPath) |
397 | 34 | c.Assert(out, IsNil) | 34 | c.Assert(out, IsNil) |
398 | 35 | c.Assert(err, Matches, `<path>: expected "foo", got 42`) | 35 | c.Assert(err, Matches, `<path>: expected "foo", got 42`) |
399 | 36 | |||
400 | 37 | out, err = sch.Coerce(nil, aPath) | ||
401 | 38 | c.Assert(out, IsNil) | ||
402 | 39 | c.Assert(err, Matches, `<path>: expected "foo", got nothing`) | ||
403 | 36 | } | 40 | } |
404 | 37 | 41 | ||
405 | 38 | func (s *S) TestAny(c *C) { | 42 | func (s *S) TestAny(c *C) { |
406 | @@ -41,6 +45,10 @@ | |||
407 | 41 | out, err := sch.Coerce("foo", aPath) | 45 | out, err := sch.Coerce("foo", aPath) |
408 | 42 | c.Assert(err, IsNil) | 46 | c.Assert(err, IsNil) |
409 | 43 | c.Assert(out, Equals, "foo") | 47 | c.Assert(out, Equals, "foo") |
410 | 48 | |||
411 | 49 | out, err = sch.Coerce(nil, aPath) | ||
412 | 50 | c.Assert(err, IsNil) | ||
413 | 51 | c.Assert(out, Equals, nil) | ||
414 | 44 | } | 52 | } |
415 | 45 | 53 | ||
416 | 46 | func (s *S) TestOneOf(c *C) { | 54 | func (s *S) TestOneOf(c *C) { |
417 | @@ -73,6 +81,10 @@ | |||
418 | 73 | out, err = sch.Coerce(1, aPath) | 81 | out, err = sch.Coerce(1, aPath) |
419 | 74 | c.Assert(out, IsNil) | 82 | c.Assert(out, IsNil) |
420 | 75 | c.Assert(err, Matches, "<path>: expected bool, got 1") | 83 | c.Assert(err, Matches, "<path>: expected bool, got 1") |
421 | 84 | |||
422 | 85 | out, err = sch.Coerce(nil, aPath) | ||
423 | 86 | c.Assert(out, IsNil) | ||
424 | 87 | c.Assert(err, Matches, "<path>: expected bool, got nothing") | ||
425 | 76 | } | 88 | } |
426 | 77 | 89 | ||
427 | 78 | func (s *S) TestInt(c *C) { | 90 | func (s *S) TestInt(c *C) { |
428 | @@ -89,6 +101,10 @@ | |||
429 | 89 | out, err = sch.Coerce(true, aPath) | 101 | out, err = sch.Coerce(true, aPath) |
430 | 90 | c.Assert(out, IsNil) | 102 | c.Assert(out, IsNil) |
431 | 91 | c.Assert(err, Matches, "<path>: expected int, got true") | 103 | c.Assert(err, Matches, "<path>: expected int, got true") |
432 | 104 | |||
433 | 105 | out, err = sch.Coerce(nil, aPath) | ||
434 | 106 | c.Assert(out, IsNil) | ||
435 | 107 | c.Assert(err, Matches, "<path>: expected int, got nothing") | ||
436 | 92 | } | 108 | } |
437 | 93 | 109 | ||
438 | 94 | func (s *S) TestFloat(c *C) { | 110 | func (s *S) TestFloat(c *C) { |
439 | @@ -105,6 +121,10 @@ | |||
440 | 105 | out, err = sch.Coerce(true, aPath) | 121 | out, err = sch.Coerce(true, aPath) |
441 | 106 | c.Assert(out, IsNil) | 122 | c.Assert(out, IsNil) |
442 | 107 | c.Assert(err, Matches, "<path>: expected float, got true") | 123 | c.Assert(err, Matches, "<path>: expected float, got true") |
443 | 124 | |||
444 | 125 | out, err = sch.Coerce(nil, aPath) | ||
445 | 126 | c.Assert(out, IsNil) | ||
446 | 127 | c.Assert(err, Matches, "<path>: expected float, got nothing") | ||
447 | 108 | } | 128 | } |
448 | 109 | 129 | ||
449 | 110 | func (s *S) TestString(c *C) { | 130 | func (s *S) TestString(c *C) { |
450 | @@ -117,6 +137,10 @@ | |||
451 | 117 | out, err = sch.Coerce(true, aPath) | 137 | out, err = sch.Coerce(true, aPath) |
452 | 118 | c.Assert(out, IsNil) | 138 | c.Assert(out, IsNil) |
453 | 119 | c.Assert(err, Matches, "<path>: expected string, got true") | 139 | c.Assert(err, Matches, "<path>: expected string, got true") |
454 | 140 | |||
455 | 141 | out, err = sch.Coerce(nil, aPath) | ||
456 | 142 | c.Assert(out, IsNil) | ||
457 | 143 | c.Assert(err, Matches, "<path>: expected string, got nothing") | ||
458 | 120 | } | 144 | } |
459 | 121 | 145 | ||
460 | 122 | func (s *S) TestSimpleRegexp(c *C) { | 146 | func (s *S) TestSimpleRegexp(c *C) { |
461 | @@ -132,18 +156,26 @@ | |||
462 | 132 | out, err = sch.Coerce("[", aPath) | 156 | out, err = sch.Coerce("[", aPath) |
463 | 133 | c.Assert(out, IsNil) | 157 | c.Assert(out, IsNil) |
464 | 134 | c.Assert(err, Matches, `<path>: expected valid regexp, got "\["`) | 158 | c.Assert(err, Matches, `<path>: expected valid regexp, got "\["`) |
465 | 159 | |||
466 | 160 | out, err = sch.Coerce(nil, aPath) | ||
467 | 161 | c.Assert(out, IsNil) | ||
468 | 162 | c.Assert(err, Matches, `<path>: expected regexp string, got nothing`) | ||
469 | 135 | } | 163 | } |
470 | 136 | 164 | ||
471 | 137 | func (s *S) TestList(c *C) { | 165 | func (s *S) TestList(c *C) { |
472 | 138 | sch := schema.List(schema.Int()) | 166 | sch := schema.List(schema.Int()) |
473 | 139 | out, err := sch.Coerce([]int8{1, 2}, aPath) | 167 | out, err := sch.Coerce([]int8{1, 2}, aPath) |
474 | 140 | c.Assert(err, IsNil) | 168 | c.Assert(err, IsNil) |
476 | 141 | c.Assert(out, Equals, []interface{}{int64(1), int64(2)}) | 169 | c.Assert(out, Equals, schema.L{int64(1), int64(2)}) |
477 | 142 | 170 | ||
478 | 143 | out, err = sch.Coerce(42, aPath) | 171 | out, err = sch.Coerce(42, aPath) |
479 | 144 | c.Assert(out, IsNil) | 172 | c.Assert(out, IsNil) |
480 | 145 | c.Assert(err, Matches, "<path>: expected list, got 42") | 173 | c.Assert(err, Matches, "<path>: expected list, got 42") |
481 | 146 | 174 | ||
482 | 175 | out, err = sch.Coerce(nil, aPath) | ||
483 | 176 | c.Assert(out, IsNil) | ||
484 | 177 | c.Assert(err, Matches, "<path>: expected list, got nothing") | ||
485 | 178 | |||
486 | 147 | out, err = sch.Coerce([]interface{}{1, true}, aPath) | 179 | out, err = sch.Coerce([]interface{}{1, true}, aPath) |
487 | 148 | c.Assert(out, IsNil) | 180 | c.Assert(out, IsNil) |
488 | 149 | c.Assert(err, Matches, `<path>\[1\]: expected int, got true`) | 181 | c.Assert(err, Matches, `<path>\[1\]: expected int, got true`) |
489 | @@ -153,12 +185,16 @@ | |||
490 | 153 | sch := schema.Map(schema.String(), schema.Int()) | 185 | sch := schema.Map(schema.String(), schema.Int()) |
491 | 154 | out, err := sch.Coerce(map[string]interface{}{"a": 1, "b": int8(2)}, aPath) | 186 | out, err := sch.Coerce(map[string]interface{}{"a": 1, "b": int8(2)}, aPath) |
492 | 155 | c.Assert(err, IsNil) | 187 | c.Assert(err, IsNil) |
494 | 156 | c.Assert(out, Equals, map[interface{}]interface{}{"a": int64(1), "b": int64(2)}) | 188 | c.Assert(out, Equals, schema.M{"a": int64(1), "b": int64(2)}) |
495 | 157 | 189 | ||
496 | 158 | out, err = sch.Coerce(42, aPath) | 190 | out, err = sch.Coerce(42, aPath) |
497 | 159 | c.Assert(out, IsNil) | 191 | c.Assert(out, IsNil) |
498 | 160 | c.Assert(err, Matches, "<path>: expected map, got 42") | 192 | c.Assert(err, Matches, "<path>: expected map, got 42") |
499 | 161 | 193 | ||
500 | 194 | out, err = sch.Coerce(nil, aPath) | ||
501 | 195 | c.Assert(out, IsNil) | ||
502 | 196 | c.Assert(err, Matches, "<path>: expected map, got nothing") | ||
503 | 197 | |||
504 | 162 | out, err = sch.Coerce(map[int]int{1: 1}, aPath) | 198 | out, err = sch.Coerce(map[int]int{1: 1}, aPath) |
505 | 163 | c.Assert(out, IsNil) | 199 | c.Assert(out, IsNil) |
506 | 164 | c.Assert(err, Matches, "<path>: expected string, got 1") | 200 | c.Assert(err, Matches, "<path>: expected string, got 1") |
507 | @@ -182,12 +218,16 @@ | |||
508 | 182 | 218 | ||
509 | 183 | out, err := sch.Coerce(map[string]interface{}{"a": "A", "b": "B"}, aPath) | 219 | out, err := sch.Coerce(map[string]interface{}{"a": "A", "b": "B"}, aPath) |
510 | 184 | c.Assert(err, IsNil) | 220 | c.Assert(err, IsNil) |
512 | 185 | c.Assert(out, Equals, map[string]interface{}{"a": "A", "b": "B"}) | 221 | c.Assert(out, Equals, schema.M{"a": "A", "b": "B"}) |
513 | 186 | 222 | ||
514 | 187 | out, err = sch.Coerce(42, aPath) | 223 | out, err = sch.Coerce(42, aPath) |
515 | 188 | c.Assert(out, IsNil) | 224 | c.Assert(out, IsNil) |
516 | 189 | c.Assert(err, Matches, "<path>: expected map, got 42") | 225 | c.Assert(err, Matches, "<path>: expected map, got 42") |
517 | 190 | 226 | ||
518 | 227 | out, err = sch.Coerce(nil, aPath) | ||
519 | 228 | c.Assert(out, IsNil) | ||
520 | 229 | c.Assert(err, Matches, "<path>: expected map, got nothing") | ||
521 | 230 | |||
522 | 191 | out, err = sch.Coerce(map[string]interface{}{"a": "A", "b": "C"}, aPath) | 231 | out, err = sch.Coerce(map[string]interface{}{"a": "A", "b": "C"}, aPath) |
523 | 192 | c.Assert(out, IsNil) | 232 | c.Assert(out, IsNil) |
524 | 193 | c.Assert(err, Matches, `<path>\.b: expected "B", got "C"`) | 233 | c.Assert(err, Matches, `<path>\.b: expected "B", got "C"`) |
525 | @@ -199,7 +239,7 @@ | |||
526 | 199 | // b is optional | 239 | // b is optional |
527 | 200 | out, err = sch.Coerce(map[string]interface{}{"a": "A"}, aPath) | 240 | out, err = sch.Coerce(map[string]interface{}{"a": "A"}, aPath) |
528 | 201 | c.Assert(err, IsNil) | 241 | c.Assert(err, IsNil) |
530 | 202 | c.Assert(out, Equals, map[string]interface{}{"a": "A"}) | 242 | c.Assert(out, Equals, schema.M{"a": "A"}) |
531 | 203 | 243 | ||
532 | 204 | // First path entry shouldn't have dots in an error message. | 244 | // First path entry shouldn't have dots in an error message. |
533 | 205 | out, err = sch.Coerce(map[string]bool{"a": true}, nil) | 245 | out, err = sch.Coerce(map[string]bool{"a": true}, nil) |
534 | @@ -220,11 +260,11 @@ | |||
535 | 220 | 260 | ||
536 | 221 | out, err := sch.Coerce(map[string]int{"type": 1, "a": 2}, aPath) | 261 | out, err := sch.Coerce(map[string]int{"type": 1, "a": 2}, aPath) |
537 | 222 | c.Assert(err, IsNil) | 262 | c.Assert(err, IsNil) |
539 | 223 | c.Assert(out, Equals, map[string]interface{}{"type": 1, "a": 2}) | 263 | c.Assert(out, Equals, schema.M{"type": 1, "a": 2}) |
540 | 224 | 264 | ||
541 | 225 | out, err = sch.Coerce(map[string]int{"type": 3, "b": 4}, aPath) | 265 | out, err = sch.Coerce(map[string]int{"type": 3, "b": 4}, aPath) |
542 | 226 | c.Assert(err, IsNil) | 266 | c.Assert(err, IsNil) |
544 | 227 | c.Assert(out, Equals, map[string]interface{}{"type": 3, "b": 4}) | 267 | c.Assert(out, Equals, schema.M{"type": 3, "b": 4}) |
545 | 228 | 268 | ||
546 | 229 | out, err = sch.Coerce(map[string]int{}, aPath) | 269 | out, err = sch.Coerce(map[string]int{}, aPath) |
547 | 230 | c.Assert(out, IsNil) | 270 | c.Assert(out, IsNil) |
548 | @@ -238,6 +278,14 @@ | |||
549 | 238 | c.Assert(out, IsNil) | 278 | c.Assert(out, IsNil) |
550 | 239 | c.Assert(err, Matches, `<path>\.b: expected 4, got 5`) | 279 | c.Assert(err, Matches, `<path>\.b: expected 4, got 5`) |
551 | 240 | 280 | ||
552 | 281 | out, err = sch.Coerce(42, aPath) | ||
553 | 282 | c.Assert(out, IsNil) | ||
554 | 283 | c.Assert(err, Matches, `<path>: expected map, got 42`) | ||
555 | 284 | |||
556 | 285 | out, err = sch.Coerce(nil, aPath) | ||
557 | 286 | c.Assert(out, IsNil) | ||
558 | 287 | c.Assert(err, Matches, `<path>: expected map, got nothing`) | ||
559 | 288 | |||
560 | 241 | // First path entry shouldn't have dots in an error message. | 289 | // First path entry shouldn't have dots in an error message. |
561 | 242 | out, err = sch.Coerce(map[string]int{"a": 1}, nil) | 290 | out, err = sch.Coerce(map[string]int{"a": 1}, nil) |
562 | 243 | c.Assert(out, IsNil) | 291 | c.Assert(out, IsNil) |
Also LGTM