Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tftypes: Support AttributePathStepper interface in Type #163

Merged
merged 3 commits into from Mar 14, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/163.txt
@@ -0,0 +1,3 @@
```release-note:enhancement
tftypes: Added `Type` support to `WalkAttributePath()` function
```
6 changes: 3 additions & 3 deletions tftypes/attribute_path.go
Expand Up @@ -284,9 +284,9 @@ type AttributePathStepper interface {
ApplyTerraform5AttributePathStep(AttributePathStep) (interface{}, error)
}

// WalkAttributePath will return the value that `path` is pointing to, using
// `in` as the root. If an error is returned, the AttributePath returned will
// indicate the steps that remained to be applied when the error was
// WalkAttributePath will return the Type or Value that `path` is pointing to,
// using `in` as the root. If an error is returned, the AttributePath returned
// will indicate the steps that remained to be applied when the error was
// encountered.
//
// map[string]interface{} and []interface{} types have built-in support. Other
Expand Down
186 changes: 179 additions & 7 deletions tftypes/attribute_path_test.go
Expand Up @@ -43,13 +43,13 @@ func (a attributePathStepperTestSlice) ApplyTerraform5AttributePathStep(step Att
func TestWalkAttributePath(t *testing.T) {
t.Parallel()
type testCase struct {
value interface{}
in interface{}
path *AttributePath
expected interface{}
}
tests := map[string]testCase{
"msi-root": {
value: map[string]interface{}{
in: map[string]interface{}{
"a": map[string]interface{}{
"red": true,
"blue": 123,
Expand All @@ -70,7 +70,7 @@ func TestWalkAttributePath(t *testing.T) {
},
},
"msi-full": {
value: map[string]interface{}{
in: map[string]interface{}{
"a": map[string]interface{}{
"red": true,
"blue": 123,
Expand All @@ -88,8 +88,180 @@ func TestWalkAttributePath(t *testing.T) {
},
expected: true,
},
"Object-AttributeName-Bool": {
in: Object{
AttributeTypes: map[string]Type{
"other": String,
"test": Bool,
},
},
path: &AttributePath{
steps: []AttributePathStep{
AttributeName("test"),
},
},
expected: Bool,
},
"Object-AttributeName-DynamicPseudoType": {
in: Object{
AttributeTypes: map[string]Type{
"other": Bool,
"test": DynamicPseudoType,
},
},
path: &AttributePath{
steps: []AttributePathStep{
AttributeName("test"),
},
},
expected: DynamicPseudoType,
},
"Object-AttributeName-List": {
in: Object{
AttributeTypes: map[string]Type{
"other": Bool,
"test": List{ElementType: String},
},
},
path: &AttributePath{
steps: []AttributePathStep{
AttributeName("test"),
},
},
expected: List{ElementType: String},
},
"Object-AttributeName-List-ElementKeyInt": {
in: Object{
AttributeTypes: map[string]Type{
"other": Bool,
"test": List{ElementType: String},
},
},
path: &AttributePath{
steps: []AttributePathStep{
AttributeName("test"),
ElementKeyInt(0),
},
},
expected: String,
},
"Object-AttributeName-Map": {
in: Object{
AttributeTypes: map[string]Type{
"other": Bool,
"test": Map{ElementType: String},
},
},
path: &AttributePath{
steps: []AttributePathStep{
AttributeName("test"),
},
},
expected: Map{ElementType: String},
},
"Object-AttributeName-Map-ElementKeyString": {
in: Object{
AttributeTypes: map[string]Type{
"other": Bool,
"test": Map{ElementType: String},
},
},
path: &AttributePath{
steps: []AttributePathStep{
AttributeName("test"),
ElementKeyString("sub-test"),
},
},
expected: String,
},
"Object-AttributeName-Number": {
in: Object{
AttributeTypes: map[string]Type{
"other": Bool,
"test": Number,
},
},
path: &AttributePath{
steps: []AttributePathStep{
AttributeName("test"),
},
},
expected: Number,
},
"Object-AttributeName-Set": {
in: Object{
AttributeTypes: map[string]Type{
"other": Bool,
"test": Set{ElementType: String},
},
},
path: &AttributePath{
steps: []AttributePathStep{
AttributeName("test"),
},
},
expected: Set{ElementType: String},
},
"Object-AttributeName-Set-ElementKeyValue": {
in: Object{
AttributeTypes: map[string]Type{
"other": Bool,
"test": Set{ElementType: String},
},
},
path: &AttributePath{
steps: []AttributePathStep{
AttributeName("test"),
ElementKeyValue(NewValue(String, "sub-test")),
},
},
expected: String,
},
"Object-AttributeName-String": {
in: Object{
AttributeTypes: map[string]Type{
"other": Bool,
"test": String,
},
},
path: &AttributePath{
steps: []AttributePathStep{
AttributeName("test"),
},
},
expected: String,
},
"Object-AttributeName-Tuple": {
in: Object{
AttributeTypes: map[string]Type{
"other": Bool,
"test": Tuple{ElementTypes: []Type{Bool, String}},
},
},
path: &AttributePath{
steps: []AttributePathStep{
AttributeName("test"),
},
},
expected: Tuple{ElementTypes: []Type{Bool, String}},
},
"Object-AttributeName-Tuple-ElementKeyInt": {
in: Object{
AttributeTypes: map[string]Type{
"other": Bool,
"test": Tuple{ElementTypes: []Type{Bool, String}},
},
},
path: &AttributePath{
steps: []AttributePathStep{
AttributeName("test"),
ElementKeyInt(1),
},
},
expected: String,
},
"slice-interface-root": {
value: []interface{}{
in: []interface{}{
map[string]interface{}{
"a": true,
"b": 123,
Expand Down Expand Up @@ -119,7 +291,7 @@ func TestWalkAttributePath(t *testing.T) {
},
},
"slice-interface-full": {
value: []interface{}{
in: []interface{}{
map[string]interface{}{
"a": true,
"b": 123,
Expand All @@ -144,7 +316,7 @@ func TestWalkAttributePath(t *testing.T) {
expected: "hello world",
},
"attributepathstepper": {
value: []interface{}{
in: []interface{}{
attributePathStepperTestStruct{
Name: "terraform",
Colors: []string{
Expand Down Expand Up @@ -173,7 +345,7 @@ func TestWalkAttributePath(t *testing.T) {
name, test := name, test
t.Run(name, func(t *testing.T) {
t.Parallel()
result, remaining, err := WalkAttributePath(test.value, test.path)
result, remaining, err := WalkAttributePath(test.in, test.path)
if err != nil {
t.Fatalf("error walking attribute path, %v still remains in the path: %s", remaining, err)
}
Expand Down
17 changes: 17 additions & 0 deletions tftypes/list.go
Expand Up @@ -15,6 +15,23 @@ type List struct {
_ []struct{}
}

// ApplyTerraform5AttributePathStep applies an AttributePathStep to a List,
// returning the Type found at that AttributePath within the List. If the
// AttributePathStep cannot be applied to the List, an ErrInvalidStep error
// will be returned.
func (l List) ApplyTerraform5AttributePathStep(step AttributePathStep) (interface{}, error) {
switch s := step.(type) {
case ElementKeyInt:
if int64(s) < 0 {
return nil, ErrInvalidStep
}

return l.ElementType, nil
default:
return nil, ErrInvalidStep
}
}

// Equal returns true if the two Lists are exactly equal. Unlike Is, passing in
// a List with no ElementType will always return false.
func (l List) Equal(o Type) bool {
Expand Down
73 changes: 72 additions & 1 deletion tftypes/list_test.go
@@ -1,6 +1,77 @@
package tftypes

import "testing"
import (
"errors"
"testing"

"github.com/google/go-cmp/cmp"
)

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

testCases := map[string]struct {
list List
step AttributePathStep
expectedType interface{}
expectedError error
}{
"AttributeName": {
list: List{},
step: AttributeName("test"),
expectedType: nil,
expectedError: ErrInvalidStep,
},
"ElementKeyInt-no-ElementType": {
list: List{},
step: ElementKeyInt(123),
expectedType: nil,
expectedError: nil,
},
"ElementKeyInt-ElementType-found": {
list: List{ElementType: String},
step: ElementKeyInt(123),
expectedType: String,
expectedError: nil,
},
"ElementKeyInt-ElementType-negative": {
list: List{ElementType: String},
step: ElementKeyInt(-1),
expectedType: nil,
expectedError: ErrInvalidStep,
},
"ElementKeyString": {
list: List{},
step: ElementKeyString("test"),
expectedType: nil,
expectedError: ErrInvalidStep,
},
"ElementKeyValue": {
list: List{},
step: ElementKeyValue(NewValue(String, "test")),
expectedType: nil,
expectedError: ErrInvalidStep,
},
}

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

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

got, err := testCase.list.ApplyTerraform5AttributePathStep(testCase.step)

if !errors.Is(err, testCase.expectedError) {
t.Errorf("expected error %q, got %s", testCase.expectedError, err)
}

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

func TestListEqual(t *testing.T) {
t.Parallel()
Expand Down
13 changes: 13 additions & 0 deletions tftypes/map.go
Expand Up @@ -16,6 +16,19 @@ type Map struct {
_ []struct{}
}

// ApplyTerraform5AttributePathStep applies an AttributePathStep to a Map,
// returning the Type found at that AttributePath within the Map. If the
// AttributePathStep cannot be applied to the Map, an ErrInvalidStep error
// will be returned.
func (m Map) ApplyTerraform5AttributePathStep(step AttributePathStep) (interface{}, error) {
switch step.(type) {
case ElementKeyString:
return m.ElementType, nil
default:
return nil, ErrInvalidStep
}
}

// Equal returns true if the two Maps are exactly equal. Unlike Is, passing in
// a Map with no ElementType will always return false.
func (m Map) Equal(o Type) bool {
Expand Down