1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
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)
},
})
}
|