Skip to content

Commit f1ac931

Browse files
committed
path: Add Append methods for Paths and Expressions
Reference: hashicorp/terraform-plugin-framework-validators#32
1 parent 1a4e007 commit f1ac931

File tree

4 files changed

+352
-0
lines changed

4 files changed

+352
-0
lines changed

path/expressions.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,36 @@ import "strings"
55
// Expressions is a collection of attribute path expressions.
66
type Expressions []Expression
77

8+
// Append adds the given Expressions to the collection without duplication and
9+
// returns the combined result.
10+
func (e *Expressions) Append(expressions ...Expression) Expressions {
11+
if e == nil {
12+
return expressions
13+
}
14+
15+
for _, newExpression := range expressions {
16+
if e.Contains(newExpression) {
17+
continue
18+
}
19+
20+
*e = append(*e, newExpression)
21+
}
22+
23+
return *e
24+
}
25+
26+
// Contains returns true if the collection of expressions includes the given
27+
// expression.
28+
func (e Expressions) Contains(checkExpression Expression) bool {
29+
for _, expression := range e {
30+
if expression.Equal(checkExpression) {
31+
return true
32+
}
33+
}
34+
35+
return false
36+
}
37+
838
// String returns the human-readable representation of the expression
939
// collection. It is intended for logging and error messages and is not
1040
// protected by compatibility guarantees.

path/expressions_test.go

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,222 @@ import (
55

66
"github.com/google/go-cmp/cmp"
77
"github.com/hashicorp/terraform-plugin-framework/path"
8+
"github.com/hashicorp/terraform-plugin-framework/types"
89
)
910

