aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/zclconf/go-cty/cty/convert/conversion.go
blob: f9aacb4ee774e4ce22b3cb049022df06c5973cc7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package convert

import (
	"github.com/zclconf/go-cty/cty"
)

// conversion is an internal variant of Conversion that carries around
// a cty.Path to be used in error responses.
type conversion func(cty.Value, cty.Path) (cty.Value, error)

func getConversion(in cty.Type, out cty.Type, unsafe bool) conversion {
	conv := getConversionKnown(in, out, unsafe)
	if conv == nil {
		return nil
	}

	// Wrap the conversion in some standard checks that we don't want to
	// have to repeat in every conversion function.
	return func(in cty.Value, path cty.Path) (cty.Value, error) {
		if out == cty.DynamicPseudoType {
			// Conversion to DynamicPseudoType always just passes through verbatim.
			return in, nil
		}
		if !in.IsKnown() {
			return cty.UnknownVal(out), nil
		}
		if in.IsNull() {
			// We'll pass through nulls, albeit type converted, and let
			// the caller deal with whatever handling they want to do in
			// case null values are considered valid in some applications.
			return cty.NullVal(out), nil
		}

		return conv(in, path)
	}
}

func getConversionKnown(in cty.Type, out cty.Type, unsafe bool) conversion {
	switch {

	case out == cty.DynamicPseudoType:
		// Conversion *to* DynamicPseudoType means that the caller wishes
		// to allow any type in this position, so we'll produce a do-nothing
		// conversion that just passes through the value as-is.
		return dynamicPassthrough

	case unsafe && in == cty.DynamicPseudoType:
		// Conversion *from* DynamicPseudoType means that we have a value
		// whose type isn't yet known during type checking. For these we will
		// assume that conversion will succeed and deal with any errors that
		// result (which is why we can only do this when "unsafe" is set).
		return dynamicFixup(out)

	case in.IsPrimitiveType() && out.IsPrimitiveType():
		conv := primitiveConversionsSafe[in][out]
		if conv != nil {
			return conv
		}
		if unsafe {
			return primitiveConversionsUnsafe[in][out]
		}
		return nil

	case out.IsObjectType() && in.IsObjectType():
		return conversionObjectToObject(in, out, unsafe)

	case out.IsTupleType() && in.IsTupleType():
		return conversionTupleToTuple(in, out, unsafe)

	case out.IsListType() && (in.IsListType() || in.IsSetType()):
		inEty := in.ElementType()
		outEty := out.ElementType()
		if inEty.Equals(outEty) {
			// This indicates that we're converting from list to set with
			// the same element type, so we don't need an element converter.
			return conversionCollectionToList(outEty, nil)
		}

		convEty := getConversion(inEty, outEty, unsafe)
		if convEty == nil {
			return nil
		}
		return conversionCollectionToList(outEty, convEty)

	case out.IsSetType() && (in.IsListType() || in.IsSetType()):
		if in.IsListType() && !unsafe {
			// Conversion from list to map is unsafe because it will lose
			// information: the ordering will not be preserved, and any
			// duplicate elements will be conflated.
			return nil
		}
		inEty := in.ElementType()
		outEty := out.ElementType()
		convEty := getConversion(inEty, outEty, unsafe)
		if inEty.Equals(outEty) {
			// This indicates that we're converting from set to list with
			// the same element type, so we don't need an element converter.
			return conversionCollectionToSet(outEty, nil)
		}

		if convEty == nil {
			return nil
		}
		return conversionCollectionToSet(outEty, convEty)

	case out.IsMapType() && in.IsMapType():
		inEty := in.ElementType()
		outEty := out.ElementType()
		convEty := getConversion(inEty, outEty, unsafe)
		if convEty == nil {
			return nil
		}
		return conversionCollectionToMap(outEty, convEty)

	case out.IsListType() && in.IsTupleType():
		outEty := out.ElementType()
		return conversionTupleToList(in, outEty, unsafe)

	case out.IsSetType() && in.IsTupleType():
		outEty := out.ElementType()
		return conversionTupleToSet(in, outEty, unsafe)

	case out.IsMapType() && in.IsObjectType():
		outEty := out.ElementType()
		return conversionObjectToMap(in, outEty, unsafe)

	default:
		return nil

	}
}

// retConversion wraps a conversion (internal type) so it can be returned
// as a Conversion (public type).
func retConversion(conv conversion) Conversion {
	if conv == nil {
		return nil
	}

	return func(in cty.Value) (cty.Value, error) {
		return conv(in, cty.Path(nil))
	}
}