119 lines
3.6 KiB
Go
119 lines
3.6 KiB
Go
package typeexpr
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/hcl/v2/ext/customdecode"
|
|
"github.com/zclconf/go-cty/cty"
|
|
"github.com/zclconf/go-cty/cty/convert"
|
|
"github.com/zclconf/go-cty/cty/function"
|
|
)
|
|
|
|
// TypeConstraintType is a cty capsule type that allows cty type constraints to
|
|
// be used as values.
|
|
//
|
|
// If TypeConstraintType is used in a context supporting the
|
|
// customdecode.CustomExpressionDecoder extension then it will implement
|
|
// expression decoding using the TypeConstraint function, thus allowing
|
|
// type expressions to be used in contexts where value expressions might
|
|
// normally be expected, such as in arguments to function calls.
|
|
var TypeConstraintType cty.Type
|
|
|
|
// TypeConstraintVal constructs a cty.Value whose type is
|
|
// TypeConstraintType.
|
|
func TypeConstraintVal(ty cty.Type) cty.Value {
|
|
return cty.CapsuleVal(TypeConstraintType, &ty)
|
|
}
|
|
|
|
// TypeConstraintFromVal extracts the type from a cty.Value of
|
|
// TypeConstraintType that was previously constructed using TypeConstraintVal.
|
|
//
|
|
// If the given value isn't a known, non-null value of TypeConstraintType
|
|
// then this function will panic.
|
|
func TypeConstraintFromVal(v cty.Value) cty.Type {
|
|
if !v.Type().Equals(TypeConstraintType) {
|
|
panic("value is not of TypeConstraintType")
|
|
}
|
|
ptr := v.EncapsulatedValue().(*cty.Type)
|
|
return *ptr
|
|
}
|
|
|
|
// ConvertFunc is a cty function that implements type conversions.
|
|
//
|
|
// Its signature is as follows:
|
|
// convert(value, type_constraint)
|
|
//
|
|
// ...where type_constraint is a type constraint expression as defined by
|
|
// typeexpr.TypeConstraint.
|
|
//
|
|
// It relies on HCL's customdecode extension and so it's not suitable for use
|
|
// in non-HCL contexts or if you are using a HCL syntax implementation that
|
|
// does not support customdecode for function arguments. However, it _is_
|
|
// supported for function calls in the HCL native expression syntax.
|
|
var ConvertFunc function.Function
|
|
|
|
func init() {
|
|
TypeConstraintType = cty.CapsuleWithOps("type constraint", reflect.TypeOf(cty.Type{}), &cty.CapsuleOps{
|
|
ExtensionData: func(key interface{}) interface{} {
|
|
switch key {
|
|
case customdecode.CustomExpressionDecoder:
|
|
return customdecode.CustomExpressionDecoderFunc(
|
|
func(expr hcl.Expression, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
ty, diags := TypeConstraint(expr)
|
|
if diags.HasErrors() {
|
|
return cty.NilVal, diags
|
|
}
|
|
return TypeConstraintVal(ty), nil
|
|
},
|
|
)
|
|
default:
|
|
return nil
|
|
}
|
|
},
|
|
TypeGoString: func(_ reflect.Type) string {
|
|
return "typeexpr.TypeConstraintType"
|
|
},
|
|
GoString: func(raw interface{}) string {
|
|
tyPtr := raw.(*cty.Type)
|
|
return fmt.Sprintf("typeexpr.TypeConstraintVal(%#v)", *tyPtr)
|
|
},
|
|
RawEquals: func(a, b interface{}) bool {
|
|
aPtr := a.(*cty.Type)
|
|
bPtr := b.(*cty.Type)
|
|
return (*aPtr).Equals(*bPtr)
|
|
},
|
|
})
|
|
|
|
ConvertFunc = function.New(&function.Spec{
|
|
Params: []function.Parameter{
|
|
{
|
|
Name: "value",
|
|
Type: cty.DynamicPseudoType,
|
|
AllowNull: true,
|
|
AllowDynamicType: true,
|
|
},
|
|
{
|
|
Name: "type",
|
|
Type: TypeConstraintType,
|
|
},
|
|
},
|
|
Type: func(args []cty.Value) (cty.Type, error) {
|
|
wantTypePtr := args[1].EncapsulatedValue().(*cty.Type)
|
|
got, err := convert.Convert(args[0], *wantTypePtr)
|
|
if err != nil {
|
|
return cty.NilType, function.NewArgError(0, err)
|
|
}
|
|
return got.Type(), nil
|
|
},
|
|
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
|
v, err := convert.Convert(args[0], retType)
|
|
if err != nil {
|
|
return cty.NilVal, function.NewArgError(0, err)
|
|
}
|
|
return v, nil
|
|
},
|
|
})
|
|
}
|