1package convert
2
3import (
4	"github.com/zclconf/go-cty/cty"
5)
6
7// conversionCollectionToList returns a conversion that will apply the given
8// conversion to all of the elements of a collection (something that supports
9// ForEachElement and LengthInt) and then returns the result as a list.
10//
11// "conv" can be nil if the elements are expected to already be of the
12// correct type and just need to be re-wrapped into a list. (For example,
13// if we're converting from a set into a list of the same element type.)
14func conversionCollectionToList(ety cty.Type, conv conversion) conversion {
15	return func(val cty.Value, path cty.Path) (cty.Value, error) {
16		elems := make([]cty.Value, 0, val.LengthInt())
17		i := int64(0)
18		path = append(path, nil)
19		it := val.ElementIterator()
20		for it.Next() {
21			_, val := it.Element()
22			var err error
23
24			path[len(path)-1] = cty.IndexStep{
25				Key: cty.NumberIntVal(i),
26			}
27
28			if conv != nil {
29				val, err = conv(val, path)
30				if err != nil {
31					return cty.NilVal, err
32				}
33			}
34			elems = append(elems, val)
35
36			i++
37		}
38
39		if len(elems) == 0 {
40			return cty.ListValEmpty(ety), nil
41		}
42
43		return cty.ListVal(elems), nil
44	}
45}
46
47// conversionCollectionToSet returns a conversion that will apply the given
48// conversion to all of the elements of a collection (something that supports
49// ForEachElement and LengthInt) and then returns the result as a set.
50//
51// "conv" can be nil if the elements are expected to already be of the
52// correct type and just need to be re-wrapped into a set. (For example,
53// if we're converting from a list into a set of the same element type.)
54func conversionCollectionToSet(ety cty.Type, conv conversion) conversion {
55	return func(val cty.Value, path cty.Path) (cty.Value, error) {
56		elems := make([]cty.Value, 0, val.LengthInt())
57		i := int64(0)
58		path = append(path, nil)
59		it := val.ElementIterator()
60		for it.Next() {
61			_, val := it.Element()
62			var err error
63
64			path[len(path)-1] = cty.IndexStep{
65				Key: cty.NumberIntVal(i),
66			}
67
68			if conv != nil {
69				val, err = conv(val, path)
70				if err != nil {
71					return cty.NilVal, err
72				}
73			}
74			elems = append(elems, val)
75
76			i++
77		}
78
79		if len(elems) == 0 {
80			return cty.SetValEmpty(ety), nil
81		}
82
83		return cty.SetVal(elems), nil
84	}
85}
86
87// conversionCollectionToMap returns a conversion that will apply the given
88// conversion to all of the elements of a collection (something that supports
89// ForEachElement and LengthInt) and then returns the result as a map.
90//
91// "conv" can be nil if the elements are expected to already be of the
92// correct type and just need to be re-wrapped into a map.
93func conversionCollectionToMap(ety cty.Type, conv conversion) conversion {
94	return func(val cty.Value, path cty.Path) (cty.Value, error) {
95		elems := make(map[string]cty.Value, 0)
96		path = append(path, nil)
97		it := val.ElementIterator()
98		for it.Next() {
99			key, val := it.Element()
100			var err error
101
102			path[len(path)-1] = cty.IndexStep{
103				Key: key,
104			}
105
106			keyStr, err := Convert(key, cty.String)
107			if err != nil {
108				// Should never happen, because keys can only be numbers or
109				// strings and both can convert to string.
110				return cty.DynamicVal, path.NewErrorf("cannot convert key type %s to string for map", key.Type().FriendlyName())
111			}
112
113			if conv != nil {
114				val, err = conv(val, path)
115				if err != nil {
116					return cty.NilVal, err
117				}
118			}
119
120			elems[keyStr.AsString()] = val
121		}
122
123		if len(elems) == 0 {
124			return cty.MapValEmpty(ety), nil
125		}
126
127		return cty.MapVal(elems), nil
128	}
129}
130
131// conversionTupleToSet returns a conversion that will take a value of the
132// given tuple type and return a set of the given element type.
133//
134// Will panic if the given tupleType isn't actually a tuple type.
135func conversionTupleToSet(tupleType cty.Type, listEty cty.Type, unsafe bool) conversion {
136	tupleEtys := tupleType.TupleElementTypes()
137
138	if len(tupleEtys) == 0 {
139		// Empty tuple short-circuit
140		return func(val cty.Value, path cty.Path) (cty.Value, error) {
141			return cty.SetValEmpty(listEty), nil
142		}
143	}
144
145	if listEty == cty.DynamicPseudoType {
146		// This is a special case where the caller wants us to find
147		// a suitable single type that all elements can convert to, if
148		// possible.
149		listEty, _ = unify(tupleEtys, unsafe)
150		if listEty == cty.NilType {
151			return nil
152		}
153	}
154
155	elemConvs := make([]conversion, len(tupleEtys))
156	for i, tupleEty := range tupleEtys {
157		if tupleEty.Equals(listEty) {
158			// no conversion required
159			continue
160		}
161
162		elemConvs[i] = getConversion(tupleEty, listEty, unsafe)
163		if elemConvs[i] == nil {
164			// If any of our element conversions are impossible, then the our
165			// whole conversion is impossible.
166			return nil
167		}
168	}
169
170	// If we fall out here then a conversion is possible, using the
171	// element conversions in elemConvs
172	return func(val cty.Value, path cty.Path) (cty.Value, error) {
173		elems := make([]cty.Value, 0, len(elemConvs))
174		path = append(path, nil)
175		i := int64(0)
176		it := val.ElementIterator()
177		for it.Next() {
178			_, val := it.Element()
179			var err error
180
181			path[len(path)-1] = cty.IndexStep{
182				Key: cty.NumberIntVal(i),
183			}
184
185			conv := elemConvs[i]
186			if conv != nil {
187				val, err = conv(val, path)
188				if err != nil {
189					return cty.NilVal, err
190				}
191			}
192			elems = append(elems, val)
193
194			i++
195		}
196
197		return cty.SetVal(elems), nil
198	}
199}
200
201// conversionTupleToList returns a conversion that will take a value of the
202// given tuple type and return a list of the given element type.
203//
204// Will panic if the given tupleType isn't actually a tuple type.
205func conversionTupleToList(tupleType cty.Type, listEty cty.Type, unsafe bool) conversion {
206	tupleEtys := tupleType.TupleElementTypes()
207
208	if len(tupleEtys) == 0 {
209		// Empty tuple short-circuit
210		return func(val cty.Value, path cty.Path) (cty.Value, error) {
211			return cty.ListValEmpty(listEty), nil
212		}
213	}
214
215	if listEty == cty.DynamicPseudoType {
216		// This is a special case where the caller wants us to find
217		// a suitable single type that all elements can convert to, if
218		// possible.
219		listEty, _ = unify(tupleEtys, unsafe)
220		if listEty == cty.NilType {
221			return nil
222		}
223	}
224
225	elemConvs := make([]conversion, len(tupleEtys))
226	for i, tupleEty := range tupleEtys {
227		if tupleEty.Equals(listEty) {
228			// no conversion required
229			continue
230		}
231
232		elemConvs[i] = getConversion(tupleEty, listEty, unsafe)
233		if elemConvs[i] == nil {
234			// If any of our element conversions are impossible, then the our
235			// whole conversion is impossible.
236			return nil
237		}
238	}
239
240	// If we fall out here then a conversion is possible, using the
241	// element conversions in elemConvs
242	return func(val cty.Value, path cty.Path) (cty.Value, error) {
243		elems := make([]cty.Value, 0, len(elemConvs))
244		path = append(path, nil)
245		i := int64(0)
246		it := val.ElementIterator()
247		for it.Next() {
248			_, val := it.Element()
249			var err error
250
251			path[len(path)-1] = cty.IndexStep{
252				Key: cty.NumberIntVal(i),
253			}
254
255			conv := elemConvs[i]
256			if conv != nil {
257				val, err = conv(val, path)
258				if err != nil {
259					return cty.NilVal, err
260				}
261			}
262			elems = append(elems, val)
263
264			i++
265		}
266
267		return cty.ListVal(elems), nil
268	}
269}
270
271// conversionObjectToMap returns a conversion that will take a value of the
272// given object type and return a map of the given element type.
273//
274// Will panic if the given objectType isn't actually an object type.
275func conversionObjectToMap(objectType cty.Type, mapEty cty.Type, unsafe bool) conversion {
276	objectAtys := objectType.AttributeTypes()
277
278	if len(objectAtys) == 0 {
279		// Empty object short-circuit
280		return func(val cty.Value, path cty.Path) (cty.Value, error) {
281			return cty.MapValEmpty(mapEty), nil
282		}
283	}
284
285	if mapEty == cty.DynamicPseudoType {
286		// This is a special case where the caller wants us to find
287		// a suitable single type that all elements can convert to, if
288		// possible.
289		objectAtysList := make([]cty.Type, 0, len(objectAtys))
290		for _, aty := range objectAtys {
291			objectAtysList = append(objectAtysList, aty)
292		}
293		mapEty, _ = unify(objectAtysList, unsafe)
294		if mapEty == cty.NilType {
295			return nil
296		}
297	}
298
299	elemConvs := make(map[string]conversion, len(objectAtys))
300	for name, objectAty := range objectAtys {
301		if objectAty.Equals(mapEty) {
302			// no conversion required
303			continue
304		}
305
306		elemConvs[name] = getConversion(objectAty, mapEty, unsafe)
307		if elemConvs[name] == nil {
308			// If any of our element conversions are impossible, then the our
309			// whole conversion is impossible.
310			return nil
311		}
312	}
313
314	// If we fall out here then a conversion is possible, using the
315	// element conversions in elemConvs
316	return func(val cty.Value, path cty.Path) (cty.Value, error) {
317		elems := make(map[string]cty.Value, len(elemConvs))
318		path = append(path, nil)
319		it := val.ElementIterator()
320		for it.Next() {
321			name, val := it.Element()
322			var err error
323
324			path[len(path)-1] = cty.IndexStep{
325				Key: name,
326			}
327
328			conv := elemConvs[name.AsString()]
329			if conv != nil {
330				val, err = conv(val, path)
331				if err != nil {
332					return cty.NilVal, err
333				}
334			}
335			elems[name.AsString()] = val
336		}
337
338		return cty.MapVal(elems), nil
339	}
340}
341