1package schema
2
3import (
4	"fmt"
5	"strconv"
6	"strings"
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
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
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}
137				case *Schema:
138					current, _ = current.Elem.(*Schema)
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
262func mapValuesToPrimitive(k string, m map[string]interface{}, schema *Schema) error {
263	elemType, err := getValueType(k, schema)
264	if err != nil {
265		return err
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}
344