11+
func TestExpressionsAppend(t *testing.T) {
12+
t.Parallel()
13+
14+
testCases := map[string]struct {
15+
expressions path.Expressions
16+
add path.Expressions
17+
expected path.Expressions
18+
}{
19+
"nil-nil": {
20+
expressions: nil,
21+
add: nil,
22+
expected: nil,
23+
},
24+
"nil-nonempty": {
25+
expressions: nil,
26+
add: path.Expressions{path.MatchRoot("test")},
27+
expected: path.Expressions{path.MatchRoot("test")},
28+
},
29+
"nonempty-nil": {
30+
expressions: path.Expressions{path.MatchRoot("test")},
31+
add: nil,
32+
expected: path.Expressions{path.MatchRoot("test")},
33+
},
34+
"empty-empty": {
35+
expressions: path.Expressions{},
36+
add: path.Expressions{},
37+
expected: path.Expressions{},
38+
},
39+
"empty-nonempty": {
40+
expressions: path.Expressions{},
41+
add: path.Expressions{path.MatchRoot("test")},
42+
expected: path.Expressions{path.MatchRoot("test")},
43+
},
44+
"nonempty-empty": {
45+
expressions: path.Expressions{path.MatchRoot("test")},
46+
add: path.Expressions{},
47+
expected: path.Expressions{path.MatchRoot("test")},
48+
},
49+
"nonempty-nonempty": {
50+
expressions: path.Expressions{
51+
path.MatchRoot("test1"),
52+
path.MatchRoot("test2"),
53+
},
54+
add: path.Expressions{
55+
path.MatchRoot("test3"),
56+
path.MatchRoot("test4"),
57+
},
58+
expected: path.Expressions{
59+
path.MatchRoot("test1"),
60+
path.MatchRoot("test2"),
61+
path.MatchRoot("test3"),
62+
path.MatchRoot("test4"),
63+
},
64+
},
65+
"deduplication": {
66+
expressions: path.Expressions{
67+
path.MatchRoot("test1"),
68+
path.MatchRoot("test2"),
69+
},
70+
add: path.Expressions{
71+
path.MatchRoot("test1"),
72+
path.MatchRoot("test3"),
73+
},
74+
expected: path.Expressions{
75+
path.MatchRoot("test1"),
76+
path.MatchRoot("test2"),
77+
path.MatchRoot("test3"),
78+
},
79+
},
80+
}
81+
82+
for name, testCase := range testCases {
83+
name, testCase := name, testCase
84+
85+
t.Run(name, func(t *testing.T) {
86+
t.Parallel()
87+
88+
got := testCase.expressions.Append(testCase.add...)
89+
90+
if diff := cmp.Diff(testCase.expressions, testCase.expected); diff != "" {
91+
t.Errorf("unexpected original difference: %s", diff)
92+
}
93+
94+
if diff := cmp.Diff(got, testCase.expected); diff != "" {
95+
t.Errorf("unexpected result difference: %s", diff)
96+
}
97+
})
98+
}
99+
}
100+
101+
func TestExpressionsContains(t *testing.T) {
102+
t.Parallel()
103+
104+
testCases := map[string]struct {
105+
expressions path.Expressions
106+
contains path.Expression
107+
expected bool
108+
}{
109+
"paths-nil": {
110+
expressions: nil,
111+
contains: path.MatchRoot("test"),
112+
expected: false,
113+
},
114+
"paths-empty": {
115+
expressions: path.Expressions{},
116+
contains: path.MatchRoot("test"),
117+
expected: false,
118+
},
119+
"contains-empty": {
120+
expressions: path.Expressions{
121+
path.MatchRoot("test"),
122+
},
123+
contains: path.MatchRelative(),
124+
expected: false,
125+
},
126+
"contains-middle": {
127+
expressions: path.Expressions{
128+
path.MatchRoot("test1").AtName("test1_attr"),
129+
path.MatchRoot("test2").AtName("test2_attr"),
130+
path.MatchRoot("test3").AtName("test3_attr"),
131+
},
132+
contains: path.MatchRoot("test2").AtName("test2_attr"),
133+
expected: true,
134+
},
135+
"contains-end": {
136+
expressions: path.Expressions{
137+
path.MatchRoot("test1").AtName("test1_attr"),
138+
path.MatchRoot("test2").AtName("test2_attr"),
139+
path.MatchRoot("test3").AtName("test3_attr"),
140+
},
141+
contains: path.MatchRoot("test3").AtName("test3_attr"),
142+
expected: true,
143+
},
144+
"relative-paths-different": {
145+
expressions: path.Expressions{
146+
path.MatchRoot("test_parent").AtName("test_child"),
147+
},
148+
contains: path.MatchRoot("test_parent").AtName("test_child").AtParent().AtName("test_child"),
149+
expected: false, // Contains intentionally does not Resolve()
150+
},
151+
"AttributeName-different": {
152+
expressions: path.Expressions{
153+
path.MatchRoot("test"),
154+
},
155+
contains: path.MatchRoot("not-test"),
156+
expected: false,
157+
},
158+
"AttributeName-equal": {
159+
expressions: path.Expressions{
160+
path.MatchRoot("test"),
161+
},
162+
contains: path.MatchRoot("test"),
163+
expected: true,
164+
},
165+
"ElementKeyInt-different": {
166+
expressions: path.Expressions{
167+
path.MatchRelative().AtListIndex(0),
168+
},
169+
contains: path.MatchRelative().AtListIndex(1),
170+
expected: false,
171+
},
172+
"ElementKeyInt-equal": {
173+
expressions: path.Expressions{
174+
path.MatchRelative().AtListIndex(0),
175+
},
176+
contains: path.MatchRelative().AtListIndex(0),
177+
expected: true,
178+
},
179+
"ElementKeyString-different": {
180+
expressions: path.Expressions{
181+
path.MatchRelative().AtMapKey("test"),
182+
},
183+
contains: path.MatchRelative().AtMapKey("not-test"),
184+
expected: false,
185+
},
186+
"ElementKeyString-equal": {
187+
expressions: path.Expressions{
188+
path.MatchRelative().AtMapKey("test"),
189+
},
190+
contains: path.MatchRelative().AtMapKey("test"),
191+
expected: true,
192+
},
193+
"ElementKeyValue-different": {
194+
expressions: path.Expressions{
195+
path.MatchRelative().AtSetValue(types.String{Value: "test"}),
196+
},
197+
contains: path.MatchRelative().AtSetValue(types.String{Value: "not-test"}),
198+
expected: false,
199+
},
200+
"ElementKeyValue-equal": {
201+
expressions: path.Expressions{
202+
path.MatchRelative().AtSetValue(types.String{Value: "test"}),
203+
},
204+
contains: path.MatchRelative().AtSetValue(types.String{Value: "test"}),
205+
expected: true,
206+
},
207+
}
208+
209+
for name, testCase := range testCases {
210+
name, testCase := name, testCase
211+
212+
t.Run(name, func(t *testing.T) {
213+
t.Parallel()
214+
215+
got := testCase.expressions.Contains(testCase.contains)
216+
217+
if got != testCase.expected {
218+
t.Errorf("expected %t, got %t", testCase.expected, got)
219+
}
220+
})
221+
}
222+
}
223+
10224
func TestExpressionsString(t *testing.T) {
11225
t.Parallel()
12226

path/paths.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@ import "strings"
55
// Paths is a collection of exact attribute paths.
66
type Paths []Path
77

8+
// Append adds the given Paths to the collection without duplication and
9+
// returns the combined result.
10+
func (p *Paths) Append(paths ...Path) Paths {
11+
if p == nil {
12+
return paths
13+
}
14+
15+
for _, newPath := range paths {
16+
if p.Contains(newPath) {
17+
continue
18+
}
19+
20+
*p = append(*p, newPath)
21+
}
22+
23+
return *p
24+
}
25+
826
// Contains returns true if the collection of paths includes the given path.
927
func (p Paths) Contains(checkPath Path) bool {
1028
for _, path := range p {

path/paths_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,96 @@ import (
88
"github.com/hashicorp/terraform-plugin-framework/types"
99
)
1010

11+
func TestPathsAppend(t *testing.T) {
12+
t.Parallel()
13+
14+
testCases := map[string]struct {
15+
paths path.Paths
16+
add path.Paths
17+
expected path.Paths
18+
}{
19+
"nil-nil": {
20+
paths: nil,
21+
add: nil,
22+
expected: nil,
23+
},
24+
"nil-nonempty": {
25+
paths: nil,
26+
add: path.Paths{path.Root("test")},
27+
expected: path.Paths{path.Root("test")},
28+
},
29+
"nonempty-nil": {
30+
paths: path.Paths{path.Root("test")},
31+
add: nil,
32+
expected: path.Paths{path.Root("test")},
33+
},
34+
"empty-empty": {
35+
paths: path.Paths{},
36+
add: path.Paths{},
37+
expected: path.Paths{},
38+
},
39+
"empty-nonempty": {
40+
paths: path.Paths{},
41+
add: path.Paths{path.Root("test")},
42+
expected: path.Paths{path.Root("test")},
43+
},
44+
"nonempty-empty": {
45+
paths: path.Paths{path.Root("test")},
46+
add: path.Paths{},
47+
expected: path.Paths{path.Root("test")},
48+
},
49+
"nonempty-nonempty": {
50+
paths: path.Paths{
51+
path.Root("test1"),
52+
path.Root("test2"),
53+
},
54+
add: path.Paths{
55+
path.Root("test3"),
56+
path.Root("test4"),
57+
},
58+
expected: path.Paths{
59+
path.Root("test1"),
60+
path.Root("test2"),
61+
path.Root("test3"),
62+
path.Root("test4"),
63+
},
64+
},
65+
"deduplication": {
66+
paths: path.Paths{
67+
path.Root("test1"),
68+
path.Root("test2"),
69+
},
70+
add: path.Paths{
71+
path.Root("test1"),
72+
path.Root("test3"),
73+
},
74+
expected: path.Paths{
75+
path.Root("test1"),
76+
path.Root("test2"),
77+
path.Root("test3"),
78+
},
79+
},
80+
}
81+
82+
for name, testCase := range testCases {
83+
name, testCase := name, testCase
84+
85+
t.Run(name, func(t *testing.T) {
86+
t.Parallel()
87+
88+
got := testCase.paths.Append(testCase.add...)
89+
90+
if diff := cmp.Diff(testCase.paths, testCase.expected); diff != "" {
91+
t.Errorf("unexpected original difference: %s", diff)
92+
}
93+
94+
if diff := cmp.Diff(got, testCase.expected); diff != "" {
95+
t.Errorf("unexpected result difference: %s", diff)
96+
}
97+
})
98+
}
99+
}
100+
11101
func TestPathsContains(t *testing.T) {
12102
t.Parallel()
13103

0 commit comments

Comments
 (0)