Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions path/expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,36 @@ import "strings"
// Expressions is a collection of attribute path expressions.
type Expressions []Expression

// Append adds the given Expressions to the collection without duplication and
// returns the combined result.
func (e *Expressions) Append(expressions ...Expression) Expressions {
if e == nil {
return expressions
}

for _, newExpression := range expressions {
if e.Contains(newExpression) {
continue
}

*e = append(*e, newExpression)
}

return *e
}

// Contains returns true if the collection of expressions includes the given
// expression.
func (e Expressions) Contains(checkExpression Expression) bool {
for _, expression := range e {
if expression.Equal(checkExpression) {
return true
}
}

return false
}

// String returns the human-readable representation of the expression
// collection. It is intended for logging and error messages and is not
// protected by compatibility guarantees.
Expand Down
214 changes: 214 additions & 0 deletions path/expressions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,222 @@ import (

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/types"
)

func TestExpressionsAppend(t *testing.T) {
t.Parallel()

testCases := map[string]struct {
expressions path.Expressions
add path.Expressions
expected path.Expressions
}{
"nil-nil": {
expressions: nil,
add: nil,
expected: nil,
},
"nil-nonempty": {
expressions: nil,
add: path.Expressions{path.MatchRoot("test")},
expected: path.Expressions{path.MatchRoot("test")},
},
"nonempty-nil": {
expressions: path.Expressions{path.MatchRoot("test")},
add: nil,
expected: path.Expressions{path.MatchRoot("test")},
},
"empty-empty": {
expressions: path.Expressions{},
add: path.Expressions{},
expected: path.Expressions{},
},
"empty-nonempty": {
expressions: path.Expressions{},
add: path.Expressions{path.MatchRoot("test")},
expected: path.Expressions{path.MatchRoot("test")},
},
"nonempty-empty": {
expressions: path.Expressions{path.MatchRoot("test")},
add: path.Expressions{},
expected: path.Expressions{path.MatchRoot("test")},
},
"nonempty-nonempty": {
expressions: path.Expressions{
path.MatchRoot("test1"),
path.MatchRoot("test2"),
},
add: path.Expressions{
path.MatchRoot("test3"),
path.MatchRoot("test4"),
},
expected: path.Expressions{
path.MatchRoot("test1"),
path.MatchRoot("test2"),
path.MatchRoot("test3"),
path.MatchRoot("test4"),
},
},
"deduplication": {
expressions: path.Expressions{
path.MatchRoot("test1"),
path.MatchRoot("test2"),
},
add: path.Expressions{
path.MatchRoot("test1"),
path.MatchRoot("test3"),
},
expected: path.Expressions{
path.MatchRoot("test1"),
path.MatchRoot("test2"),
path.MatchRoot("test3"),
},
},
}

for name, testCase := range testCases {
name, testCase := name, testCase

t.Run(name, func(t *testing.T) {
t.Parallel()

got := testCase.expressions.Append(testCase.add...)

if diff := cmp.Diff(testCase.expressions, testCase.expected); diff != "" {
t.Errorf("unexpected original difference: %s", diff)
}

if diff := cmp.Diff(got, testCase.expected); diff != "" {
t.Errorf("unexpected result difference: %s", diff)
}
})
}
}

