147 lines
5.2 KiB
Go
147 lines
5.2 KiB
Go
package customdecode
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
// ExpressionType is a cty capsule type that carries hcl.Expression values.
|
|
//
|
|
// This type implements custom decoding in the most general way possible: it
|
|
// just captures whatever expression is given to it, with no further processing
|
|
// whatsoever. It could therefore be useful in situations where an application
|
|
// must defer processing of the expression content until a later step.
|
|
//
|
|
// ExpressionType only captures the expression, not the evaluation context it
|
|
// was destined to be evaluated in. That means this type can be fine for
|
|
// situations where the recipient of the value only intends to do static
|
|
// analysis, but ExpressionClosureType is more appropriate in situations where
|
|
// the recipient will eventually evaluate the given expression.
|
|
var ExpressionType cty.Type
|
|
|
|
// ExpressionVal returns a new cty value of type ExpressionType, wrapping the
|
|
// given expression.
|
|
func ExpressionVal(expr hcl.Expression) cty.Value {
|
|
return cty.CapsuleVal(ExpressionType, &expr)
|
|
}
|
|
|
|
// ExpressionFromVal returns the expression encapsulated in the given value, or
|
|
// panics if the value is not a known value of ExpressionType.
|
|
func ExpressionFromVal(v cty.Value) hcl.Expression {
|
|
if !v.Type().Equals(ExpressionType) {
|
|
panic("value is not of ExpressionType")
|
|
}
|
|
ptr := v.EncapsulatedValue().(*hcl.Expression)
|
|
return *ptr
|
|
}
|
|
|
|
// ExpressionClosureType is a cty capsule type that carries hcl.Expression
|
|
// values along with their original evaluation contexts.
|
|
//
|
|
// This is similar to ExpressionType except that during custom decoding it
|
|
// also captures the hcl.EvalContext that was provided, allowing callers to
|
|
// evaluate the expression later in the same context where it would originally
|
|
// have been evaluated, or a context derived from that one.
|
|
var ExpressionClosureType cty.Type
|
|
|
|
// ExpressionClosure is the type encapsulated in ExpressionClosureType
|
|
type ExpressionClosure struct {
|
|
Expression hcl.Expression
|
|
EvalContext *hcl.EvalContext
|
|
}
|
|
|
|
// ExpressionClosureVal returns a new cty value of type ExpressionClosureType,
|
|
// wrapping the given expression closure.
|
|
func ExpressionClosureVal(closure *ExpressionClosure) cty.Value {
|
|
return cty.CapsuleVal(ExpressionClosureType, closure)
|
|
}
|
|
|
|
// Value evaluates the closure's expression using the closure's EvalContext,
|
|
// returning the result.
|
|
func (c *ExpressionClosure) Value() (cty.Value, hcl.Diagnostics) {
|
|
return c.Expression.Value(c.EvalContext)
|
|
}
|
|
|
|
// ExpressionClosureFromVal returns the expression closure encapsulated in the
|
|
// given value, or panics if the value is not a known value of
|
|
// ExpressionClosureType.
|
|
//
|
|
// The caller MUST NOT modify the returned closure or the EvalContext inside
|
|
// it. To derive a new EvalContext, either create a child context or make
|
|
// a copy.
|
|
func ExpressionClosureFromVal(v cty.Value) *ExpressionClosure {
|
|
if !v.Type().Equals(ExpressionClosureType) {
|
|
panic("value is not of ExpressionClosureType")
|
|
}
|
|
return v.EncapsulatedValue().(*ExpressionClosure)
|
|
}
|
|
|
|
func init() {
|
|
// Getting hold of a reflect.Type for hcl.Expression is a bit tricky because
|
|
// it's an interface type, but we can do it with some indirection.
|
|
goExpressionType := reflect.TypeOf((*hcl.Expression)(nil)).Elem()
|
|
|
|
ExpressionType = cty.CapsuleWithOps("expression", goExpressionType, &cty.CapsuleOps{
|
|
ExtensionData: func(key interface{}) interface{} {
|
|
switch key {
|
|
case CustomExpressionDecoder:
|
|
return CustomExpressionDecoderFunc(
|
|
func(expr hcl.Expression, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
return ExpressionVal(expr), nil
|
|
},
|
|
)
|
|
default:
|
|
return nil
|
|
}
|
|
},
|
|
TypeGoString: func(_ reflect.Type) string {
|
|
return "customdecode.ExpressionType"
|
|
},
|
|
GoString: func(raw interface{}) string {
|
|
exprPtr := raw.(*hcl.Expression)
|
|
return fmt.Sprintf("customdecode.ExpressionVal(%#v)", *exprPtr)
|
|
},
|
|
RawEquals: func(a, b interface{}) bool {
|
|
aPtr := a.(*hcl.Expression)
|
|
bPtr := b.(*hcl.Expression)
|
|
return reflect.DeepEqual(*aPtr, *bPtr)
|
|
},
|
|
})
|
|
ExpressionClosureType = cty.CapsuleWithOps("expression closure", reflect.TypeOf(ExpressionClosure{}), &cty.CapsuleOps{
|
|
ExtensionData: func(key interface{}) interface{} {
|
|
switch key {
|
|
case CustomExpressionDecoder:
|
|
return CustomExpressionDecoderFunc(
|
|
func(expr hcl.Expression, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
return ExpressionClosureVal(&ExpressionClosure{
|
|
Expression: expr,
|
|
EvalContext: ctx,
|
|
}), nil
|
|
},
|
|
)
|
|
default:
|
|
return nil
|
|
}
|
|
},
|
|
TypeGoString: func(_ reflect.Type) string {
|
|
return "customdecode.ExpressionClosureType"
|
|
},
|
|
GoString: func(raw interface{}) string {
|
|
closure := raw.(*ExpressionClosure)
|
|
return fmt.Sprintf("customdecode.ExpressionClosureVal(%#v)", closure)
|
|
},
|
|
RawEquals: func(a, b interface{}) bool {
|
|
closureA := a.(*ExpressionClosure)
|
|
closureB := b.(*ExpressionClosure)
|
|
// The expression itself compares by deep equality, but EvalContexts
|
|
// conventionally compare by pointer identity, so we'll comply
|
|
// with both conventions here by testing them separately.
|
|
return closureA.EvalContext == closureB.EvalContext &&
|
|
reflect.DeepEqual(closureA.Expression, closureB.Expression)
|
|
},
|
|
})
|
|
}
|