| 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 |
| }, |
| }) |
| } |