Merge lp:~niemeyer/pyjuju/go-iface-schema into lp:pyjuju/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
Reviewer Review Type Date Requested Status
Kapil Thangavelu (community) Approve
William Reade (community) Approve
Review via email: mp+73099@code.launchpad.net
To post a comment you must log in.
Revision history for this message
William Reade (fwereade) wrote :

Also LGTM

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

Subscribers

People subscribed via source and target branches