Merge lp:~niemeyer/pyjuju/go-schema into lp:pyjuju/go

Proposed by Gustavo Niemeyer
Status: Merged
Approved by: Gustavo Niemeyer
Approved revision: 2
Merged at revision: 2
Proposed branch: lp:~niemeyer/pyjuju/go-schema
Merge into: lp:pyjuju/go
Diff against target: 617 lines (+587/-7)
2 files modified
schema/schema.go (+355/-1)
schema/schema_test.go (+232/-6)
To merge this branch: bzr merge lp:~niemeyer/pyjuju/go-schema
Reviewer Review Type Date Requested Status
Jim Baker (community) Approve
William Reade (community) Approve
Juju Engineering Pending
Review via email: mp+72811@code.launchpad.net

Description of the change

Ported schema verification and coercion logic from Python. (!)

To post a comment you must log in.
Revision history for this message
William Reade (fwereade) wrote :

It appears to do what it intends, but I do have a few questions...

[0]

+ path = append(path, "[", "?", "]")
...
+ for i := 0; i != l; i++ {
+ path[len(path)-2] = strconv.Itoa(i)
+ elem, err := c.elem.Coerce(rv.Index(i).Interface(), path)

I wouldn't say this is *unclear*, but I did have to stop and think for a moment. I don't see any code path in which the "?" will actually be used, and I would favour a construction more like:

+ for i := 0; i != l; i++ {
+ itemPath = append(path, "[", strconv.Itoa(i). "]")
+ elem, err := c.elem.Coerce(rv.Index(i).Interface(), itemPath)

Are there benefits to the original approach that I've missed?

[1]

+ vpath := append(path, ".", "?")

Same issue as [0].

[2]

Throughout: c, e, o, v, rv, l, vpath, newk, newv... while, again, these aren't actually unclear in context, they don't really reveal their intention very well (if at all). Are there forces that work against the use of longer variable names?

[3]

Finally, I'm concerned about the potential for drift -- both in Schema itself, and in individual schemas.

A language-agnostic schema format (where we define any given schema OAOO, and load it in both Go and Python) would solve the second problem. The first one is slightly trickier, but we could probably do it by encoding tests as sets of (schema-path, input, expected-error-or-serialized-output); we could then have one SchemaTest *loader* per language, which would replace the existing Schema unit tests -- and hopefully not need to change very much, if at all -- and should be sufficient to prevent drift between the implementations (a new Schema test is just a few files added to an existing directory; if a change is made in Python but not in Go, the next run of the Go tests will expose the issue). We'd still need tests for individual schemas, of course, but they could be done in the same format and share the benefits.

In practice, it may be that any single-language change would cause clear errors, quickly and reliably enough that we wouldn't get value from the extra scaffolding, but I'd need to be convinced of that ;).

review: Needs Information
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

Thanks for the review William. Some feedback:

[0][1]

Yes, in your suggested model we have to shrink and expand the list on every iteration of the loop, while this is a simple assignment. The Python version works the same way.

[2]

Our strict long variable name policy has lead us to a position that is not entirely nice. I'm still very much for readability, and that's precisely the point in fact. Long names in tight contexts are doing more harm than good in our code base, leading to very long lines that often wrap and easily become difficult to follow.

I still think we should be careful about names, even more when these names have a wider scope, but I'd like to propose we take a different stanza in Go, and attempt to follow the practices and style used by the Go development team itself (in a similar way we use PEP8 in Python). Go also has the advantage of typing and static compilation, so it's easier to infer what something is, and harder to make mistakes.

Just for context, here is an example of long names getting in the way of readability:

    @inlineCallbacks
    def test_machine_cannot_be_removed_if_assigned(self):
        """Verify that a machine cannot be removed before being unassigned"""
        machine_state = yield self.machine_state_manager.add_machine_state()
        service_state = yield self.service_state_manager.add_service_state(
            "wordpress", self.formula_state)
        unit_state = yield service_state.add_unit_state()
        yield unit_state.assign_to_machine(machine_state)
        ex = yield self.assertFailure(
            self.machine_state_manager.remove_machine_state(machine_state.id),
            MachineStateInUse)
        self.assertEqual(ex.machine_id, 0)
        yield unit_state.unassign_from_machine()
        topology = yield self.get_topology()
        self.assertTrue(topology.has_machine("machine-0000000000"))
        removed = yield self.machine_state_manager.remove_machine_state(
            machine_state.id)
        self.assertTrue(removed)
        topology = yield self.get_topology()
        self.assertFalse(topology.has_machine("machine-0000000000"))
        # can do this multiple times
        removed = yield self.machine_state_manager.remove_machine_state(
            machine_state.id)
        self.assertFalse(removed)

[3]

I agree with you on that. I just can't work on this right now, but let's try to go into that direction.

Revision history for this message
William Reade (fwereade) wrote :

I'm not entirely convinced by [2] but I'm willing to participate in the experiment. I've filed lp:833906 for [3], and I'm happy to approve.

review: Approve
Revision history for this message
Jim Baker (jimbaker) wrote :

+1 It's a nice Rosetta Stone for comparing Python and Go, including how the concept, its implementation, and tests should be idiomatically done in Go. Very nice indeed!

Revision history for this message
Jim Baker (jimbaker) wrote :

The above should have been a "approved review"

review: Approve

Preview Diff

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

Subscribers

People subscribed via source and target branches