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  }