github.com/terraform-linters/tflint@v0.51.2-0.20240520175844-3750771571b6/terraform/lang/funcs/conversion.go (about) 1 package funcs 2 3 import ( 4 "strconv" 5 6 "github.com/terraform-linters/tflint-plugin-sdk/terraform/lang/marks" 7 "github.com/zclconf/go-cty/cty" 8 "github.com/zclconf/go-cty/cty/convert" 9 "github.com/zclconf/go-cty/cty/function" 10 ) 11 12 // MakeToFunc constructs a "to..." function, like "tostring", which converts 13 // its argument to a specific type or type kind. 14 // 15 // The given type wantTy can be any type constraint that cty's "convert" package 16 // would accept. In particular, this means that you can pass 17 // cty.List(cty.DynamicPseudoType) to mean "list of any single type", which 18 // will then cause cty to attempt to unify all of the element types when given 19 // a tuple. 20 func MakeToFunc(wantTy cty.Type) function.Function { 21 return function.New(&function.Spec{ 22 Params: []function.Parameter{ 23 { 24 Name: "v", 25 // We use DynamicPseudoType rather than wantTy here so that 26 // all values will pass through the function API verbatim and 27 // we can handle the conversion logic within the Type and 28 // Impl functions. This allows us to customize the error 29 // messages to be more appropriate for an explicit type 30 // conversion, whereas the cty function system produces 31 // messages aimed at _implicit_ type conversions. 32 Type: cty.DynamicPseudoType, 33 AllowNull: true, 34 AllowMarked: true, 35 AllowDynamicType: 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 }