1package convert
2
3import (
4	"github.com/zclconf/go-cty/cty"
5)
6
7// conversion is an internal variant of Conversion that carries around
8// a cty.Path to be used in error responses.
9type conversion func(cty.Value, cty.Path) (cty.Value, error)
10
11func getConversion(in cty.Type, out cty.Type, unsafe bool) conversion {
12	conv := getConversionKnown(in, out, unsafe)
13	if conv == nil {
14		return nil
15	}
16
17	// Wrap the conversion in some standard checks that we don't want to
18	// have to repeat in every conversion function.
19	return func(in cty.Value, path cty.Path) (cty.Value, error) {
20		if out == cty.DynamicPseudoType {
21			// Conversion to DynamicPseudoType always just passes through verbatim.
22			return in, nil
23		}
24		if !in.IsKnown() {
25			return cty.UnknownVal(out), nil
26		}
27		if in.IsNull() {
28			// We'll pass through nulls, albeit type converted, and let
29			// the caller deal with whatever handling they want to do in
30			// case null values are considered valid in some applications.
31			return cty.NullVal(out), nil
32		}
33
34		return conv(in, path)
35	}
36}
37
38func getConversionKnown(in cty.Type, out cty.Type, unsafe bool) conversion {
39	switch {
40
41	case out == cty.DynamicPseudoType:
42		// Conversion *to* DynamicPseudoType means that the caller wishes
43		// to allow any type in this position, so we'll produce a do-nothing
44		// conversion that just passes through the value as-is.
45		return dynamicPassthrough
46
47	case unsafe && in == cty.DynamicPseudoType:
48		// Conversion *from* DynamicPseudoType means that we have a value
49		// whose type isn't yet known during type checking. For these we will
50		// assume that conversion will succeed and deal with any errors that
51		// result (which is why we can only do this when "unsafe" is set).
52		return dynamicFixup(out)
53
54	case in.IsPrimitiveType() && out.IsPrimitiveType():
55		conv := primitiveConversionsSafe[in][out]
56		if conv != nil {
57			return conv
58		}
59		if unsafe {
60			return primitiveConversionsUnsafe[in][out]
61		}
62		return nil
63
64	case out.IsObjectType() && in.IsObjectType():
65		return conversionObjectToObject(in, out, unsafe)
66
67	case out.IsTupleType() && in.IsTupleType():
68		return conversionTupleToTuple(in, out, unsafe)
69
70	case out.IsListType() && (in.IsListType() || in.IsSetType()):
71		inEty := in.ElementType()
72		outEty := out.ElementType()
73		if inEty.Equals(outEty) {
74			// This indicates that we're converting from list to set with
75			// the same element type, so we don't need an element converter.
76			return conversionCollectionToList(outEty, nil)
77		}
78
79		convEty := getConversion(inEty, outEty, unsafe)
80		if convEty == nil {
81			return nil
82		}
83		return conversionCollectionToList(outEty, convEty)
84
85	case out.IsSetType() && (in.IsListType() || in.IsSetType()):
86		if in.IsListType() && !unsafe {
87			// Conversion from list to map is unsafe because it will lose
88			// information: the ordering will not be preserved, and any
89			// duplicate elements will be conflated.
90			return nil
91		}
92		inEty := in.ElementType()
93		outEty := out.ElementType()
94		convEty := getConversion(inEty, outEty, unsafe)
95		if inEty.Equals(outEty) {
96			// This indicates that we're converting from set to list with
97			// the same element type, so we don't need an element converter.
98			return conversionCollectionToSet(outEty, nil)
99		}
100
101		if convEty == nil {
102			return nil
103		}
104		return conversionCollectionToSet(outEty, convEty)
105
106	case out.IsMapType() && in.IsMapType():
107		inEty := in.ElementType()
108		outEty := out.ElementType()
109		convEty := getConversion(inEty, outEty, unsafe)
110		if convEty == nil {
111			return nil
112		}
113		return conversionCollectionToMap(outEty, convEty)
114
115	case out.IsListType() && in.IsTupleType():
116		outEty := out.ElementType()
117		return conversionTupleToList(in, outEty, unsafe)
118
119	case out.IsSetType() && in.IsTupleType():
120		outEty := out.ElementType()
121		return conversionTupleToSet(in, outEty, unsafe)
122
123	case out.IsMapType() && in.IsObjectType():
124		outEty := out.ElementType()
125		return conversionObjectToMap(in, outEty, unsafe)
126
127	default:
128		return nil
129
130	}
131}
132
133// retConversion wraps a conversion (internal type) so it can be returned
134// as a Conversion (public type).
135func retConversion(conv conversion) Conversion {
136	if conv == nil {
137		return nil
138	}
139
140	return func(in cty.Value) (cty.Value, error) {
141		return conv(in, cty.Path(nil))
142	}
143}
144