forked from hashicorp/terraform-plugin-framework
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathexpression.go
277 lines (225 loc) · 8.94 KB
/
expression.go
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
package path
import (
"github.com/hashicorp/terraform-plugin-framework/attr"
)
// Expression represents an attribute path with expression steps, which can
// represent zero, one, or more actual paths in schema data. This logic is
// either based on an absolute path starting at the root of the schema data,
// similar to Path, or a relative path which is intended to be merged with an
// existing absolute path.
//
// Use the MatchRoot() function to create an Expression for an absolute path
// with an initial AtName() step. Use the MatchRelative() function to create
// an Expression for a relative path, which will be merged with an existing
// absolute path.
//
// Similar to Path, Expression functionality has some overlapping method names
// and follows a builder pattern, which allows for chaining method calls to
// construct a full expression. The available traversal steps after Expression
// creation are:
//
// - AtAnyListIndex(): Step into a list at any index
// - AtAnyMapKey(): Step into a map at any key
// - AtAnySetValue(): Step into a set at any attr.Value element
// - AtListIndex(): Step into a list at a specific index
// - AtMapKey(): Step into a map at a specific key
// - AtName(): Step into an attribute or block with a specific name
// - AtParent(): Step backwards one step
// - AtSetValue(): Step into a set at a specific attr.Value element
//
// For example, to express any list element with a root list attribute named
// "some_attribute":
//
// path.MatchRoot("some_attribute").AtAnyListIndex()
//
// An Expression is generally preferable over a Path in schema-defined
// functionality that is intended to accept paths as parameters, such as
// attribute validators and attribute plan modifiers, since it allows consumers
// to support relative paths. Use the Merge() or MergeExpressions() method to
// combine the current attribute path expression with those expression(s).
//
// To find Paths from an Expression in schema based data structures, such as
// tfsdk.Config, tfsdk.Plan, and tfsdk.State, use their PathMatches() method.
type Expression struct {
// root stores whether an expression was intentionally created to start
// from the root of the data. This is used with Merge to overwrite steps
// instead of appending steps.
root bool
// steps is the transversals included with the expression. In general,
// operations against the path should protect against modification of the
// original.
steps ExpressionSteps
}
// AtAnyListIndex returns a copied expression with a new list index step at the
// end. The returned path is safe to modify without affecting the original.
func (e Expression) AtAnyListIndex() Expression {
copiedPath := e.Copy()
copiedPath.steps.Append(ExpressionStepElementKeyIntAny{})
return copiedPath
}
// AtAnyMapKey returns a copied expression with a new map key step at the end.
// The returned path is safe to modify without affecting the original.
func (e Expression) AtAnyMapKey() Expression {
copiedPath := e.Copy()
copiedPath.steps.Append(ExpressionStepElementKeyStringAny{})
return copiedPath
}
// AtAnySetValue returns a copied expression with a new set value step at the
// end. The returned path is safe to modify without affecting the original.
func (e Expression) AtAnySetValue() Expression {
copiedPath := e.Copy()
copiedPath.steps.Append(ExpressionStepElementKeyValueAny{})
return copiedPath
}
// AtListIndex returns a copied expression with a new list index step at the
// end. The returned path is safe to modify without affecting the original.
func (e Expression) AtListIndex(index int) Expression {
copiedPath := e.Copy()
copiedPath.steps.Append(ExpressionStepElementKeyIntExact(index))
return copiedPath
}
// AtMapKey returns a copied expression with a new map key step at the end.
// The returned path is safe to modify without affecting the original.
func (e Expression) AtMapKey(key string) Expression {
copiedPath := e.Copy()
copiedPath.steps.Append(ExpressionStepElementKeyStringExact(key))
return copiedPath
}
// AtName returns a copied expression with a new attribute or block name step
// at the end. The returned path is safe to modify without affecting the
// original.
func (e Expression) AtName(name string) Expression {
copiedPath := e.Copy()
copiedPath.steps.Append(ExpressionStepAttributeNameExact(name))
return copiedPath
}
// AtParent returns a copied expression with a new parent step at the end.
// The returned path is safe to modify without affecting the original.
func (e Expression) AtParent() Expression {
copiedPath := e.Copy()
copiedPath.steps.Append(ExpressionStepParent{})
return copiedPath
}
// AtSetValue returns a copied expression with a new set value step at the end.
// The returned path is safe to modify without affecting the original.
func (e Expression) AtSetValue(value attr.Value) Expression {
copiedPath := e.Copy()
copiedPath.steps.Append(ExpressionStepElementKeyValueExact{Value: value})
return copiedPath
}
// Copy returns a duplicate of the expression that is safe to modify without
// affecting the original.
func (e Expression) Copy() Expression {
return Expression{
root: e.root,
steps: e.Steps().Copy(),
}
}
// Equal returns true if the given expression is exactly equivalent.
func (e Expression) Equal(o Expression) bool {
if e.root != o.root {
return false
}
if e.steps == nil && o.steps == nil {
return true
}
if e.steps == nil {
return false
}
if !e.steps.Equal(o.steps) {
return false
}
return true
}
// Matches returns true if the given Path is valid for the Expression. Any
// relative expression steps, such as ExpressionStepParent, are automatically
// resolved before matching.
func (e Expression) Matches(path Path) bool {
return e.steps.Matches(path.Steps())
}
// MatchesParent returns true if the given Path is a valid parent for the
// Expression. This is helpful for determining if a child Path would
// potentially match the full Expression during depth-first traversal. Any
// relative expression steps, such as ExpressionStepParent, are automatically
// resolved before matching.
func (e Expression) MatchesParent(path Path) bool {
return e.steps.MatchesParent(path.Steps())
}
// Merge returns a copied expression either with the steps of the given
// expression added to the end of the existing steps, or overwriting the
// steps if the given expression was a root expression.
//
// Any merged expressions will preserve all expressions steps, such as
// ExpressionStepParent, for troubleshooting. Methods such as Matches() will
// automatically resolve the expression when using it. Call the Resolve()
// method explicitly if a resolved expression without any ExpressionStepParent
// is desired.
func (e Expression) Merge(other Expression) Expression {
if other.root {
return other.Copy()
}
copiedExpression := e.Copy()
copiedExpression.steps.Append(other.steps...)
return copiedExpression
}
// MergeExpressions returns collection of expressions that calls Merge() on
// the current expression with each of the others.
//
// If no Expression are given, then it will return a collection of expressions
// containing only the current expression.
func (e Expression) MergeExpressions(others ...Expression) Expressions {
var result Expressions
if len(others) == 0 {
result.Append(e)
return result
}
for _, other := range others {
result.Append(e.Merge(other))
}
return result
}
// Resolve returns a copied expression with any relative steps, such as
// ExpressionStepParent, resolved. This is not necessary before calling methods
// such as Matches(), however it can be useful before returning the String()
// method so the path information is simplified.
//
// Returns an empty expression if any ExpressionStepParent attempt to go
// beyond the first element.
func (e Expression) Resolve() Expression {
copiedExpression := e.Copy()
copiedExpression.steps = copiedExpression.steps.Resolve()
return copiedExpression
}
// Steps returns a copy of the underlying expression steps. Returns an empty
// collection of steps if expression is nil.
func (e Expression) Steps() ExpressionSteps {
if len(e.steps) == 0 {
return ExpressionSteps{}
}
return e.steps.Copy()
}
// String returns the human-readable representation of the path.
// It is intended for logging and error messages and is not protected by
// compatibility guarantees.
func (e Expression) String() string {
return e.steps.String()
}
// MatchRelative creates an empty attribute path expression that is intended
// to be combined with an existing attribute path expression. This allows
// creating a relative expression in nested schemas, using AtParent() to
// traverse up the path or other At methods to traverse further down.
func MatchRelative() Expression {
return Expression{
steps: ExpressionSteps{},
}
}
// MatchRoot creates an attribute path expression starting with
// ExpressionStepAttributeNameExact.
func MatchRoot(rootAttributeName string) Expression {
return Expression{
root: true,
steps: ExpressionSteps{
ExpressionStepAttributeNameExact(rootAttributeName),
},
}
}