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