Merge lp:~niemeyer/juju-core/schema-field-map-defaults into lp:~juju/juju-core/trunk

Proposed by Gustavo Niemeyer
Status: Merged
Merged at revision: 318
Proposed branch: lp:~niemeyer/juju-core/schema-field-map-defaults
Merge into: lp:~juju/juju-core/trunk
Prerequisite: lp:~niemeyer/juju-core/schema-string-map
Diff against target: 382 lines (+113/-75)
7 files modified
charm/config.go (+4/-1)
charm/meta.go (+11/-12)
environs/config/config.go (+12/-12)
environs/dummy/environs.go (+3/-3)
environs/ec2/config.go (+12/-19)
schema/schema.go (+39/-18)
schema/schema_test.go (+32/-10)
To merge this branch: bzr merge lp:~niemeyer/juju-core/schema-field-map-defaults
Reviewer Review Type Date Requested Status
The Go Language Gophers Pending
Review via email: mp+115863@code.launchpad.net

Description of the change

schema: support defaults in FieldMap

https://codereview.appspot.com/6419056/

To post a comment you must log in.
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

Reviewers: mp+115863_code.launchpad.net,

Message:
Please take a look.

Description:
schema: support defaults in FieldMap

https://code.launchpad.net/~niemeyer/juju-core/schema-field-map-defaults/+merge/115863

Requires:
https://code.launchpad.net/~niemeyer/juju-core/schema-string-map/+merge/115849

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/6419056/

Affected files:
   A [revision details]
   M charm/config.go
   M charm/meta.go
   M environs/config/config.go
   M environs/dummy/environs.go
   M environs/ec2/config.go
   M schema/schema.go
   M schema/schema_test.go

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :
Revision history for this message
Roger Peppe (rogpeppe) wrote :

On 2012/07/20 00:32:02, niemeyer wrote:
> Please take a look.

excellent stuff; make schema work for its living!

LGTM

https://codereview.appspot.com/6419056/

Revision history for this message
Roger Peppe (rogpeppe) wrote :
Revision history for this message
William Reade (fwereade) wrote :
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

*** Submitted:

schema: support defaults in FieldMap

R=rog, fwereade
CC=
https://codereview.appspot.com/6419056

