diff --git a/.changelog/pending.txt b/.changelog/pending.txt new file mode 100644 index 00000000..8011eb29 --- /dev/null +++ b/.changelog/pending.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +tftypes: Added `Type` support to `WalkAttributePath()` function +``` diff --git a/tftypes/attribute_path.go b/tftypes/attribute_path.go index fd91f96a..d6108d4a 100644 --- a/tftypes/attribute_path.go +++ b/tftypes/attribute_path.go @@ -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 diff --git a/tftypes/attribute_path_test.go b/tftypes/attribute_path_test.go index 623c2485..5e041ac0 100644 --- a/tftypes/attribute_path_test.go +++ b/tftypes/attribute_path_test.go @@ -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, @@ -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, @@ -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, @@ -119,7 +291,7 @@ func TestWalkAttributePath(t *testing.T) { }, }, "slice-interface-full": { - value: []interface{}{ + in: []interface{}{ map[string]interface{}{ "a": true, "b": 123, @@ -144,7 +316,7 @@ func TestWalkAttributePath(t *testing.T) { expected: "hello world", }, "attributepathstepper": { - value: []interface{}{ + in: []interface{}{ attributePathStepperTestStruct{ Name: "terraform", Colors: []string{ @@ -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) } diff --git a/tftypes/list.go b/tftypes/list.go index 820efbcd..a0bc2f37 100644 --- a/tftypes/list.go +++ b/tftypes/list.go @@ -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 { diff --git a/tftypes/list_test.go b/tftypes/list_test.go index db4a2ca0..d2e37bf9 100644 --- a/tftypes/list_test.go +++ b/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() diff --git a/tftypes/map.go b/tftypes/map.go index ecc6bed0..7c1e7d05 100644 --- a/tftypes/map.go +++ b/tftypes/map.go @@ -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 { diff --git a/tftypes/map_test.go b/tftypes/map_test.go index 7e44ef47..7e6981e6 100644 --- a/tftypes/map_test.go +++ b/tftypes/map_test.go @@ -1,6 +1,71 @@ package tftypes -import "testing" +import ( + "errors" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestMapApplyTerraform5AttributePathStep(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + m Map + step AttributePathStep + expectedType interface{} + expectedError error + }{ + "AttributeName": { + m: Map{}, + step: AttributeName("test"), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "ElementKeyInt": { + m: Map{}, + step: ElementKeyInt(123), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "ElementKeyString-no-ElementType": { + m: Map{}, + step: ElementKeyString("test"), + expectedType: nil, + expectedError: nil, + }, + "ElementKeyString-ElementType": { + m: Map{ElementType: String}, + step: ElementKeyString("test"), + expectedType: String, + expectedError: nil, + }, + "ElementKeyValue": { + m: Map{}, + 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.m.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 TestMapEqual(t *testing.T) { t.Parallel() diff --git a/tftypes/object.go b/tftypes/object.go index ef3a622b..ce2bf132 100644 --- a/tftypes/object.go +++ b/tftypes/object.go @@ -44,6 +44,29 @@ type Object struct { _ []struct{} } +// ApplyTerraform5AttributePathStep applies an AttributePathStep to an Object, +// returning the Type found at that AttributePath within the Object. If the +// AttributePathStep cannot be applied to the Object, an ErrInvalidStep error +// will be returned. +func (o Object) ApplyTerraform5AttributePathStep(step AttributePathStep) (interface{}, error) { + switch s := step.(type) { + case AttributeName: + if len(o.AttributeTypes) == 0 { + return nil, ErrInvalidStep + } + + attrType, ok := o.AttributeTypes[string(s)] + + if !ok { + return nil, ErrInvalidStep + } + + return attrType, nil + default: + return nil, ErrInvalidStep + } +} + // Equal returns true if the two Objects are exactly equal. Unlike Is, passing // in an Object with no AttributeTypes will always return false. func (o Object) Equal(other Type) bool { diff --git a/tftypes/object_test.go b/tftypes/object_test.go index 418664ac..0955bbc2 100644 --- a/tftypes/object_test.go +++ b/tftypes/object_test.go @@ -1,6 +1,77 @@ package tftypes -import "testing" +import ( + "errors" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestObjectApplyTerraform5AttributePathStep(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + object Object + step AttributePathStep + expectedType interface{} + expectedError error + }{ + "AttributeName-no-AttributeTypes": { + object: Object{}, + step: AttributeName("test"), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "AttributeName-AttributeTypes-found": { + object: Object{AttributeTypes: map[string]Type{"test": String}}, + step: AttributeName("test"), + expectedType: String, + expectedError: nil, + }, + "AttributeName-AttributeTypes-not-found": { + object: Object{AttributeTypes: map[string]Type{"other": String}}, + step: AttributeName("test"), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "ElementKeyInt": { + object: Object{}, + step: ElementKeyInt(123), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "ElementKeyString": { + object: Object{}, + step: ElementKeyString("test"), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "ElementKeyValue": { + object: Object{}, + 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.object.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 TestObjectEqual(t *testing.T) { t.Parallel() diff --git a/tftypes/primitive.go b/tftypes/primitive.go index 26cfe839..0349f414 100644 --- a/tftypes/primitive.go +++ b/tftypes/primitive.go @@ -37,6 +37,12 @@ type primitive struct { _ []struct{} } +// ApplyTerraform5AttributePathStep always returns an ErrInvalidStep error +// as it is invalid to step into a primitive. +func (p primitive) ApplyTerraform5AttributePathStep(step AttributePathStep) (interface{}, error) { + return nil, ErrInvalidStep +} + func (p primitive) Equal(o Type) bool { v, ok := o.(primitive) if !ok { diff --git a/tftypes/primitive_test.go b/tftypes/primitive_test.go index 5536e095..66686003 100644 --- a/tftypes/primitive_test.go +++ b/tftypes/primitive_test.go @@ -1,9 +1,138 @@ package tftypes import ( + "errors" "testing" + + "github.com/google/go-cmp/cmp" ) +func TestPrimitiveApplyTerraform5AttributePathStep(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + primitive primitive + step AttributePathStep + expectedType interface{} + expectedError error + }{ + "Bool-AttributeName": { + primitive: Bool, + step: AttributeName("test"), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "Bool-ElementKeyInt": { + primitive: Bool, + step: ElementKeyInt(123), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "Bool-ElementKeyString": { + primitive: Bool, + step: ElementKeyString("test"), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "Bool-ElementKeyValue": { + primitive: Bool, + step: ElementKeyValue(NewValue(String, "test")), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "DynamicPseudoType-AttributeName": { + primitive: DynamicPseudoType, + step: AttributeName("test"), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "DynamicPseudoType-ElementKeyInt": { + primitive: DynamicPseudoType, + step: ElementKeyInt(123), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "DynamicPseudoType-ElementKeyString": { + primitive: DynamicPseudoType, + step: ElementKeyString("test"), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "DynamicPseudoType-ElementKeyValue": { + primitive: DynamicPseudoType, + step: ElementKeyValue(NewValue(String, "test")), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "Number-AttributeName": { + primitive: Number, + step: AttributeName("test"), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "Number-ElementKeyInt": { + primitive: Number, + step: ElementKeyInt(123), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "Number-ElementKeyString": { + primitive: Number, + step: ElementKeyString("test"), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "Number-ElementKeyValue": { + primitive: Number, + step: ElementKeyValue(NewValue(String, "test")), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "String-AttributeName": { + primitive: String, + step: AttributeName("test"), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "String-ElementKeyInt": { + primitive: String, + step: ElementKeyInt(123), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "String-ElementKeyString": { + primitive: String, + step: ElementKeyString("test"), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "String-ElementKeyValue": { + primitive: String, + 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.primitive.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 TestPrimitiveEqual(t *testing.T) { type testCase struct { p1 primitive diff --git a/tftypes/set.go b/tftypes/set.go index 28ab23b7..00163067 100644 --- a/tftypes/set.go +++ b/tftypes/set.go @@ -15,6 +15,19 @@ type Set struct { _ []struct{} } +// ApplyTerraform5AttributePathStep applies an AttributePathStep to a Set, +// returning the Type found at that AttributePath within the Set. If the +// AttributePathStep cannot be applied to the Set, an ErrInvalidStep error +// will be returned. +func (s Set) ApplyTerraform5AttributePathStep(step AttributePathStep) (interface{}, error) { + switch step.(type) { + case ElementKeyValue: + return s.ElementType, nil + default: + return nil, ErrInvalidStep + } +} + // Equal returns true if the two Sets are exactly equal. Unlike Is, passing in // a Set with no ElementType will always return false. func (s Set) Equal(o Type) bool { diff --git a/tftypes/set_test.go b/tftypes/set_test.go index 3c67403a..abe9d7d6 100644 --- a/tftypes/set_test.go +++ b/tftypes/set_test.go @@ -1,6 +1,71 @@ package tftypes -import "testing" +import ( + "errors" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestSetApplyTerraform5AttributePathStep(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + set Set + step AttributePathStep + expectedType interface{} + expectedError error + }{ + "AttributeName": { + set: Set{}, + step: AttributeName("test"), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "ElementKeyInt": { + set: Set{}, + step: ElementKeyInt(123), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "ElementKeyString": { + set: Set{}, + step: ElementKeyString("test"), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "ElementKeyValue-no-ElementType": { + set: Set{}, + step: ElementKeyValue(NewValue(String, "test")), + expectedType: nil, + expectedError: nil, + }, + "ElementKeyValue-ElementType": { + set: Set{ElementType: String}, + step: ElementKeyValue(NewValue(String, "test")), + expectedType: String, + expectedError: nil, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, err := testCase.set.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 TestSetEqual(t *testing.T) { t.Parallel() diff --git a/tftypes/tuple.go b/tftypes/tuple.go index df57d90d..5ca5709a 100644 --- a/tftypes/tuple.go +++ b/tftypes/tuple.go @@ -19,6 +19,23 @@ type Tuple struct { _ []struct{} } +// ApplyTerraform5AttributePathStep applies an AttributePathStep to a Tuple, +// returning the Type found at that AttributePath within the Tuple. If the +// AttributePathStep cannot be applied to the Tuple, an ErrInvalidStep error +// will be returned. +func (tu Tuple) ApplyTerraform5AttributePathStep(step AttributePathStep) (interface{}, error) { + switch s := step.(type) { + case ElementKeyInt: + if int64(s) < 0 || int64(s) >= int64(len(tu.ElementTypes)) { + return nil, ErrInvalidStep + } + + return tu.ElementTypes[int64(s)], nil + default: + return nil, ErrInvalidStep + } +} + // Equal returns true if the two Tuples are exactly equal. Unlike Is, passing // in a Tuple with no ElementTypes will always return false. func (tu Tuple) Equal(o Type) bool { diff --git a/tftypes/tuple_test.go b/tftypes/tuple_test.go index 4a1a288c..c21aafaf 100644 --- a/tftypes/tuple_test.go +++ b/tftypes/tuple_test.go @@ -1,6 +1,83 @@ package tftypes -import "testing" +import ( + "errors" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestTupleApplyTerraform5AttributePathStep(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + tuple Tuple + step AttributePathStep + expectedType interface{} + expectedError error + }{ + "AttributeName": { + tuple: Tuple{}, + step: AttributeName("test"), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "ElementKeyInt-no-ElementTypes": { + tuple: Tuple{}, + step: ElementKeyInt(123), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "ElementKeyInt-ElementTypes-found": { + tuple: Tuple{ElementTypes: []Type{String}}, + step: ElementKeyInt(0), + expectedType: String, + expectedError: nil, + }, + "ElementKeyInt-ElementTypes-negative": { + tuple: Tuple{ElementTypes: []Type{String}}, + step: ElementKeyInt(-1), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "ElementKeyInt-ElementTypes-overflow": { + tuple: Tuple{ElementTypes: []Type{String}}, + step: ElementKeyInt(1), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "ElementKeyString": { + tuple: Tuple{}, + step: ElementKeyString("test"), + expectedType: nil, + expectedError: ErrInvalidStep, + }, + "ElementKeyValue": { + tuple: Tuple{}, + 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.tuple.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 TestTupleEqual(t *testing.T) { t.Parallel() diff --git a/tftypes/type.go b/tftypes/type.go index 955e322f..4f0e4af7 100644 --- a/tftypes/type.go +++ b/tftypes/type.go @@ -12,6 +12,12 @@ import ( // implemented by the tftypes package. Types define the shape and // characteristics of data coming from or being sent to Terraform. type Type interface { + // AttributePathStepper requires each Type to implement the + // ApplyTerraform5AttributePathStep method, so Type is compatible with + // WalkAttributePath. The method should return the Type found at that + // AttributePath within the Type or ErrInvalidStep. + AttributePathStepper + // Is is used to determine what type a Type implementation is. It is // the recommended method for determining whether two types are // equivalent or not.