github.com/hashicorp/terraform-plugin-sdk@v1.17.2/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  			},
    34  		},
    35  		Type: func(args []cty.Value) (cty.Type, error) {
    36  			gotTy := args[0].Type()
    37  			if gotTy.Equals(wantTy) {
    38  				return wantTy, nil
    39  			}
    40  			conv := convert.GetConversionUnsafe(args[0].Type(), wantTy)
    41  			if conv == nil {
    42  				// We'll use some specialized errors for some trickier cases,
    43  				// but most we can handle in a simple way.
    44  				switch {
    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))
    49  				default:
    50  					return cty.NilType, function.NewArgErrorf(0, "cannot convert %s to %s", gotTy.FriendlyName(), wantTy.FriendlyNameForConstraint())
    51  				}
    52  			}
    53  			// If a conversion is available then everything is fine.
    54  			return wantTy, nil
    55  		},
    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)
    60  			if err != nil {
    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()
    67  				switch {
    68  				case gotTy == cty.String && wantTy == cty.Bool:
    69  					what := "string"
    70  					if !args[0].IsNull() {
    71  						what = strconv.Quote(args[0].AsString())
    72  					}
    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:
    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 number; given string must be a decimal representation of a number`, what)
    80  				default:
    81  					return cty.NilVal, function.NewArgErrorf(0, "cannot convert %s to %s", gotTy.FriendlyName(), wantTy.FriendlyNameForConstraint())
    82  				}
    83  			}
    84  			return ret, nil
    85  		},
    86  	})
    87  }