https://codereview.appspot.com/6419056/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'charm/config.go'
--- charm/config.go 2012-07-19 22:31:15 +0000
+++ charm/config.go 2012-07-20 00:33:23 +0000
@@ -130,7 +130,10 @@
130 "default": schema.OneOf(schema.String(), schema.Int(), schema.Float(), schema.Bool()),130 "default": schema.OneOf(schema.String(), schema.Int(), schema.Float(), schema.Bool()),
131 "description": schema.String(),131 "description": schema.String(),
132 },132 },
133 schema.Optional{"default", "description"},133 schema.Defaults{
134 "default": schema.Omit,
135 "description": schema.Omit,
136 },
134)137)
135138
136var configSchema = schema.FieldMap(139var configSchema = schema.FieldMap(
137140
=== modified file 'charm/meta.go'
--- charm/meta.go 2012-07-19 22:31:15 +0000
+++ charm/meta.go 2012-07-20 00:33:23 +0000
@@ -155,10 +155,6 @@
155 return155 return
156 }156 }
157157
158 // Optional values are context-sensitive and/or have
159 // defaults, which is different than what FieldMap can
160 // readily support. So just do it here first, then
161 // coerce to the real schema.
162 v, err = mapC.Coerce(v, path)158 v, err = mapC.Coerce(v, path)
163 if err != nil {159 if err != nil {
164 return160 return
@@ -167,12 +163,6 @@
167 if _, ok := m["limit"]; !ok {163 if _, ok := m["limit"]; !ok {
168 m["limit"] = c.limit164 m["limit"] = c.limit
169 }165 }
170 if _, ok := m["optional"]; !ok {
171 m["optional"] = false
172 }
173 if _, ok := m["scope"]; !ok {
174 m["scope"] = ScopeGlobal
175 }
176 return ifaceSchema.Coerce(m, path)166 return ifaceSchema.Coerce(m, path)
177}167}
178168
@@ -183,7 +173,10 @@
183 "scope": schema.OneOf(schema.Const(ScopeGlobal), schema.Const(ScopeContainer)),173 "scope": schema.OneOf(schema.Const(ScopeGlobal), schema.Const(ScopeContainer)),
184 "optional": schema.Bool(),174 "optional": schema.Bool(),
185 },175 },
186 schema.Optional{"scope"},176 schema.Defaults{
177 "scope": ScopeGlobal,
178 "optional": false,
179 },
187)180)
188181
189var charmSchema = schema.FieldMap(182var charmSchema = schema.FieldMap(
@@ -197,5 +190,11 @@
197 "revision": schema.Int(), // Obsolete190 "revision": schema.Int(), // Obsolete
198 "subordinate": schema.Bool(),191 "subordinate": schema.Bool(),
199 },192 },
200 schema.Optional{"provides", "requires", "peers", "revision", "subordinate"},193 schema.Defaults{
194 "provides": schema.Omit,
195 "requires": schema.Omit,
196 "peers": schema.Omit,
197 "revision": schema.Omit,
198 "subordinate": schema.Omit,
199 },
201)200)
202201
=== modified file 'environs/config/config.go'
--- environs/config/config.go 2012-07-19 22:31:15 +0000
+++ environs/config/config.go 2012-07-20 00:33:23 +0000
@@ -23,20 +23,21 @@
23 m: m.(map[string]interface{}),23 m: m.(map[string]interface{}),
24 t: make(map[string]interface{}),24 t: make(map[string]interface{}),
25 }25 }
26 if s, _ := c.m["default-series"].(string); s == "" {26
27 if c.m["default-series"].(string) == "" {
27 c.m["default-series"] = CurrentSeries28 c.m["default-series"] = CurrentSeries
28 }29 }
2930
30 // Load authorized-keys-path onto authorized-keys, if necessary.31 // Load authorized-keys-path onto authorized-keys, if necessary.
31 path, _ := c.m["authorized-keys-path"].(string)32 path := c.m["authorized-keys-path"].(string)
32 keys, _ := c.m["authorized-keys"].(string)33 keys := c.m["authorized-keys"].(string)
33 if path != "" || keys == "" {34 if path != "" || keys == "" {
34 c.m["authorized-keys"], err = authorizedKeys(path)35 c.m["authorized-keys"], err = authorizedKeys(path)
35 if err != nil {36 if err != nil {
36 return nil, err37 return nil, err
37 }38 }
38 delete(c.m, "authorized-keys-path")
39 }39 }
40 delete(c.m, "authorized-keys-path")
4041
41 // Check if there are any required fields that are empty.42 // Check if there are any required fields that are empty.
42 for _, attr := range []string{"name", "type", "default-series", "authorized-keys"} {43 for _, attr := range []string{"name", "type", "default-series", "authorized-keys"} {
@@ -103,11 +104,10 @@
103 "authorized-keys-path": schema.String(),104 "authorized-keys-path": schema.String(),
104}105}
105106
106var checker = schema.FieldMap(107var defaults = schema.Defaults{
107 fields,108 "default-series": CurrentSeries,
108 []string{109 "authorized-keys": "",
109 "default-series",110 "authorized-keys-path": "",
110 "authorized-keys",111}
111 "authorized-keys-path",112
112 },113var checker = schema.FieldMap(fields, defaults)
113)
114114
=== modified file 'environs/dummy/environs.go'
--- environs/dummy/environs.go 2012-07-19 22:31:15 +0000
+++ environs/dummy/environs.go 2012-07-20 00:33:23 +0000
@@ -218,8 +218,8 @@
218 "zookeeper": schema.Bool(),218 "zookeeper": schema.Bool(),
219 "broken": schema.Bool(),219 "broken": schema.Bool(),
220 },220 },
221 []string{221 schema.Defaults{
222 "broken",222 "broken": false,
223 },223 },
224)224)
225225
@@ -233,7 +233,7 @@
233 Config: cfg,233 Config: cfg,
234 zookeeper: m1["zookeeper"].(bool),234 zookeeper: m1["zookeeper"].(bool),
235 }235 }
236 ecfg.broken, _ = m1["broken"].(bool)236 ecfg.broken = m1["broken"].(bool)
237 return ecfg, nil237 return ecfg, nil
238}238}
239239
240240
=== modified file 'environs/ec2/config.go'
--- environs/ec2/config.go 2012-07-19 22:31:15 +0000
+++ environs/ec2/config.go 2012-07-20 00:33:23 +0000
@@ -25,11 +25,12 @@
25 "region": schema.String(),25 "region": schema.String(),
26 "control-bucket": schema.String(),26 "control-bucket": schema.String(),
27 "public-bucket": schema.String(),27 "public-bucket": schema.String(),
28 }, []string{28 },
29 "access-key",29 schema.Defaults{
30 "secret-key",30 "access-key": "",
31 "region",31 "secret-key": "",
32 "public-bucket",32 "region": "us-east-1",
33 "public-bucket": "",
33 },34 },
34)35)
3536
@@ -41,9 +42,9 @@
41 m := v.(map[string]interface{})42 m := v.(map[string]interface{})
42 c := &providerConfig{Config: config}43 c := &providerConfig{Config: config}
43 c.bucket = m["control-bucket"].(string)44 c.bucket = m["control-bucket"].(string)
44 c.publicBucket = maybeString(m["public-bucket"], "")45 c.publicBucket = m["public-bucket"].(string)
45 c.auth.AccessKey = maybeString(m["access-key"], "")46 c.auth.AccessKey = m["access-key"].(string)
46 c.auth.SecretKey = maybeString(m["secret-key"], "")47 c.auth.SecretKey = m["secret-key"].(string)
47 if c.auth.AccessKey == "" || c.auth.SecretKey == "" {48 if c.auth.AccessKey == "" || c.auth.SecretKey == "" {
48 if c.auth.AccessKey != "" {49 if c.auth.AccessKey != "" {
49 return nil, fmt.Errorf("environment has access-key but no secret-key")50 return nil, fmt.Errorf("environment has access-key but no secret-key")
@@ -57,17 +58,9 @@
57 }58 }
58 }59 }
5960
60 regionName := maybeString(m["region"], "us-east-1")61 c.region = m["region"].(string)
61 if _, ok := aws.Regions[regionName]; !ok {62 if _, ok := aws.Regions[c.region]; !ok {
62 return nil, fmt.Errorf("invalid region name %q", regionName)63 return nil, fmt.Errorf("invalid region name %q", c.region)
63 }64 }
64 c.region = regionName
65 return c, nil65 return c, nil
66}66}
67
68func maybeString(x interface{}, dflt string) string {
69 if x == nil {
70 return dflt
71 }
72 return x.(string)
73}
7467
=== modified file 'schema/schema.go'
--- schema/schema.go 2012-07-19 22:31:15 +0000
+++ schema/schema.go 2012-07-20 00:33:23 +0000
@@ -305,8 +305,14 @@
305 return out, nil305 return out, nil
306}306}
307307
308// Omit is a marker for FieldMap and StructFieldMap defaults parameter.
309// If a field is not present in the map and defaults to Omit, the missing
310// field will be ommitted from the coerced map as well.
311var Omit omit
312type omit struct{}
313
308type Fields map[string]Checker314type Fields map[string]Checker
309type Optional []string315type Defaults map[string]interface{}
310316
311// FieldMap returns a Checker that accepts a map value with defined317// FieldMap returns a Checker that accepts a map value with defined
312// string keys. Every key has an independent checker associated,318// string keys. Every key has an independent checker associated,
@@ -314,32 +320,27 @@
314// individually. If a field fails to be processed, processing stops320// individually. If a field fails to be processed, processing stops
315// and returns with the underlying error.321// and returns with the underlying error.
316//322//
323// Fields in defaults will be set to the provided value if not present
324// in the coerced map. If the default value is schema.Omit, the field
325// missing field will be ommitted from the coerced map as well.
326//
317// The coerced output value has type map[string]interface{}.327// The coerced output value has type map[string]interface{}.
318func FieldMap(fields Fields, optional Optional) Checker {328func FieldMap(fields Fields, defaults Defaults) Checker {
319 return fieldMapC{fields, optional, false}329 return fieldMapC{fields, defaults, false}
320}330}
321331
322// StrictFieldMap returns a Checker that acts as the one returned by FieldMap,332// StrictFieldMap returns a Checker that acts as the one returned by FieldMap,
323// but the Checker returns an error if it encounters an unknown key.333// but the Checker returns an error if it encounters an unknown key.
324func StrictFieldMap(fields Fields, optional Optional) Checker {334func StrictFieldMap(fields Fields, defaults Defaults) Checker {
325 return fieldMapC{fields, optional, true}335 return fieldMapC{fields, defaults, true}
326}336}
327337
328type fieldMapC struct {338type fieldMapC struct {
329 fields Fields339 fields Fields
330 optional []string340 defaults Defaults
331 strict bool341 strict bool
332}342}
333343
334func (c fieldMapC) isOptional(key string) bool {
335 for _, k := range c.optional {
336 if k == key {
337 return true
338 }
339 }
340 return false
341}
342
343func (c fieldMapC) Coerce(v interface{}, path []string) (interface{}, error) {344func (c fieldMapC) Coerce(v interface{}, path []string) (interface{}, error) {
344 rv := reflect.ValueOf(v)345 rv := reflect.ValueOf(v)
345 if rv.Kind() != reflect.Map {346 if rv.Kind() != reflect.Map {
@@ -362,20 +363,40 @@
362 l := rv.Len()363 l := rv.Len()
363 out := make(map[string]interface{}, l)364 out := make(map[string]interface{}, l)
364 for k, checker := range c.fields {365 for k, checker := range c.fields {
365 vpath[len(vpath)-1] = k
366 var value interface{}366 var value interface{}
367 valuev := rv.MapIndex(reflect.ValueOf(k))367 valuev := rv.MapIndex(reflect.ValueOf(k))
368 if valuev.IsValid() {368 if valuev.IsValid() {
369 value = valuev.Interface()369 value = valuev.Interface()
370 } else if c.isOptional(k) {370 } else if dflt, ok := c.defaults[k]; ok {
371 continue371 if dflt == Omit {
372 continue
373 }
374 value = dflt
372 }375 }
376 vpath[len(vpath)-1] = k
373 newv, err := checker.Coerce(value, vpath)377 newv, err := checker.Coerce(value, vpath)
374 if err != nil {378 if err != nil {
375 return nil, err379 return nil, err
376 }380 }
377 out[k] = newv381 out[k] = newv
378 }382 }
383 for k, v := range c.defaults {
384 if v == Omit {
385 continue
386 }
387 if _, ok := out[k]; !ok {
388 checker, ok := c.fields[k]
389 if !ok {
390 return nil, fmt.Errorf("got default value for unknown field %q", k)
391 }
392 vpath[len(vpath)-1] = k
393 newv, err := checker.Coerce(v, vpath)
394 if err != nil {
395 return nil, err
396 }
397 out[k] = newv
398 }
399 }
379 return out, nil400 return out, nil
380}401}
381402
382403
=== modified file 'schema/schema_test.go'
--- schema/schema_test.go 2012-07-19 22:31:15 +0000
+++ schema/schema_test.go 2012-07-20 00:33:23 +0000
@@ -240,7 +240,7 @@
240 out, err := sch.Coerce(map[string]interface{}{"a": "A", "b": "B"}, aPath)240 out, err := sch.Coerce(map[string]interface{}{"a": "A", "b": "B"}, aPath)
241241
242 c.Assert(err, IsNil)242 c.Assert(err, IsNil)
243 c.Assert(out, DeepEquals, map[string]interface{}{"a": "A", "b": "B"})243 c.Assert(out, DeepEquals, map[string]interface{}{"a": "A", "b": "B", "c": "C"})
244244
245 out, err = sch.Coerce(42, aPath)245 out, err = sch.Coerce(42, aPath)
246 c.Assert(out, IsNil)246 c.Assert(out, IsNil)
@@ -261,7 +261,7 @@
261 // b is optional261 // b is optional
262 out, err = sch.Coerce(map[string]interface{}{"a": "A"}, aPath)262 out, err = sch.Coerce(map[string]interface{}{"a": "A"}, aPath)
263 c.Assert(err, IsNil)263 c.Assert(err, IsNil)
264 c.Assert(out, DeepEquals, map[string]interface{}{"a": "A"})264 c.Assert(out, DeepEquals, map[string]interface{}{"a": "A", "c": "C"})
265265
266 // First path entry shouldn't have dots in an error message.266 // First path entry shouldn't have dots in an error message.
267 out, err = sch.Coerce(map[string]bool{"a": true}, nil)267 out, err = sch.Coerce(map[string]bool{"a": true}, nil)
@@ -273,26 +273,48 @@
273 fields := schema.Fields{273 fields := schema.Fields{
274 "a": schema.Const("A"),274 "a": schema.Const("A"),
275 "b": schema.Const("B"),275 "b": schema.Const("B"),
276 }276 "c": schema.Const("C"),
277 sch := schema.FieldMap(fields, schema.Optional{"b"})277 }
278 defaults := schema.Defaults{
279 "b": schema.Omit,
280 "c": "C",
281 }
282 sch := schema.FieldMap(fields, defaults)
278 assertFieldMap(c, sch)283 assertFieldMap(c, sch)
279284
280 out, err := sch.Coerce(map[string]interface{}{"a": "A", "b": "B", "c": "C"}, aPath)285 out, err := sch.Coerce(map[string]interface{}{"a": "A", "b": "B", "d": "D"}, aPath)
281 c.Assert(err, IsNil)286 c.Assert(err, IsNil)
282 c.Assert(out, DeepEquals, map[string]interface{}{"a": "A", "b": "B"})287 c.Assert(out, DeepEquals, map[string]interface{}{"a": "A", "b": "B", "c": "C"})
288}
289
290func (s *S) TestFieldMapDefaultInvalid(c *C) {
291 fields := schema.Fields{
292 "a": schema.Const("A"),
293 }
294 defaults := schema.Defaults{
295 "a": "B",
296 }
297 sch := schema.FieldMap(fields, defaults)
298 _, err := sch.Coerce(map[string]interface{}{}, aPath)
299 c.Assert(err, ErrorMatches, `<path>.a: expected "A", got "B"`)
283}300}
284301
285func (s *S) TestStrictFieldMap(c *C) {302func (s *S) TestStrictFieldMap(c *C) {
286 fields := schema.Fields{303 fields := schema.Fields{
287 "a": schema.Const("A"),304 "a": schema.Const("A"),
288 "b": schema.Const("B"),305 "b": schema.Const("B"),
289 }306 "c": schema.Const("C"),
290 sch := schema.StrictFieldMap(fields, schema.Optional{"b"})307 }
308 defaults := schema.Defaults{
309 "b": schema.Omit,
310 "c": "C",
311 }
312 sch := schema.StrictFieldMap(fields, defaults)
291 assertFieldMap(c, sch)313 assertFieldMap(c, sch)
292314
293 out, err := sch.Coerce(map[string]interface{}{"a": "A", "b": "B", "c": "C"}, aPath)315 out, err := sch.Coerce(map[string]interface{}{"a": "A", "b": "B", "d": "D"}, aPath)
294 c.Assert(out, IsNil)316 c.Assert(out, IsNil)
295 c.Assert(err, ErrorMatches, `<path>.c: expected nothing, got "C"`)317 c.Assert(err, ErrorMatches, `<path>.d: expected nothing, got "D"`)
296}318}
297319
298func (s *S) TestSchemaMap(c *C) {320func (s *S) TestSchemaMap(c *C) {

Subscribers

People subscribed via source and target branches