blob: 765f7e33d88e6492d035bcdd0959d805d612311f [file] [log] [blame]
David K. Bainbridge528b3182017-01-23 08:51:59 -08001// Copyright 2015 Canonical Ltd.
2// Licensed under the LGPLv3, see LICENCE file for details.
3
4package schema
5
6import (
7 "fmt"
8 "reflect"
9)
10
11// Omit is a marker for FieldMap and StructFieldMap defaults parameter.
12// If a field is not present in the map and defaults to Omit, the missing
13// field will be ommitted from the coerced map as well.
14var Omit omit
15
16type omit struct{}
17
18type Fields map[string]Checker
19type Defaults map[string]interface{}
20
21// FieldMap returns a Checker that accepts a map value with defined
22// string keys. Every key has an independent checker associated,
23// and processing will only succeed if all the values succeed
24// individually. If a field fails to be processed, processing stops
25// and returns with the underlying error.
26//
27// Fields in defaults will be set to the provided value if not present
28// in the coerced map. If the default value is schema.Omit, the
29// missing field will be omitted from the coerced map.
30//
31// The coerced output value has type map[string]interface{}.
32func FieldMap(fields Fields, defaults Defaults) Checker {
33 return fieldMapC{fields, defaults, false}
34}
35
36// StrictFieldMap returns a Checker that acts as the one returned by FieldMap,
37// but the Checker returns an error if it encounters an unknown key.
38func StrictFieldMap(fields Fields, defaults Defaults) Checker {
39 return fieldMapC{fields, defaults, true}
40}
41
42type fieldMapC struct {
43 fields Fields
44 defaults Defaults
45 strict bool
46}
47
48var stringType = reflect.TypeOf("")
49
50func hasStrictStringKeys(rv reflect.Value) bool {
51 if rv.Type().Key() == stringType {
52 return true
53 }
54 if rv.Type().Key().Kind() != reflect.Interface {
55 return false
56 }
57 for _, k := range rv.MapKeys() {
58 if k.Elem().Type() != stringType {
59 return false
60 }
61 }
62 return true
63}
64
65func (c fieldMapC) Coerce(v interface{}, path []string) (interface{}, error) {
66 rv := reflect.ValueOf(v)
67 if rv.Kind() != reflect.Map {
68 return nil, error_{"map", v, path}
69 }
70 if !hasStrictStringKeys(rv) {
71 return nil, error_{"map[string]", v, path}
72 }
73
74 if c.strict {
75 for _, k := range rv.MapKeys() {
76 ks := k.String()
77 if _, ok := c.fields[ks]; !ok {
78 return nil, fmt.Errorf("%sunknown key %q (value %#v)", pathAsPrefix(path), ks, rv.MapIndex(k).Interface())
79 }
80 }
81 }
82
83 vpath := append(path, ".", "?")
84
85 out := make(map[string]interface{}, rv.Len())
86 for k, checker := range c.fields {
87 valuev := rv.MapIndex(reflect.ValueOf(k))
88 var value interface{}
89 if valuev.IsValid() {
90 value = valuev.Interface()
91 } else if dflt, ok := c.defaults[k]; ok {
92 if dflt == Omit {
93 continue
94 }
95 value = dflt
96 }
97 vpath[len(vpath)-1] = k
98 newv, err := checker.Coerce(value, vpath)
99 if err != nil {
100 return nil, err
101 }
102 out[k] = newv
103 }
104 for k, v := range c.defaults {
105 if v == Omit {
106 continue
107 }
108 if _, ok := out[k]; !ok {
109 checker, ok := c.fields[k]
110 if !ok {
111 return nil, fmt.Errorf("got default value for unknown field %q", k)
112 }
113 vpath[len(vpath)-1] = k
114 newv, err := checker.Coerce(v, vpath)
115 if err != nil {
116 return nil, err
117 }
118 out[k] = newv
119 }
120 }
121 return out, nil
122}
123
124// FieldMapSet returns a Checker that accepts a map value checked
125// against one of several FieldMap checkers. The actual checker
126// used is the first one whose checker associated with the selector
127// field processes the map correctly. If no checker processes
128// the selector value correctly, an error is returned.
129//
130// The coerced output value has type map[string]interface{}.
131func FieldMapSet(selector string, maps []Checker) Checker {
132 fmaps := make([]fieldMapC, len(maps))
133 for i, m := range maps {
134 if fmap, ok := m.(fieldMapC); ok {
135 if checker, _ := fmap.fields[selector]; checker == nil {
136 panic("FieldMapSet has a FieldMap with a missing selector")
137 }
138 fmaps[i] = fmap
139 } else {
140 panic("FieldMapSet got a non-FieldMap checker")
141 }
142 }
143 return mapSetC{selector, fmaps}
144}
145
146type mapSetC struct {
147 selector string
148 fmaps []fieldMapC
149}
150
151func (c mapSetC) Coerce(v interface{}, path []string) (interface{}, error) {
152 rv := reflect.ValueOf(v)
153 if rv.Kind() != reflect.Map {
154 return nil, error_{"map", v, path}
155 }
156
157 var selector interface{}
158 selectorv := rv.MapIndex(reflect.ValueOf(c.selector))
159 if selectorv.IsValid() {
160 selector = selectorv.Interface()
161 for _, fmap := range c.fmaps {
162 _, err := fmap.fields[c.selector].Coerce(selector, path)
163 if err != nil {
164 continue
165 }
166 return fmap.Coerce(v, path)
167 }
168 }
169 return nil, error_{"supported selector", selector, append(path, ".", c.selector)}
170}