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