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		if !val.Length().IsKnown() {
17			// If the input collection has an unknown length (which is true
18			// for a set containing unknown values) then our result must be
19			// an unknown list, because we can't predict how many elements
20			// the resulting list should have.
21			return cty.UnknownVal(cty.List(val.Type().ElementType())), nil
22		}
23
24		elems := make([]cty.Value, 0, val.LengthInt())
25		i := int64(0)
26		elemPath := append(path.Copy(), nil)
27		it := val.ElementIterator()
28		for it.Next() {
29			_, val := it.Element()
30			var err error
31
32			elemPath[len(elemPath)-1] = cty.IndexStep{
33				Key: cty.NumberIntVal(i),
34			}
35
36			if conv != nil {
37				val, err = conv(val, elemPath)
38				if err != nil {
39					return cty.NilVal, err
40				}
41			}
42			elems = append(elems, val)
43
44			i++
45		}
46
47		if len(elems) == 0 {
48			// Prefer a concrete type over a dynamic type when returning an
49			// empty list
50			if ety == cty.DynamicPseudoType {
51				return cty.ListValEmpty(val.Type().ElementType()), nil
52			}
53			return cty.ListValEmpty(ety), nil
54		}
55
56		if !cty.CanListVal(elems) {
57			return cty.NilVal, path.NewErrorf("element types must all match for conversion to list")
58		}
59
60		return cty.ListVal(elems), nil
61	}
62}
63
64// conversionCollectionToSet returns a conversion that will apply the given
65// conversion to all of the elements of a collection (something that supports
66// ForEachElement and LengthInt) and then returns the result as a set.
67//
68// "conv" can be nil if the elements are expected to already be of the
69// correct type and just need to be re-wrapped into a set. (For example,
70// if we're converting from a list into a set of the same element type.)
71func conversionCollectionToSet(ety cty.Type, conv conversion) conversion {
72	return func(val cty.Value, path cty.Path) (cty.Value, error) {
73		elems := make([]cty.Value, 0, val.LengthInt())
74		i := int64(0)
75		elemPath := append(path.Copy(), nil)
76		it := val.ElementIterator()
77		for it.Next() {
78			_, val := it.Element()
79			var err error
80
81			elemPath[len(elemPath)-1] = cty.IndexStep{
82				Key: cty.NumberIntVal(i),
83			}
84
85			if conv != nil {
86				val, err = conv(val, elemPath)
87				if err != nil {
88					return cty.NilVal, err
89				}
90			}
91			elems = append(elems, val)
92
93			i++
94		}
95
96		if len(elems) == 0 {
97			// Prefer a concrete type over a dynamic type when returning an
98			// empty set
99			if ety == cty.DynamicPseudoType {
100				return cty.SetValEmpty(val.Type().ElementType()), nil
101			}
102			return cty.SetValEmpty(ety), nil
103		}
104
105		if !cty.CanSetVal(elems) {
106			return cty.NilVal, path.NewErrorf("element types must all match for conversion to set")
107		}
108
109		return cty.SetVal(elems), nil
110	}
111}
112
113// conversionCollectionToMap returns a conversion that will apply the given
114// conversion to all of the elements of a collection (something that supports
115// ForEachElement and LengthInt) and then returns the result as a map.
116//
117// "conv" can be nil if the elements are expected to already be of the
118// correct type and just need to be re-wrapped into a map.
119func conversionCollectionToMap(ety cty.Type, conv conversion) conversion {
120	return func(val cty.Value, path cty.Path) (cty.Value, error) {
121		elems := make(map[string]cty.Value, 0)
122		elemPath := append(path.Copy(), nil)
123		it := val.ElementIterator()
124		for it.Next() {
125			key, val := it.Element()
126			var err error
127
128			elemPath[len(elemPath)-1] = cty.IndexStep{
129				Key: key,
130			}
131
132			keyStr, err := Convert(key, cty.String)
133			if err != nil {
134				// Should never happen, because keys can only be numbers or
135				// strings and both can convert to string.
136				return cty.DynamicVal, elemPath.NewErrorf("cannot convert key type %s to string for map", key.Type().FriendlyName())
137			}
138
139			if conv != nil {
140				val, err = conv(val, elemPath)
141				if err != nil {
142					return cty.NilVal, err
143				}
144			}
145
146			elems[keyStr.AsString()] = val
147		}
148
149		if len(elems) == 0 {
150			// Prefer a concrete type over a dynamic type when returning an
151			// empty map
152			if ety == cty.DynamicPseudoType {
153				return cty.MapValEmpty(val.Type().ElementType()), nil
154			}
155			return cty.MapValEmpty(ety), nil
156		}
157
158		if ety.IsCollectionType() || ety.IsObjectType() {
159			var err error
160			if elems, err = conversionUnifyCollectionElements(elems, path, false); err != nil {
161				return cty.NilVal, err
162			}
163		}
164
165		if !cty.CanMapVal(elems) {
166			return cty.NilVal, path.NewErrorf("element types must all match for conversion to map")
167		}
168
169		return cty.MapVal(elems), nil
170	}
171}
172
173// conversionTupleToSet returns a conversion that will take a value of the
174// given tuple type and return a set of the given element type.
175//
176// Will panic if the given tupleType isn't actually a tuple type.
177func conversionTupleToSet(tupleType cty.Type, setEty cty.Type, unsafe bool) conversion {
178	tupleEtys := tupleType.TupleElementTypes()
179
180	if len(tupleEtys) == 0 {
181		// Empty tuple short-circuit
182		return func(val cty.Value, path cty.Path) (cty.Value, error) {
183			return cty.SetValEmpty(setEty), nil
184		}
185	}
186
187	if setEty == cty.DynamicPseudoType {
188		// This is a special case where the caller wants us to find
189		// a suitable single type that all elements can convert to, if
190		// possible.
191		setEty, _ = unify(tupleEtys, unsafe)
192		if setEty == cty.NilType {
193			return nil
194		}
195
196		// If the set element type after unification is still the dynamic
197		// type, the only way this can result in a valid set is if all values
198		// are of dynamic type
199		if setEty == cty.DynamicPseudoType {
200			for _, tupleEty := range tupleEtys {
201				if !tupleEty.Equals(cty.DynamicPseudoType) {
202					return nil
203				}
204			}
205		}
206	}
207
208	elemConvs := make([]conversion, len(tupleEtys))
209	for i, tupleEty := range tupleEtys {
210		if tupleEty.Equals(setEty) {
211			// no conversion required
212			continue
213		}
214
215		elemConvs[i] = getConversion(tupleEty, setEty, unsafe)
216		if elemConvs[i] == nil {
217			// If any of our element conversions are impossible, then the our
218			// whole conversion is impossible.
219			return nil
220		}
221	}
222
223	// If we fall out here then a conversion is possible, using the
224	// element conversions in elemConvs
225	return func(val cty.Value, path cty.Path) (cty.Value, error) {
226		elems := make([]cty.Value, 0, len(elemConvs))
227		elemPath := append(path.Copy(), nil)
228		i := int64(0)
229		it := val.ElementIterator()
230		for it.Next() {
231			_, val := it.Element()
232			var err error
233
234			elemPath[len(elemPath)-1] = cty.IndexStep{
235				Key: cty.NumberIntVal(i),
236			}
237
238			conv := elemConvs[i]
239			if conv != nil {
240				val, err = conv(val, elemPath)
241				if err != nil {
242					return cty.NilVal, err
243				}
244			}
245			elems = append(elems, val)
246
247			i++
248		}
249
250		if !cty.CanSetVal(elems) {
251			return cty.NilVal, path.NewErrorf("element types must all match for conversion to set")
252		}
253
254		return cty.SetVal(elems), nil
255	}
256}
257
258// conversionTupleToList returns a conversion that will take a value of the
259// given tuple type and return a list of the given element type.
260//
261// Will panic if the given tupleType isn't actually a tuple type.
262func conversionTupleToList(tupleType cty.Type, listEty cty.Type, unsafe bool) conversion {
263	tupleEtys := tupleType.TupleElementTypes()
264
265	if len(tupleEtys) == 0 {
266		// Empty tuple short-circuit
267		return func(val cty.Value, path cty.Path) (cty.Value, error) {
268			return cty.ListValEmpty(listEty), nil
269		}
270	}
271
272	if listEty == cty.DynamicPseudoType {
273		// This is a special case where the caller wants us to find
274		// a suitable single type that all elements can convert to, if
275		// possible.
276		listEty, _ = unify(tupleEtys, unsafe)
277		if listEty == cty.NilType {
278			return nil
279		}
280
281		// If the list element type after unification is still the dynamic
282		// type, the only way this can result in a valid list is if all values
283		// are of dynamic type
284		if listEty == cty.DynamicPseudoType {
285			for _, tupleEty := range tupleEtys {
286				if !tupleEty.Equals(cty.DynamicPseudoType) {
287					return nil
288				}
289			}
290		}
291	}
292
293	elemConvs := make([]conversion, len(tupleEtys))
294	for i, tupleEty := range tupleEtys {
295		if tupleEty.Equals(listEty) {
296			// no conversion required
297			continue
298		}
299
300		elemConvs[i] = getConversion(tupleEty, listEty, unsafe)
301		if elemConvs[i] == nil {
302			// If any of our element conversions are impossible, then the our
303			// whole conversion is impossible.
304			return nil
305		}
306	}
307
308	// If we fall out here then a conversion is possible, using the
309	// element conversions in elemConvs
310	return func(val cty.Value, path cty.Path) (cty.Value, error) {
311		elems := make([]cty.Value, 0, len(elemConvs))
312		elemTys := make([]cty.Type, 0, len(elems))
313		elemPath := append(path.Copy(), nil)
314		i := int64(0)
315		it := val.ElementIterator()
316		for it.Next() {
317			_, val := it.Element()
318			var err error
319
320			elemPath[len(elemPath)-1] = cty.IndexStep{
321				Key: cty.NumberIntVal(i),
322			}
323
324			conv := elemConvs[i]
325			if conv != nil {
326				val, err = conv(val, elemPath)
327				if err != nil {
328					return cty.NilVal, err
329				}
330			}
331			elems = append(elems, val)
332			elemTys = append(elemTys, val.Type())
333
334			i++
335		}
336
337		elems, err := conversionUnifyListElements(elems, elemPath, unsafe)
338		if err != nil {
339			return cty.NilVal, err
340		}
341
342		if !cty.CanListVal(elems) {
343			return cty.NilVal, path.NewErrorf("element types must all match for conversion to list")
344		}
345
346		return cty.ListVal(elems), nil
347	}
348}
349
350// conversionObjectToMap returns a conversion that will take a value of the
351// given object type and return a map of the given element type.
352//
353// Will panic if the given objectType isn't actually an object type.
354func conversionObjectToMap(objectType cty.Type, mapEty cty.Type, unsafe bool) conversion {
355	objectAtys := objectType.AttributeTypes()
356
357	if len(objectAtys) == 0 {
358		// Empty object short-circuit
359		return func(val cty.Value, path cty.Path) (cty.Value, error) {
360			return cty.MapValEmpty(mapEty), nil
361		}
362	}
363
364	if mapEty == cty.DynamicPseudoType {
365		// This is a special case where the caller wants us to find
366		// a suitable single type that all elements can convert to, if
367		// possible.
368		objectAtysList := make([]cty.Type, 0, len(objectAtys))
369		for _, aty := range objectAtys {
370			objectAtysList = append(objectAtysList, aty)
371		}
372		mapEty, _ = unify(objectAtysList, unsafe)
373		if mapEty == cty.NilType {
374			return nil
375		}
376	}
377
378	elemConvs := make(map[string]conversion, len(objectAtys))
379	for name, objectAty := range objectAtys {
380		if objectAty.Equals(mapEty) {
381			// no conversion required
382			continue
383		}
384
385		elemConvs[name] = getConversion(objectAty, mapEty, unsafe)
386		if elemConvs[name] == nil {
387			// If any of our element conversions are impossible, then the our
388			// whole conversion is impossible.
389			return nil
390		}
391	}
392
393	// If we fall out here then a conversion is possible, using the
394	// element conversions in elemConvs
395	return func(val cty.Value, path cty.Path) (cty.Value, error) {
396		elems := make(map[string]cty.Value, len(elemConvs))
397		elemPath := append(path.Copy(), nil)
398		it := val.ElementIterator()
399		for it.Next() {
400			name, val := it.Element()
401			var err error
402
403			elemPath[len(elemPath)-1] = cty.IndexStep{
404				Key: name,
405			}
406
407			conv := elemConvs[name.AsString()]
408			if conv != nil {
409				val, err = conv(val, elemPath)
410				if err != nil {
411					return cty.NilVal, err
412				}
413			}
414			elems[name.AsString()] = val
415		}
416
417		if mapEty.IsCollectionType() || mapEty.IsObjectType() {
418			var err error
419			if elems, err = conversionUnifyCollectionElements(elems, path, unsafe); err != nil {
420				return cty.NilVal, err
421			}
422		}
423
424		if !cty.CanMapVal(elems) {
425			return cty.NilVal, path.NewErrorf("attribute types must all match for conversion to map")
426		}
427
428		return cty.MapVal(elems), nil
429	}
430}
431
432// conversionMapToObject returns a conversion that will take a value of the
433// given map type and return an object of the given type. The object attribute
434// types must all be compatible with the map element type.
435//
436// Will panic if the given mapType and objType are not maps and objects
437// respectively.
438func conversionMapToObject(mapType cty.Type, objType cty.Type, unsafe bool) conversion {
439	objectAtys := objType.AttributeTypes()
440	mapEty := mapType.ElementType()
441
442	elemConvs := make(map[string]conversion, len(objectAtys))
443	for name, objectAty := range objectAtys {
444		if objectAty.Equals(mapEty) {
445			// no conversion required
446			continue
447		}
448
449		elemConvs[name] = getConversion(mapEty, objectAty, unsafe)
450		if elemConvs[name] == nil {
451			// If any of our element conversions are impossible, then the our
452			// whole conversion is impossible.
453			return nil
454		}
455	}
456
457	// If we fall out here then a conversion is possible, using the
458	// element conversions in elemConvs
459	return func(val cty.Value, path cty.Path) (cty.Value, error) {
460		elems := make(map[string]cty.Value, len(elemConvs))
461		elemPath := append(path.Copy(), nil)
462		it := val.ElementIterator()
463		for it.Next() {
464			name, val := it.Element()
465
466			// if there is no corresponding attribute, we skip this key
467			if _, ok := objectAtys[name.AsString()]; !ok {
468				continue
469			}
470
471			var err error
472
473			elemPath[len(elemPath)-1] = cty.IndexStep{
474				Key: name,
475			}
476
477			conv := elemConvs[name.AsString()]
478			if conv != nil {
479				val, err = conv(val, elemPath)
480				if err != nil {
481					return cty.NilVal, err
482				}
483			}
484
485			elems[name.AsString()] = val
486		}
487
488		for name, aty := range objectAtys {
489			if _, exists := elems[name]; !exists {
490				if optional := objType.AttributeOptional(name); optional {
491					elems[name] = cty.NullVal(aty)
492				} else {
493					return cty.NilVal, path.NewErrorf("map has no element for required attribute %q", name)
494				}
495			}
496		}
497
498		return cty.ObjectVal(elems), nil
499	}
500}
501
502func conversionUnifyCollectionElements(elems map[string]cty.Value, path cty.Path, unsafe bool) (map[string]cty.Value, error) {
503	elemTypes := make([]cty.Type, 0, len(elems))
504	for _, elem := range elems {
505		elemTypes = append(elemTypes, elem.Type())
506	}
507	unifiedType, _ := unify(elemTypes, unsafe)
508	if unifiedType == cty.NilType {
509		return nil, path.NewErrorf("cannot find a common base type for all elements")
510	}
511
512	unifiedElems := make(map[string]cty.Value)
513	elemPath := append(path.Copy(), nil)
514
515	for name, elem := range elems {
516		if elem.Type().Equals(unifiedType) {
517			unifiedElems[name] = elem
518			continue
519		}
520		conv := getConversion(elem.Type(), unifiedType, unsafe)
521		if conv == nil {
522		}
523		elemPath[len(elemPath)-1] = cty.IndexStep{
524			Key: cty.StringVal(name),
525		}
526		val, err := conv(elem, elemPath)
527		if err != nil {
528			return nil, err
529		}
530		unifiedElems[name] = val
531	}
532
533	return unifiedElems, nil
534}
535
536func conversionUnifyListElements(elems []cty.Value, path cty.Path, unsafe bool) ([]cty.Value, error) {
537	elemTypes := make([]cty.Type, len(elems))
538	for i, elem := range elems {
539		elemTypes[i] = elem.Type()
540	}
541	unifiedType, _ := unify(elemTypes, unsafe)
542	if unifiedType == cty.NilType {
543		return nil, path.NewErrorf("cannot find a common base type for all elements")
544	}
545
546	ret := make([]cty.Value, len(elems))
547	elemPath := append(path.Copy(), nil)
548
549	for i, elem := range elems {
550		if elem.Type().Equals(unifiedType) {
551			ret[i] = elem
552			continue
553		}
554		conv := getConversion(elem.Type(), unifiedType, unsafe)
555		if conv == nil {
556		}
557		elemPath[len(elemPath)-1] = cty.IndexStep{
558			Key: cty.NumberIntVal(int64(i)),
559		}
560		val, err := conv(elem, elemPath)
561		if err != nil {
562			return nil, err
563		}
564		ret[i] = val
565	}
566
567	return ret, nil
568}
569