]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package schema |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "reflect" | |
6 | "strconv" | |
7 | "strings" | |
8 | "sync" | |
9 | ||
10 | "github.com/mitchellh/mapstructure" | |
11 | ) | |
12 | ||
13 | // MapFieldWriter writes data into a single map[string]string structure. | |
14 | type MapFieldWriter struct { | |
15 | Schema map[string]*Schema | |
16 | ||
17 | lock sync.Mutex | |
18 | result map[string]string | |
19 | } | |
20 | ||
21 | // Map returns the underlying map that is being written to. | |
22 | func (w *MapFieldWriter) Map() map[string]string { | |
23 | w.lock.Lock() | |
24 | defer w.lock.Unlock() | |
25 | if w.result == nil { | |
26 | w.result = make(map[string]string) | |
27 | } | |
28 | ||
29 | return w.result | |
30 | } | |
31 | ||
32 | func (w *MapFieldWriter) unsafeWriteField(addr string, value string) { | |
33 | w.lock.Lock() | |
34 | defer w.lock.Unlock() | |
35 | if w.result == nil { | |
36 | w.result = make(map[string]string) | |
37 | } | |
38 | ||
39 | w.result[addr] = value | |
40 | } | |
41 | ||
15c0b25d AP |
42 | // clearTree clears a field and any sub-fields of the given address out of the |
43 | // map. This should be used to reset some kind of complex structures (namely | |
44 | // sets) before writing to make sure that any conflicting data is removed (for | |
45 | // example, if the set was previously written to the writer's layer). | |
46 | func (w *MapFieldWriter) clearTree(addr []string) { | |
47 | prefix := strings.Join(addr, ".") + "." | |
48 | for k := range w.result { | |
49 | if strings.HasPrefix(k, prefix) { | |
50 | delete(w.result, k) | |
51 | } | |
52 | } | |
53 | } | |
54 | ||
bae9f6d2 JC |
55 | func (w *MapFieldWriter) WriteField(addr []string, value interface{}) error { |
56 | w.lock.Lock() | |
57 | defer w.lock.Unlock() | |
58 | if w.result == nil { | |
59 | w.result = make(map[string]string) | |
60 | } | |
61 | ||
62 | schemaList := addrToSchema(addr, w.Schema) | |
63 | if len(schemaList) == 0 { | |
64 | return fmt.Errorf("Invalid address to set: %#v", addr) | |
65 | } | |
66 | ||
67 | // If we're setting anything other than a list root or set root, | |
68 | // then disallow it. | |
69 | for _, schema := range schemaList[:len(schemaList)-1] { | |
70 | if schema.Type == TypeList { | |
71 | return fmt.Errorf( | |
72 | "%s: can only set full list", | |
73 | strings.Join(addr, ".")) | |
74 | } | |
75 | ||
76 | if schema.Type == TypeMap { | |
77 | return fmt.Errorf( | |
78 | "%s: can only set full map", | |
79 | strings.Join(addr, ".")) | |
80 | } | |
81 | ||
82 | if schema.Type == TypeSet { | |
83 | return fmt.Errorf( | |
84 | "%s: can only set full set", | |
85 | strings.Join(addr, ".")) | |
86 | } | |
87 | } | |
88 | ||
89 | return w.set(addr, value) | |
90 | } | |
91 | ||
92 | func (w *MapFieldWriter) set(addr []string, value interface{}) error { | |
93 | schemaList := addrToSchema(addr, w.Schema) | |
94 | if len(schemaList) == 0 { | |
95 | return fmt.Errorf("Invalid address to set: %#v", addr) | |
96 | } | |
97 | ||
98 | schema := schemaList[len(schemaList)-1] | |
99 | switch schema.Type { | |
100 | case TypeBool, TypeInt, TypeFloat, TypeString: | |
101 | return w.setPrimitive(addr, value, schema) | |
102 | case TypeList: | |
103 | return w.setList(addr, value, schema) | |
104 | case TypeMap: | |
105 | return w.setMap(addr, value, schema) | |
106 | case TypeSet: | |
107 | return w.setSet(addr, value, schema) | |
108 | case typeObject: | |
109 | return w.setObject(addr, value, schema) | |
110 | default: | |
111 | panic(fmt.Sprintf("Unknown type: %#v", schema.Type)) | |
112 | } | |
113 | } | |
114 | ||
115 | func (w *MapFieldWriter) setList( | |
116 | addr []string, | |
117 | v interface{}, | |
118 | schema *Schema) error { | |
119 | k := strings.Join(addr, ".") | |
120 | setElement := func(idx string, value interface{}) error { | |
121 | addrCopy := make([]string, len(addr), len(addr)+1) | |
122 | copy(addrCopy, addr) | |
123 | return w.set(append(addrCopy, idx), value) | |
124 | } | |
125 | ||
126 | var vs []interface{} | |
127 | if err := mapstructure.Decode(v, &vs); err != nil { | |
128 | return fmt.Errorf("%s: %s", k, err) | |
129 | } | |
130 | ||
15c0b25d AP |
131 | // Wipe the set from the current writer prior to writing if it exists. |
132 | // Multiple writes to the same layer is a lot safer for lists than sets due | |
133 | // to the fact that indexes are always deterministic and the length will | |
134 | // always be updated with the current length on the last write, but making | |
135 | // sure we have a clean namespace removes any chance for edge cases to pop up | |
136 | // and ensures that the last write to the set is the correct value. | |
137 | w.clearTree(addr) | |
138 | ||
bae9f6d2 JC |
139 | // Set the entire list. |
140 | var err error | |
141 | for i, elem := range vs { | |
142 | is := strconv.FormatInt(int64(i), 10) | |
143 | err = setElement(is, elem) | |
144 | if err != nil { | |
145 | break | |
146 | } | |
147 | } | |
148 | if err != nil { | |
149 | for i, _ := range vs { | |
150 | is := strconv.FormatInt(int64(i), 10) | |
151 | setElement(is, nil) | |
152 | } | |
153 | ||
154 | return err | |
155 | } | |
156 | ||
157 | w.result[k+".#"] = strconv.FormatInt(int64(len(vs)), 10) | |
158 | return nil | |
159 | } | |
160 | ||
161 | func (w *MapFieldWriter) setMap( | |
162 | addr []string, | |
163 | value interface{}, | |
164 | schema *Schema) error { | |
165 | k := strings.Join(addr, ".") | |
166 | v := reflect.ValueOf(value) | |
167 | vs := make(map[string]interface{}) | |
168 | ||
169 | if value == nil { | |
170 | // The empty string here means the map is removed. | |
171 | w.result[k] = "" | |
172 | return nil | |
173 | } | |
174 | ||
175 | if v.Kind() != reflect.Map { | |
176 | return fmt.Errorf("%s: must be a map", k) | |
177 | } | |
178 | if v.Type().Key().Kind() != reflect.String { | |
179 | return fmt.Errorf("%s: keys must strings", k) | |
180 | } | |
181 | for _, mk := range v.MapKeys() { | |
182 | mv := v.MapIndex(mk) | |
183 | vs[mk.String()] = mv.Interface() | |
184 | } | |
185 | ||
15c0b25d AP |
186 | // Wipe this address tree. The contents of the map should always reflect the |
187 | // last write made to it. | |
188 | w.clearTree(addr) | |
189 | ||
bae9f6d2 JC |
190 | // Remove the pure key since we're setting the full map value |
191 | delete(w.result, k) | |
192 | ||
193 | // Set each subkey | |
194 | addrCopy := make([]string, len(addr), len(addr)+1) | |
195 | copy(addrCopy, addr) | |
196 | for subKey, v := range vs { | |
197 | if err := w.set(append(addrCopy, subKey), v); err != nil { | |
198 | return err | |
199 | } | |
200 | } | |
201 | ||
202 | // Set the count | |
203 | w.result[k+".%"] = strconv.Itoa(len(vs)) | |
204 | ||
205 | return nil | |
206 | } | |
207 | ||
208 | func (w *MapFieldWriter) setObject( | |
209 | addr []string, | |
210 | value interface{}, | |
211 | schema *Schema) error { | |
212 | // Set the entire object. First decode into a proper structure | |
213 | var v map[string]interface{} | |
214 | if err := mapstructure.Decode(value, &v); err != nil { | |
215 | return fmt.Errorf("%s: %s", strings.Join(addr, "."), err) | |
216 | } | |
217 | ||
218 | // Make space for additional elements in the address | |
219 | addrCopy := make([]string, len(addr), len(addr)+1) | |
220 | copy(addrCopy, addr) | |
221 | ||
222 | // Set each element in turn | |
223 | var err error | |
224 | for k1, v1 := range v { | |
225 | if err = w.set(append(addrCopy, k1), v1); err != nil { | |
226 | break | |
227 | } | |
228 | } | |
229 | if err != nil { | |
230 | for k1, _ := range v { | |
231 | w.set(append(addrCopy, k1), nil) | |
232 | } | |
233 | } | |
234 | ||
235 | return err | |
236 | } | |
237 | ||
238 | func (w *MapFieldWriter) setPrimitive( | |
239 | addr []string, | |
240 | v interface{}, | |
241 | schema *Schema) error { | |
242 | k := strings.Join(addr, ".") | |
243 | ||
244 | if v == nil { | |
245 | // The empty string here means the value is removed. | |
246 | w.result[k] = "" | |
247 | return nil | |
248 | } | |
249 | ||
250 | var set string | |
251 | switch schema.Type { | |
252 | case TypeBool: | |
253 | var b bool | |
254 | if err := mapstructure.Decode(v, &b); err != nil { | |
255 | return fmt.Errorf("%s: %s", k, err) | |
256 | } | |
257 | ||
258 | set = strconv.FormatBool(b) | |
259 | case TypeString: | |
260 | if err := mapstructure.Decode(v, &set); err != nil { | |
261 | return fmt.Errorf("%s: %s", k, err) | |
262 | } | |
263 | case TypeInt: | |
264 | var n int | |
265 | if err := mapstructure.Decode(v, &n); err != nil { | |
266 | return fmt.Errorf("%s: %s", k, err) | |
267 | } | |
268 | set = strconv.FormatInt(int64(n), 10) | |
269 | case TypeFloat: | |
270 | var n float64 | |
271 | if err := mapstructure.Decode(v, &n); err != nil { | |
272 | return fmt.Errorf("%s: %s", k, err) | |
273 | } | |
274 | set = strconv.FormatFloat(float64(n), 'G', -1, 64) | |
275 | default: | |
276 | return fmt.Errorf("Unknown type: %#v", schema.Type) | |
277 | } | |
278 | ||
279 | w.result[k] = set | |
280 | return nil | |
281 | } | |
282 | ||
283 | func (w *MapFieldWriter) setSet( | |
284 | addr []string, | |
285 | value interface{}, | |
286 | schema *Schema) error { | |
287 | addrCopy := make([]string, len(addr), len(addr)+1) | |
288 | copy(addrCopy, addr) | |
289 | k := strings.Join(addr, ".") | |
290 | ||
291 | if value == nil { | |
292 | w.result[k+".#"] = "0" | |
293 | return nil | |
294 | } | |
295 | ||
296 | // If it is a slice, then we have to turn it into a *Set so that | |
297 | // we get the proper order back based on the hash code. | |
298 | if v := reflect.ValueOf(value); v.Kind() == reflect.Slice { | |
299 | // Build a temp *ResourceData to use for the conversion | |
107c1cdb | 300 | tempAddr := addr[len(addr)-1:] |
bae9f6d2 JC |
301 | tempSchema := *schema |
302 | tempSchema.Type = TypeList | |
107c1cdb | 303 | tempSchemaMap := map[string]*Schema{tempAddr[0]: &tempSchema} |
bae9f6d2 JC |
304 | tempW := &MapFieldWriter{Schema: tempSchemaMap} |
305 | ||
306 | // Set the entire list, this lets us get sane values out of it | |
107c1cdb | 307 | if err := tempW.WriteField(tempAddr, value); err != nil { |
bae9f6d2 JC |
308 | return err |
309 | } | |
310 | ||
311 | // Build the set by going over the list items in order and | |
312 | // hashing them into the set. The reason we go over the list and | |
313 | // not the `value` directly is because this forces all types | |
314 | // to become []interface{} (generic) instead of []string, which | |
315 | // most hash functions are expecting. | |
316 | s := schema.ZeroValue().(*Set) | |
317 | tempR := &MapFieldReader{ | |
318 | Map: BasicMapReader(tempW.Map()), | |
319 | Schema: tempSchemaMap, | |
320 | } | |
321 | for i := 0; i < v.Len(); i++ { | |
322 | is := strconv.FormatInt(int64(i), 10) | |
107c1cdb | 323 | result, err := tempR.ReadField(append(tempAddr, is)) |
bae9f6d2 JC |
324 | if err != nil { |
325 | return err | |
326 | } | |
327 | if !result.Exists { | |
328 | panic("set item just set doesn't exist") | |
329 | } | |
330 | ||
331 | s.Add(result.Value) | |
332 | } | |
333 | ||
334 | value = s | |
335 | } | |
336 | ||
15c0b25d AP |
337 | // Clear any keys that match the set address first. This is necessary because |
338 | // it's always possible and sometimes may be necessary to write to a certain | |
339 | // writer layer more than once with different set data each time, which will | |
340 | // lead to different keys being inserted, which can lead to determinism | |
341 | // problems when the old data isn't wiped first. | |
342 | w.clearTree(addr) | |
343 | ||
107c1cdb ND |
344 | if value.(*Set) == nil { |
345 | w.result[k+".#"] = "0" | |
346 | return nil | |
347 | } | |
348 | ||
bae9f6d2 JC |
349 | for code, elem := range value.(*Set).m { |
350 | if err := w.set(append(addrCopy, code), elem); err != nil { | |
351 | return err | |
352 | } | |
353 | } | |
354 | ||
355 | w.result[k+".#"] = strconv.Itoa(value.(*Set).Len()) | |
356 | return nil | |
357 | } |