github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/lang/funcs/conversion.go (about) 1 package funcs 2 3 import ( 4 "strconv" 5 6 "github.com/cycloidio/terraform/lang/marks" 7 "github.com/cycloidio/terraform/lang/types" 8 "github.com/zclconf/go-cty/cty" 9 "github.com/zclconf/go-cty/cty/convert" 10 "github.com/zclconf/go-cty/cty/function" 11 ) 12 13 // MakeToFunc constructs a "to..." function, like "tostring", which converts 14 // its argument to a specific type or type kind. 15 // 16 // The given type wantTy can be any type constraint that cty's "convert" package 17 // would accept. In particular, this means that you can pass 18 // cty.List(cty.DynamicPseudoType) to mean "list of any single type", which 19 // will then cause cty to attempt to unify all of the element types when given 20 // a tuple. 21 func MakeToFunc(wantTy cty.Type) function.Function { 22 return function.New(&function.Spec{ 23 Params: []function.Parameter{ 24 { 25 Name: "v", 26 // We use DynamicPseudoType rather than wantTy here so that 27 // all values will pass through the function API verbatim and 28 // we can handle the conversion logic within the Type and 29 // Impl functions. This allows us to customize the error 30 // messages to be more appropriate for an explicit type 31 // conversion, whereas the cty function system produces 32 // messages aimed at _implicit_ type conversions. 33 Type: cty.DynamicPseudoType, 34 AllowNull: true, 35 AllowMarked: true, 36 }, 37 }, 38 Type: func(args []cty.Value) (cty.Type, error) { 39 gotTy := args[0].Type() 40 if gotTy.Equals(wantTy) { 41 return wantTy, nil 42 } 43 conv := convert.GetConversionUnsafe(args[0].Type(), wantTy) 44 if conv == nil { 45 // We'll use some specialized errors for some trickier cases, 46 // but most we can handle in a simple way. 47 switch { 48 case gotTy.IsTupleType() && wantTy.IsTupleType(): 49 return cty.NilType, function.NewArgErrorf(0, "incompatible tuple type for conversion: %s", convert.MismatchMessage(gotTy, wantTy)) 50 case gotTy.IsObjectType() && wantTy.IsObjectType(): 51 return cty.NilType, function.NewArgErrorf(0, "incompatible object type for conversion: %s", convert.MismatchMessage(gotTy, wantTy)) 52 default: 53 return cty.NilType, function.NewArgErrorf(0, "cannot convert %s to %s", gotTy.FriendlyName(), wantTy.FriendlyNameForConstraint()) 54 } 55 } 56 // If a conversion is available then everything is fine. 57 return wantTy, nil 58 }, 59 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 60 // We didn't set "AllowUnknown" on our argument, so it is guaranteed 61 // to be known here but may still be null. 62 ret, err := convert.Convert(args[0], retType) 63 if err != nil { 64 val, _ := args[0].UnmarkDeep() 65 // Because we used GetConversionUnsafe above, conversion can 66 // still potentially fail in here. For example, if the user 67 // asks to convert the string "a" to bool then we'll 68 // optimistically permit it during type checking but fail here 69 // once we note that the value isn't either "true" or "false". 70 gotTy := val.Type() 71 switch { 72 case marks.Contains(args[0], marks.Sensitive): 73 // Generic message so we won't inadvertently disclose 74 // information about sensitive values. 75 return cty.NilVal, function.NewArgErrorf(0, "cannot convert this sensitive %s to %s", gotTy.FriendlyName(), wantTy.FriendlyNameForConstraint()) 76 77 case gotTy == cty.String && wantTy == cty.Bool: 78 what := "string" 79 if !val.IsNull() { 80 what = strconv.Quote(val.AsString()) 81 } 82 return cty.NilVal, function.NewArgErrorf(0, `cannot convert %s to bool; only the strings "true" or "false" are allowed`, what) 83 case gotTy == cty.String && wantTy == cty.Number: 84 what := "string" 85 if !val.IsNull() { 86 what = strconv.Quote(val.AsString()) 87 } 88 return cty.NilVal, function.NewArgErrorf(0, `cannot convert %s to number; given string must be a decimal representation of a number`, what) 89 default: 90 return cty.NilVal, function.NewArgErrorf(0, "cannot convert %s to %s", gotTy.FriendlyName(), wantTy.FriendlyNameForConstraint()) 91 } 92 } 93 return ret, nil 94 }, 95 }) 96 } 97 98 // TypeFunc returns an encapsulated value containing its argument's type. This 99 // value is marked to allow us to limit the use of this function at the moment 100 // to only a few supported use cases. 101 var TypeFunc = function.New(&function.Spec{ 102 Params: []function.Parameter{ 103 { 104 Name: "value", 105 Type: cty.DynamicPseudoType, 106 AllowDynamicType: true, 107 AllowUnknown: true, 108 AllowNull: true, 109 }, 110 }, 111 Type: function.StaticReturnType(types.TypeType), 112 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 113 givenType := args[0].Type() 114 return cty.CapsuleVal(types.TypeType, &givenType).Mark(marks.TypeType), nil 115 }, 116 }) 117 118 func Type(input []cty.Value) (cty.Value, error) { 119 return TypeFunc.Call(input) 120 }