]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/helper/schema/field_reader.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / helper / schema / field_reader.go
CommitLineData
bae9f6d2
JC
1package schema
2
3import (
4 "fmt"
5 "strconv"
107c1cdb 6 "strings"
bae9f6d2
JC
7)
8
9// FieldReaders are responsible for decoding fields out of data into
10// the proper typed representation. ResourceData uses this to query data
11// out of multiple sources: config, state, diffs, etc.
12type FieldReader interface {
13 ReadField([]string) (FieldReadResult, error)
14}
15
16// FieldReadResult encapsulates all the resulting data from reading
17// a field.
18type FieldReadResult struct {
19 // Value is the actual read value. NegValue is the _negative_ value
20 // or the items that should be removed (if they existed). NegValue
21 // doesn't make sense for primitives but is important for any
22 // container types such as maps, sets, lists.
23 Value interface{}
24 ValueProcessed interface{}
25
26 // Exists is true if the field was found in the data. False means
27 // it wasn't found if there was no error.
28 Exists bool
29
30 // Computed is true if the field was found but the value
31 // is computed.
32 Computed bool
33}
34
35// ValueOrZero returns the value of this result or the zero value of the
36// schema type, ensuring a consistent non-nil return value.
37func (r *FieldReadResult) ValueOrZero(s *Schema) interface{} {
38 if r.Value != nil {
39 return r.Value
40 }
41
42 return s.ZeroValue()
43}
44
107c1cdb
ND
45// SchemasForFlatmapPath tries its best to find a sequence of schemas that
46// the given dot-delimited attribute path traverses through.
47func SchemasForFlatmapPath(path string, schemaMap map[string]*Schema) []*Schema {
48 parts := strings.Split(path, ".")
49 return addrToSchema(parts, schemaMap)
50}
51
bae9f6d2
JC
52// addrToSchema finds the final element schema for the given address
53// and the given schema. It returns all the schemas that led to the final
54// schema. These are in order of the address (out to in).
55func addrToSchema(addr []string, schemaMap map[string]*Schema) []*Schema {
56 current := &Schema{
57 Type: typeObject,
58 Elem: schemaMap,
59 }
60
61 // If we aren't given an address, then the user is requesting the
62 // full object, so we return the special value which is the full object.
63 if len(addr) == 0 {
64 return []*Schema{current}
65 }
66
67 result := make([]*Schema, 0, len(addr))
68 for len(addr) > 0 {
69 k := addr[0]
70 addr = addr[1:]
71
72 REPEAT:
73 // We want to trim off the first "typeObject" since its not a
74 // real lookup that people do. i.e. []string{"foo"} in a structure
75 // isn't {typeObject, typeString}, its just a {typeString}.
76 if len(result) > 0 || current.Type != typeObject {
77 result = append(result, current)
78 }
79
80 switch t := current.Type; t {
81 case TypeBool, TypeInt, TypeFloat, TypeString:
82 if len(addr) > 0 {
83 return nil
84 }
85 case TypeList, TypeSet:
86 isIndex := len(addr) > 0 && addr[0] == "#"
87
88 switch v := current.Elem.(type) {
89 case *Resource:
90 current = &Schema{
91 Type: typeObject,
92 Elem: v.Schema,
93 }
94 case *Schema:
95 current = v
96 case ValueType:
97 current = &Schema{Type: v}
98 default:
99 // we may not know the Elem type and are just looking for the
100 // index
101 if isIndex {
102 break
103 }
104
105 if len(addr) == 0 {
106 // we've processed the address, so return what we've
107 // collected
108 return result
109 }
110
111 if len(addr) == 1 {
112 if _, err := strconv.Atoi(addr[0]); err == nil {
113 // we're indexing a value without a schema. This can
114 // happen if the list is nested in another schema type.
115 // Default to a TypeString like we do with a map
116 current = &Schema{Type: TypeString}
117 break
118 }
119 }
120
121 return nil
122 }
123
124 // If we only have one more thing and the next thing
125 // is a #, then we're accessing the index which is always
126 // an int.
127 if isIndex {
128 current = &Schema{Type: TypeInt}
129 break
130 }
131
132 case TypeMap:
133 if len(addr) > 0 {
134 switch v := current.Elem.(type) {
135 case ValueType:
136 current = &Schema{Type: v}
15c0b25d
AP
137 case *Schema:
138 current, _ = current.Elem.(*Schema)
bae9f6d2
JC
139 default:
140 // maps default to string values. This is all we can have
141 // if this is nested in another list or map.
142 current = &Schema{Type: TypeString}
143 }
144 }
145 case typeObject:
146 // If we're already in the object, then we want to handle Sets
147 // and Lists specially. Basically, their next key is the lookup
148 // key (the set value or the list element). For these scenarios,
149 // we just want to skip it and move to the next element if there
150 // is one.
151 if len(result) > 0 {
152 lastType := result[len(result)-2].Type
153 if lastType == TypeSet || lastType == TypeList {
154 if len(addr) == 0 {
155 break
156 }
157
158 k = addr[0]
159 addr = addr[1:]
160 }
161 }
162
163 m := current.Elem.(map[string]*Schema)
164 val, ok := m[k]
165 if !ok {
166 return nil
167 }
168
169 current = val
170 goto REPEAT
171 }
172 }
173
174 return result
175}
176
177// readListField is a generic method for reading a list field out of a
178// a FieldReader. It does this based on the assumption that there is a key
179// "foo.#" for a list "foo" and that the indexes are "foo.0", "foo.1", etc.
180// after that point.
181func readListField(
182 r FieldReader, addr []string, schema *Schema) (FieldReadResult, error) {
183 addrPadded := make([]string, len(addr)+1)
184 copy(addrPadded, addr)
185 addrPadded[len(addrPadded)-1] = "#"
186
187 // Get the number of elements in the list
188 countResult, err := r.ReadField(addrPadded)
189 if err != nil {
190 return FieldReadResult{}, err
191 }
192 if !countResult.Exists {
193 // No count, means we have no list
194 countResult.Value = 0
195 }
196
197 // If we have an empty list, then return an empty list
198 if countResult.Computed || countResult.Value.(int) == 0 {
199 return FieldReadResult{
200 Value: []interface{}{},
201 Exists: countResult.Exists,
202 Computed: countResult.Computed,
203 }, nil
204 }
205
206 // Go through each count, and get the item value out of it
207 result := make([]interface{}, countResult.Value.(int))
208 for i, _ := range result {
209 is := strconv.FormatInt(int64(i), 10)
210 addrPadded[len(addrPadded)-1] = is
211 rawResult, err := r.ReadField(addrPadded)
212 if err != nil {
213 return FieldReadResult{}, err
214 }
215 if !rawResult.Exists {
216 // This should never happen, because by the time the data
217 // gets to the FieldReaders, all the defaults should be set by
218 // Schema.
219 rawResult.Value = nil
220 }
221
222 result[i] = rawResult.Value
223 }
224
225 return FieldReadResult{
226 Value: result,
227 Exists: true,
228 }, nil
229}
230
231// readObjectField is a generic method for reading objects out of FieldReaders
232// based on the assumption that building an address of []string{k, FIELD}
233// will result in the proper field data.
234func readObjectField(
235 r FieldReader,
236 addr []string,
237 schema map[string]*Schema) (FieldReadResult, error) {
238 result := make(map[string]interface{})
239 exists := false
240 for field, s := range schema {
241 addrRead := make([]string, len(addr), len(addr)+1)
242 copy(addrRead, addr)
243 addrRead = append(addrRead, field)
244 rawResult, err := r.ReadField(addrRead)
245 if err != nil {
246 return FieldReadResult{}, err
247 }
248 if rawResult.Exists {
249 exists = true
250 }
251
252 result[field] = rawResult.ValueOrZero(s)
253 }
254
255 return FieldReadResult{
256 Value: result,
257 Exists: exists,
258 }, nil
259}
260
261// convert map values to the proper primitive type based on schema.Elem
15c0b25d
AP
262func mapValuesToPrimitive(k string, m map[string]interface{}, schema *Schema) error {
263 elemType, err := getValueType(k, schema)
264 if err != nil {
265 return err
bae9f6d2
JC
266 }
267
268 switch elemType {
269 case TypeInt, TypeFloat, TypeBool:
270 for k, v := range m {
271 vs, ok := v.(string)
272 if !ok {
273 continue
274 }
275
276 v, err := stringToPrimitive(vs, false, &Schema{Type: elemType})
277 if err != nil {
278 return err
279 }
280
281 m[k] = v
282 }
283 }
284 return nil
285}
286
287func stringToPrimitive(
288 value string, computed bool, schema *Schema) (interface{}, error) {
289 var returnVal interface{}
290 switch schema.Type {
291 case TypeBool:
292 if value == "" {
293 returnVal = false
294 break
295 }
296 if computed {
297 break
298 }
299
300 v, err := strconv.ParseBool(value)
301 if err != nil {
302 return nil, err
303 }
304
305 returnVal = v
306 case TypeFloat:
307 if value == "" {
308 returnVal = 0.0
309 break
310 }
311 if computed {
312 break
313 }
314
315 v, err := strconv.ParseFloat(value, 64)
316 if err != nil {
317 return nil, err
318 }
319
320 returnVal = v
321 case TypeInt:
322 if value == "" {
323 returnVal = 0
324 break
325 }
326 if computed {
327 break
328 }
329
330 v, err := strconv.ParseInt(value, 0, 0)
331 if err != nil {
332 return nil, err
333 }
334
335 returnVal = int(v)
336 case TypeString:
337 returnVal = value
338 default:
339 panic(fmt.Sprintf("Unknown type: %s", schema.Type))
340 }
341
342 return returnVal, nil
343}