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.
12 type FieldReader interface {
13 ReadField([]string) (FieldReadResult, error)
16 // FieldReadResult encapsulates all the resulting data from reading
18 type 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.
24 ValueProcessed interface{}
26 // Exists is true if the field was found in the data. False means
27 // it wasn't found if there was no error.
30 // Computed is true if the field was found but the value
35 // ValueOrZero returns the value of this result or the zero value of the
36 // schema type, ensuring a consistent non-nil return value.
37 func (r *FieldReadResult) ValueOrZero(s *Schema) interface{} {
45 // SchemasForFlatmapPath tries its best to find a sequence of schemas that
46 // the given dot-delimited attribute path traverses through.
47 func SchemasForFlatmapPath(path string, schemaMap map[string]*Schema) []*Schema {
48 parts := strings.Split(path, ".")
49 return addrToSchema(parts, schemaMap)
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).
55 func addrToSchema(addr []string, schemaMap map[string]*Schema) []*Schema {
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.
64 return []*Schema{current}
67 result := make([]*Schema, 0, len(addr))
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)
80 switch t := current.Type; t {
81 case TypeBool, TypeInt, TypeFloat, TypeString:
85 case TypeList, TypeSet:
86 isIndex := len(addr) > 0 && addr[0] == "#"
88 switch v := current.Elem.(type) {
97 current = &Schema{Type: v}
99 // we may not know the Elem type and are just looking for the
106 // we've processed the address, so return what we've
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}
124 // If we only have one more thing and the next thing
125 // is a #, then we're accessing the index which is always
128 current = &Schema{Type: TypeInt}
134 switch v := current.Elem.(type) {
136 current = &Schema{Type: v}
138 current, _ = current.Elem.(*Schema)
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}
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
152 lastType := result[len(result)-2].Type
153 if lastType == TypeSet || lastType == TypeList {
163 m := current.Elem.(map[string]*Schema)
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.
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] = "#"
187 // Get the number of elements in the list
188 countResult, err := r.ReadField(addrPadded)
190 return FieldReadResult{}, err
192 if !countResult.Exists {
193 // No count, means we have no list
194 countResult.Value = 0
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,
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)
213 return FieldReadResult{}, err
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
219 rawResult.Value = nil
222 result[i] = rawResult.Value
225 return FieldReadResult{
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.
234 func readObjectField(
237 schema map[string]*Schema) (FieldReadResult, error) {
238 result := make(map[string]interface{})
240 for field, s := range schema {
241 addrRead := make([]string, len(addr), len(addr)+1)
243 addrRead = append(addrRead, field)
244 rawResult, err := r.ReadField(addrRead)
246 return FieldReadResult{}, err
248 if rawResult.Exists {
252 result[field] = rawResult.ValueOrZero(s)
255 return FieldReadResult{
261 // convert map values to the proper primitive type based on schema.Elem
262 func mapValuesToPrimitive(k string, m map[string]interface{}, schema *Schema) error {
263 elemType, err := getValueType(k, schema)
269 case TypeInt, TypeFloat, TypeBool:
270 for k, v := range m {
276 v, err := stringToPrimitive(vs, false, &Schema{Type: elemType})
287 func stringToPrimitive(
288 value string, computed bool, schema *Schema) (interface{}, error) {
289 var returnVal interface{}
300 v, err := strconv.ParseBool(value)
315 v, err := strconv.ParseFloat(value, 64)
330 v, err := strconv.ParseInt(value, 0, 0)
339 panic(fmt.Sprintf("Unknown type: %s", schema.Type))
342 return returnVal, nil