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