func TestExpressionsContains(t *testing.T) {
t.Parallel()

testCases := map[string]struct {
expressions path.Expressions
contains path.Expression
expected bool
}{
"paths-nil": {
expressions: nil,
contains: path.MatchRoot("test"),
expected: false,
},
"paths-empty": {
expressions: path.Expressions{},
contains: path.MatchRoot("test"),
expected: false,
},
"contains-empty": {
expressions: path.Expressions{
path.MatchRoot("test"),
},
contains: path.MatchRelative(),
expected: false,
},
"contains-middle": {
expressions: path.Expressions{
path.MatchRoot("test1").AtName("test1_attr"),
path.MatchRoot("test2").AtName("test2_attr"),
path.MatchRoot("test3").AtName("test3_attr"),
},
contains: path.MatchRoot("test2").AtName("test2_attr"),
expected: true,
},
"contains-end": {
expressions: path.Expressions{
path.MatchRoot("test1").AtName("test1_attr"),
path.MatchRoot("test2").AtName("test2_attr"),
path.MatchRoot("test3").AtName("test3_attr"),
},
contains: path.MatchRoot("test3").AtName("test3_attr"),
expected: true,
},
"relative-paths-different": {
expressions: path.Expressions{
path.MatchRoot("test_parent").AtName("test_child"),
},
contains: path.MatchRoot("test_parent").AtName("test_child").AtParent().AtName("test_child"),
expected: false, // Contains intentionally does not Resolve()
},
"AttributeName-different": {
expressions: path.Expressions{
path.MatchRoot("test"),
},
contains: path.MatchRoot("not-test"),
expected: false,
},
"AttributeName-equal": {
expressions: path.Expressions{
path.MatchRoot("test"),
},
contains: path.MatchRoot("test"),
expected: true,
},
"ElementKeyInt-different": {
expressions: path.Expressions{
path.MatchRelative().AtListIndex(0),
},
contains: path.MatchRelative().AtListIndex(1),
expected: false,
},
"ElementKeyInt-equal": {
expressions: path.Expressions{
path.MatchRelative().AtListIndex(0),
},
contains: path.MatchRelative().AtListIndex(0),
expected: true,
},
"ElementKeyString-different": {
expressions: path.Expressions{
path.MatchRelative().AtMapKey("test"),
},
contains: path.MatchRelative().AtMapKey("not-test"),
expected: false,
},
"ElementKeyString-equal": {
expressions: path.Expressions{
path.MatchRelative().AtMapKey("test"),
},
contains: path.MatchRelative().AtMapKey("test"),
expected: true,
},
"ElementKeyValue-different": {
expressions: path.Expressions{
path.MatchRelative().AtSetValue(types.String{Value: "test"}),
},
contains: path.MatchRelative().AtSetValue(types.String{Value: "not-test"}),
expected: false,
},
"ElementKeyValue-equal": {
expressions: path.Expressions{
path.MatchRelative().AtSetValue(types.String{Value: "test"}),
},
contains: path.MatchRelative().AtSetValue(types.String{Value: "test"}),
expected: true,
},
}

for name, testCase := range testCases {
name, testCase := name, testCase

t.Run(name, func(t *testing.T) {
t.Parallel()

got := testCase.expressions.Contains(testCase.contains)

if got != testCase.expected {
t.Errorf("expected %t, got %t", testCase.expected, got)
}
})
}
}

func TestExpressionsString(t *testing.T) {
t.Parallel()

Expand Down
18 changes: 18 additions & 0 deletions path/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ import "strings"
// Paths is a collection of exact attribute paths.
type Paths []Path

// Append adds the given Paths to the collection without duplication and
// returns the combined result.
func (p *Paths) Append(paths ...Path) Paths {
if p == nil {
return paths
}

for _, newPath := range paths {
if p.Contains(newPath) {
continue
}

*p = append(*p, newPath)
}

return *p
}

// Contains returns true if the collection of paths includes the given path.
func (p Paths) Contains(checkPath Path) bool {
for _, path := range p {
Expand Down
90 changes: 90 additions & 0 deletions path/paths_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,96 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
)

func TestPathsAppend(t *testing.T) {
t.Parallel()

testCases := map[string]struct {
paths path.Paths
add path.Paths
expected path.Paths
}{
"nil-nil": {
paths: nil,
add: nil,
expected: nil,
},
"nil-nonempty": {
paths: nil,
add: path.Paths{path.Root("test")},
expected: path.Paths{path.Root("test")},
},
"nonempty-nil": {
paths: path.Paths{path.Root("test")},
add: nil,
expected: path.Paths{path.Root("test")},
},
"empty-empty": {
paths: path.Paths{},
add: path.Paths{},
expected: path.Paths{},
},
"empty-nonempty": {
paths: path.Paths{},
add: path.Paths{path.Root("test")},
expected: path.Paths{path.Root("test")},
},
"nonempty-empty": {
paths: path.Paths{path.Root("test")},
add: path.Paths{},
expected: path.Paths{path.Root("test")},
},
"nonempty-nonempty": {
paths: path.Paths{
path.Root("test1"),
path.Root("test2"),
},
add: path.Paths{
path.Root("test3"),
path.Root("test4"),
},
expected: path.Paths{
path.Root("test1"),
path.Root("test2"),
path.Root("test3"),
path.Root("test4"),
},
},
"deduplication": {
paths: path.Paths{
path.Root("test1"),
path.Root("test2"),
},
add: path.Paths{
path.Root("test1"),
path.Root("test3"),
},
expected: path.Paths{
path.Root("test1"),
path.Root("test2"),
path.Root("test3"),
},
},
}

for name, testCase := range testCases {
name, testCase := name, testCase

t.Run(name, func(t *testing.T) {
t.Parallel()

got := testCase.paths.Append(testCase.add...)

if diff := cmp.Diff(testCase.paths, testCase.expected); diff != "" {
t.Errorf("unexpected original difference: %s", diff)
}

if diff := cmp.Diff(got, testCase.expected); diff != "" {
t.Errorf("unexpected result difference: %s", diff)
}
})
}
}

func TestPathsContains(t *testing.T) {
t.Parallel()

Expand Down