6 "github.com/zclconf/go-cty/cty"
7 "github.com/zclconf/go-cty/cty/convert"
8 "github.com/zclconf/go-cty/cty/function"
11 // MakeToFunc constructs a "to..." function, like "tostring", which converts
12 // its argument to a specific type or type kind.
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
19 func MakeToFunc(wantTy cty.Type) function.Function {
20 return function.New(&function.Spec{
21 Params: []function.Parameter{
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,
35 Type: func(args []cty.Value) (cty.Type, error) {
36 gotTy := args[0].Type()
37 if gotTy.Equals(wantTy) {
40 conv := convert.GetConversionUnsafe(args[0].Type(), wantTy)
42 // We'll use some specialized errors for some trickier cases,
43 // but most we can handle in a simple way.
45 case gotTy.IsTupleType() && wantTy.IsTupleType():
46 return cty.NilType, function.NewArgErrorf(0, "incompatible tuple type for conversion: %s", convert.MismatchMessage(gotTy, wantTy))
47 case gotTy.IsObjectType() && wantTy.IsObjectType():
48 return cty.NilType, function.NewArgErrorf(0, "incompatible object type for conversion: %s", convert.MismatchMessage(gotTy, wantTy))
50 return cty.NilType, function.NewArgErrorf(0, "cannot convert %s to %s", gotTy.FriendlyName(), wantTy.FriendlyNameForConstraint())
53 // If a conversion is available then everything is fine.
56 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
57 // We didn't set "AllowUnknown" on our argument, so it is guaranteed
58 // to be known here but may still be null.
59 ret, err := convert.Convert(args[0], retType)
61 // Because we used GetConversionUnsafe above, conversion can
62 // still potentially fail in here. For example, if the user
63 // asks to convert the string "a" to bool then we'll
64 // optimistically permit it during type checking but fail here
65 // once we note that the value isn't either "true" or "false".
66 gotTy := args[0].Type()
68 case gotTy == cty.String && wantTy == cty.Bool:
70 if !args[0].IsNull() {
71 what = strconv.Quote(args[0].AsString())
73 return cty.NilVal, function.NewArgErrorf(0, `cannot convert %s to bool; only the strings "true" or "false" are allowed`, what)
74 case gotTy == cty.String && wantTy == cty.Number:
76 if !args[0].IsNull() {
77 what = strconv.Quote(args[0].AsString())
79 return cty.NilVal, function.NewArgErrorf(0, `cannot convert %s to number; given string must be a decimal representation of a number`, what)
81 return cty.NilVal, function.NewArgErrorf(0, "cannot convert %s to %s", gotTy.FriendlyName(), wantTy.FriendlyNameForConstraint())