Skip to content

Commit d55e48d

Browse files
committed
tftypes: Support AttributePathStepper interface in Type
Reference: #110 This will allow implementations to step into `Type` via `AttributePath` similar to `Value`, which can simplify data handling logic.
1 parent 8da41e2 commit d55e48d

16 files changed

+763
-15
lines changed

.changelog/pending.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
tftypes: Added `Type` support to `WalkAttributePath()` function
3+
```

tftypes/attribute_path.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -284,9 +284,9 @@ type AttributePathStepper interface {
284284
ApplyTerraform5AttributePathStep(AttributePathStep) (interface{}, error)
285285
}
286286

287-
// WalkAttributePath will return the value that `path` is pointing to, using
288-
// `in` as the root. If an error is returned, the AttributePath returned will
289-
// indicate the steps that remained to be applied when the error was
287+
// WalkAttributePath will return the Type or Value that `path` is pointing to,
288+
// using `in` as the root. If an error is returned, the AttributePath returned
289+
// will indicate the steps that remained to be applied when the error was
290290
// encountered.
291291
//
292292
// map[string]interface{} and []interface{} types have built-in support. Other

tftypes/attribute_path_test.go

+179-7
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ func (a attributePathStepperTestSlice) ApplyTerraform5AttributePathStep(step Att
4343
func TestWalkAttributePath(t *testing.T) {
4444
t.Parallel()
4545
type testCase struct {
46-
value interface{}
46+
in interface{}
4747
path *AttributePath
4848
expected interface{}
4949
}
5050
tests := map[string]testCase{
5151
"msi-root": {
52-
value: map[string]interface{}{
52+
in: map[string]interface{}{
5353
"a": map[string]interface{}{
5454
"red": true,
5555
"blue": 123,
@@ -70,7 +70,7 @@ func TestWalkAttributePath(t *testing.T) {
7070
},
7171
},
7272
"msi-full": {
73-
value: map[string]interface{}{
73+
in: map[string]interface{}{
7474
"a": map[string]interface{}{
7575
"red": true,
7676
"blue": 123,
@@ -88,8 +88,180 @@ func TestWalkAttributePath(t *testing.T) {
8888
},
8989
expected: true,
9090
},
91+
"Object-AttributeName-Bool": {
92+
in: Object{
93+
AttributeTypes: map[string]Type{
94+
"other": String,
95+
"test": Bool,
96+
},
97+
},
98+
path: &AttributePath{
99+
steps: []AttributePathStep{
100+
AttributeName("test"),
101+
},
102+
},
103+
expected: Bool,
104+
},
105+
"Object-AttributeName-DynamicPseudoType": {
106+
in: Object{
107+
AttributeTypes: map[string]Type{
108+
"other": Bool,
109+
"test": DynamicPseudoType,
110+
},
111+
},
112+
path: &AttributePath{
113+
steps: []AttributePathStep{
114+
AttributeName("test"),
115+
},
116+
},
117+
expected: DynamicPseudoType,
118+
},
119+
"Object-AttributeName-List": {
120+
in: Object{
121+
AttributeTypes: map[string]Type{
122+
"other": Bool,
123+
"test": List{ElementType: String},
124+
},
125+
},
126+
path: &AttributePath{
127+
steps: []AttributePathStep{
128+
AttributeName("test"),
129+
},
130+
},
131+
expected: List{ElementType: String},
132+
},
133+
"Object-AttributeName-List-ElementKeyInt": {
134+
in: Object{
135+
AttributeTypes: map[string]Type{
136+
"other": Bool,
137+
"test": List{ElementType: String},
138+
},
139+
},
140+
path: &AttributePath{
141+
steps: []AttributePathStep{
142+
AttributeName("test"),
143+
ElementKeyInt(0),
144+
},
145+
},
146+
expected: String,
147+
},
148+
"Object-AttributeName-Map": {
149+
in: Object{
150+
AttributeTypes: map[string]Type{
151+
"other": Bool,
152+
"test": Map{ElementType: String},
153+
},
154+
},
155+
path: &AttributePath{
156+
steps: []AttributePathStep{
157+
AttributeName("test"),
158+
},
159+
},
160+
expected: Map{ElementType: String},
161+
},
162+
"Object-AttributeName-Map-ElementKeyString": {
163+
in: Object{
164+
AttributeTypes: map[string]Type{
165+
"other": Bool,
166+
"test": Map{ElementType: String},
167+
},
168+
},
169+
path: &AttributePath{
170+
steps: []AttributePathStep{
171+
AttributeName("test"),
172+
ElementKeyString("sub-test"),
173+
},
174+
},
175+
expected: String,
176+
},
177+
"Object-AttributeName-Number": {
178+
in: Object{
179+
AttributeTypes: map[string]Type{
180+
"other": Bool,
181+
"test": Number,
182+
},
183+
},
184+
path: &AttributePath{
185+
steps: []AttributePathStep{
186+
AttributeName("test"),
187+
},
188+
},
189+
expected: Number,
190+
},
191+
"Object-AttributeName-Set": {
192+
in: Object{
193+
AttributeTypes: map[string]Type{
194+
"other": Bool,
195+
"test": Set{ElementType: String},
196+
},
197+
},
198+
path: &AttributePath{
199+
steps: []AttributePathStep{
200+
AttributeName("test"),
201+
},
202+
},
203+
expected: Set{ElementType: String},
204+
},
205+
"Object-AttributeName-Set-ElementKeyValue": {
206+
in: Object{
207+
AttributeTypes: map[string]Type{
208+
"other": Bool,
209+
"test": Set{ElementType: String},
210+
},
211+
},
212+
path: &AttributePath{
213+
steps: []AttributePathStep{
214+
AttributeName("test"),
215+
ElementKeyValue(NewValue(String, "sub-test")),
216+
},
217+
},
218+
expected: String,
219+
},
220+
"Object-AttributeName-String": {
221+
in: Object{
222+
AttributeTypes: map[string]Type{
223+
"other": Bool,
224+
"test": String,
225+
},
226+
},
227+
path: &AttributePath{
228+
steps: []AttributePathStep{
229+
AttributeName("test"),
230+
},
231+
},
232+
expected: String,
233+
},
234+
"Object-AttributeName-Tuple": {
235+
in: Object{
236+
AttributeTypes: map[string]Type{
237+
"other": Bool,
238+
"test": Tuple{ElementTypes: []Type{Bool, String}},
239+
},
240+
},
241+
path: &AttributePath{
242+
steps: []AttributePathStep{
243+
AttributeName("test"),
244+
},
245+
},
246+
expected: Tuple{ElementTypes: []Type{Bool, String}},
247+
},
248+
"Object-AttributeName-Tuple-ElementKeyInt": {
249+
in: Object{
250+
AttributeTypes: map[string]Type{
251+
"other": Bool,
252+
"test": Tuple{ElementTypes: []Type{Bool, String}},
253+
},
254+
},
255+
path: &AttributePath{
256+
steps: []AttributePathStep{
257+
AttributeName("test"),
258+
ElementKeyInt(1),
259+
},
260+
},
261+
expected: String,
262+
},
91263
"slice-interface-root": {
92-
value: []interface{}{
264+
in: []interface{}{
93265
map[string]interface{}{
94266
"a": true,
95267
"b": 123,
@@ -119,7 +291,7 @@ func TestWalkAttributePath(t *testing.T) {
119291
},
120292
},
121293
"slice-interface-full": {
122-
value: []interface{}{
294+
in: []interface{}{
123295
map[string]interface{}{
124296
"a": true,
125297
"b": 123,
@@ -144,7 +316,7 @@ func TestWalkAttributePath(t *testing.T) {
144316
expected: "hello world",
145317
},
146318
"attributepathstepper": {
147-
value: []interface{}{
319+
in: []interface{}{
148320
attributePathStepperTestStruct{
149321
Name: "terraform",
150322
Colors: []string{
@@ -173,7 +345,7 @@ func TestWalkAttributePath(t *testing.T) {
173345
name, test := name, test
174346
t.Run(name, func(t *testing.T) {
175347
t.Parallel()
176-
result, remaining, err := WalkAttributePath(test.value, test.path)
348+
result, remaining, err := WalkAttributePath(test.in, test.path)
177349
if err != nil {
178350
t.Fatalf("error walking attribute path, %v still remains in the path: %s", remaining, err)
179351
}

tftypes/list.go

+17
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,23 @@ type List struct {
1515
_ []struct{}
1616
}
1717

18+
// ApplyTerraform5AttributePathStep applies an AttributePathStep to a List,
19+
// returning the Type found at that AttributePath within the List. If the
20+
// AttributePathStep cannot be applied to the List, an ErrInvalidStep error
21+
// will be returned.
22+
func (l List) ApplyTerraform5AttributePathStep(step AttributePathStep) (interface{}, error) {
23+
switch s := step.(type) {
24+
case ElementKeyInt:
25+
if int64(s) < 0 {
26+
return nil, ErrInvalidStep
27+
}
28+
29+
return l.ElementType, nil
30+
default:
31+
return nil, ErrInvalidStep
32+
}
33+
}
34+
1835
// Equal returns true if the two Lists are exactly equal. Unlike Is, passing in
1936
// a List with no ElementType will always return false.
2037
func (l List) Equal(o Type) bool {

tftypes/list_test.go

+72-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,77 @@
11
package tftypes
22

3-
import "testing"
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/google/go-cmp/cmp"
8+
)
9+
10+
func TestListApplyTerraform5AttributePathStep(t *testing.T) {
11+
t.Parallel()
12+
13+
testCases := map[string]struct {
14+
list List
15+
step AttributePathStep
16+
expectedType interface{}
17+
expectedError error
18+
}{
19+
"AttributeName": {
20+
list: List{},
21+
step: AttributeName("test"),
22+
expectedType: nil,
23+
expectedError: ErrInvalidStep,
24+
},
25+
"ElementKeyInt-no-ElementType": {
26+
list: List{},
27+
step: ElementKeyInt(123),
28+
expectedType: nil,
29+
expectedError: nil,
30+
},
31+
"ElementKeyInt-ElementType-found": {
32+
list: List{ElementType: String},
33+
step: ElementKeyInt(123),
34+
expectedType: String,
35+
expectedError: nil,
36+
},
37+
"ElementKeyInt-ElementType-negative": {
38+
list: List{ElementType: String},
39+
step: ElementKeyInt(-1),
40+
expectedType: nil,
41+
expectedError: ErrInvalidStep,
42+
},
43+
"ElementKeyString": {
44+
list: List{},
45+
step: ElementKeyString("test"),
46+
expectedType: nil,
47+
expectedError: ErrInvalidStep,
48+
},
49+
"ElementKeyValue": {
50+
list: List{},
51+
step: ElementKeyValue(NewValue(String, "test")),
52+
expectedType: nil,
53+
expectedError: ErrInvalidStep,
54+
},
55+
}
56+
57+
for name, testCase := range testCases {
58+
name, testCase := name, testCase
59+
60+
t.Run(name, func(t *testing.T) {
61+
t.Parallel()
62+
63+
got, err := testCase.list.ApplyTerraform5AttributePathStep(testCase.step)
64+
65+
if !errors.Is(err, testCase.expectedError) {
66+
t.Errorf("expected error %q, got %s", testCase.expectedError, err)
67+
}
68+
69+
if diff := cmp.Diff(got, testCase.expectedType); diff != "" {
70+
t.Errorf("unexpected difference: %s", diff)
71+
}
72+
})
73+
}
74+
}
475

576
func TestListEqual(t *testing.T) {
677
t.Parallel()

tftypes/map.go

+13
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,19 @@ type Map struct {
1616
_ []struct{}
1717
}
1818

19+
// ApplyTerraform5AttributePathStep applies an AttributePathStep to a Map,
20+
// returning the Type found at that AttributePath within the Map. If the
21+
// AttributePathStep cannot be applied to the Map, an ErrInvalidStep error
22+
// will be returned.
23+
func (m Map) ApplyTerraform5AttributePathStep(step AttributePathStep) (interface{}, error) {
24+
switch step.(type) {
25+
case ElementKeyString:
26+
return m.ElementType, nil
27+
default:
28+
return nil, ErrInvalidStep
29+
}
30+
}
31+
1932
// Equal returns true if the two Maps are exactly equal. Unlike Is, passing in
2033
// a Map with no ElementType will always return false.
2134
func (m Map) Equal(o Type) bool {

0 commit comments

Comments
 (0)