diff --git a/.changelog/112.txt b/.changelog/112.txt new file mode 100644 index 00000000..77cae455 --- /dev/null +++ b/.changelog/112.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +tftypes: Added `AttributePath` `LastStep()` method and `AttributePathStep` `Equal()` method +``` diff --git a/tftypes/attribute_path.go b/tftypes/attribute_path.go index 256756fe..fd91f96a 100644 --- a/tftypes/attribute_path.go +++ b/tftypes/attribute_path.go @@ -85,21 +85,9 @@ func (a *AttributePath) Equal(o *AttributePath) bool { } for pos, aStep := range a.Steps() { oStep := o.Steps()[pos] - switch aStep.(type) { - case AttributeName, ElementKeyString, ElementKeyInt: - if oStep != aStep { - return false - } - case ElementKeyValue: - oVal, ok := oStep.(ElementKeyValue) - if !ok { - return false - } - if !Value(aStep.(ElementKeyValue)).Equal(Value(oVal)) { - return false - } - default: - panic(fmt.Sprintf("unknown step %T in AttributePath.Equal", aStep)) + + if !aStep.Equal(oStep) { + return false } } return true @@ -129,6 +117,18 @@ func (a *AttributePath) NewError(err error) error { } } +// LastStep returns the last step in the path. If the path was +// empty, nil is returned. +func (a *AttributePath) LastStep() AttributePathStep { + steps := a.Steps() + + if len(steps) == 0 { + return nil + } + + return steps[len(steps)-1] +} + // WithAttributeName adds an AttributeName step to `a`, using `name` as the // attribute's name. `a` is copied, not modified. func (a *AttributePath) WithAttributeName(name string) *AttributePath { @@ -185,6 +185,9 @@ func (a *AttributePath) WithoutLastStep() *AttributePath { // indicating a specific attribute or element that is the next value in the // path. type AttributePathStep interface { + // Equal returns true if the AttributePathStep is equal to the other. + Equal(AttributePathStep) bool + unfulfillable() // make this interface fillable only by this package } @@ -199,6 +202,18 @@ var ( // AttributeName is the name of the attribute to be selected. type AttributeName string +// Equal returns true if the other AttributePathStep is an AttributeName and +// has the same value. +func (a AttributeName) Equal(other AttributePathStep) bool { + otherA, ok := other.(AttributeName) + + if !ok { + return false + } + + return string(a) == string(otherA) +} + func (a AttributeName) unfulfillable() {} // ElementKeyString is an AttributePathStep implementation that indicates the @@ -206,6 +221,18 @@ func (a AttributeName) unfulfillable() {} // The value of the ElementKeyString is the key of the element to select. type ElementKeyString string +// Equal returns true if the other AttributePathStep is an ElementKeyString and +// has the same value. +func (e ElementKeyString) Equal(other AttributePathStep) bool { + otherE, ok := other.(ElementKeyString) + + if !ok { + return false + } + + return string(e) == string(otherE) +} + func (e ElementKeyString) unfulfillable() {} // ElementKeyInt is an AttributePathStep implementation that indicates the next @@ -213,6 +240,18 @@ func (e ElementKeyString) unfulfillable() {} // value of the ElementKeyInt is the key of the element to select. type ElementKeyInt int64 +// Equal returns true if the other AttributePathStep is an ElementKeyInt and +// has the same value. +func (e ElementKeyInt) Equal(other AttributePathStep) bool { + otherE, ok := other.(ElementKeyInt) + + if !ok { + return false + } + + return int(e) == int(otherE) +} + func (e ElementKeyInt) unfulfillable() {} // ElementKeyValue is an AttributePathStep implementation that indicates the @@ -221,6 +260,18 @@ func (e ElementKeyInt) unfulfillable() {} // to select. type ElementKeyValue Value +// Equal returns true if the other AttributePathStep is an ElementKeyValue and +// has the same value. +func (e ElementKeyValue) Equal(other AttributePathStep) bool { + otherE, ok := other.(ElementKeyValue) + + if !ok { + return false + } + + return Value(e).Equal(Value(otherE)) +} + func (e ElementKeyValue) unfulfillable() {} // AttributePathStepper is an interface that types can implement to make them diff --git a/tftypes/attribute_path_test.go b/tftypes/attribute_path_test.go index 559bdb83..623c2485 100644 --- a/tftypes/attribute_path_test.go +++ b/tftypes/attribute_path_test.go @@ -523,6 +523,62 @@ func TestAttributePathEqual(t *testing.T) { } } +func TestAttributePathLastStep(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + path *AttributePath + expected AttributePathStep + }{ + "empty": { + path: NewAttributePath(), + expected: nil, + }, + "nil": { + path: nil, + expected: nil, + }, + "AttributeName": { + path: NewAttributePath().WithAttributeName("testing"), + expected: AttributeName("testing"), + }, + "AttributeName-AttributeName": { + path: NewAttributePath().WithAttributeName("testing").WithAttributeName("testing2"), + expected: AttributeName("testing2"), + }, + "AttributeName-AttributeName-AttributeName": { + path: NewAttributePath().WithElementKeyString("testing").WithAttributeName("testing2").WithAttributeName("testing3"), + expected: AttributeName("testing3"), + }, + "ElementKeyInt": { + path: NewAttributePath().WithElementKeyInt(1234), + expected: ElementKeyInt(1234), + }, + "ElementKeyString": { + path: NewAttributePath().WithElementKeyString("testing"), + expected: ElementKeyString("testing"), + }, + "ElementKeyValue": { + path: NewAttributePath().WithElementKeyValue(NewValue(String, "testing")), + expected: ElementKeyValue(NewValue(String, "testing")), + }, + } + + for name, tc := range testCases { + name, tc := name, tc + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tc.path.LastStep() + + if diff := cmp.Diff(tc.expected, got); diff != "" { + t.Errorf("Unexpected results (-wanted, +got): %s", diff) + } + }) + } +} + func TestAttributePathString(t *testing.T) { t.Parallel() type testCase struct { @@ -585,3 +641,203 @@ func TestAttributePathString(t *testing.T) { }) } } + +func TestAttributeNameEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attributeName AttributeName + other AttributePathStep + expected bool + }{ + "AttributeName-different": { + attributeName: AttributeName("test"), + other: AttributeName("other"), + expected: false, + }, + "AttributeName-equal": { + attributeName: AttributeName("test"), + other: AttributeName("test"), + expected: true, + }, + "ElementKeyInt": { + attributeName: AttributeName("test"), + other: ElementKeyInt(1), + expected: false, + }, + "ElementKeyString": { + attributeName: AttributeName("test"), + other: ElementKeyString("test"), + expected: false, + }, + "ElementKeyValue": { + attributeName: AttributeName("test"), + other: ElementKeyValue(NewValue(String, "test")), + expected: false, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tc.attributeName.Equal(tc.other) + + if diff := cmp.Diff(tc.expected, got); diff != "" { + t.Errorf("Unexpected results (-wanted, +got): %s", diff) + } + }) + } +} + +func TestElementKeyIntEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + elementKeyInt ElementKeyInt + other AttributePathStep + expected bool + }{ + "AttributeName": { + elementKeyInt: ElementKeyInt(1), + other: AttributeName("test"), + expected: false, + }, + "ElementKeyInt-different": { + elementKeyInt: ElementKeyInt(1), + other: ElementKeyInt(2), + expected: false, + }, + "ElementKeyInt-equal": { + elementKeyInt: ElementKeyInt(1), + other: ElementKeyInt(1), + expected: true, + }, + "ElementKeyString": { + elementKeyInt: ElementKeyInt(1), + other: ElementKeyString("test"), + expected: false, + }, + "ElementKeyValue": { + elementKeyInt: ElementKeyInt(1), + other: ElementKeyValue(NewValue(String, "test")), + expected: false, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tc.elementKeyInt.Equal(tc.other) + + if diff := cmp.Diff(tc.expected, got); diff != "" { + t.Errorf("Unexpected results (-wanted, +got): %s", diff) + } + }) + } +} + +func TestElementKeyStringEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + elementKeyString ElementKeyString + other AttributePathStep + expected bool + }{ + "AttributeName": { + elementKeyString: ElementKeyString("test"), + other: AttributeName("test"), + expected: false, + }, + "ElementKeyInt": { + elementKeyString: ElementKeyString("test"), + other: ElementKeyInt(1), + expected: false, + }, + "ElementKeyString-different": { + elementKeyString: ElementKeyString("test"), + other: ElementKeyString("other"), + expected: false, + }, + "ElementKeyString-equal": { + elementKeyString: ElementKeyString("test"), + other: ElementKeyString("test"), + expected: true, + }, + "ElementKeyValue": { + elementKeyString: ElementKeyString("test"), + other: ElementKeyValue(NewValue(String, "test")), + expected: false, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tc.elementKeyString.Equal(tc.other) + + if diff := cmp.Diff(tc.expected, got); diff != "" { + t.Errorf("Unexpected results (-wanted, +got): %s", diff) + } + }) + } +} + +func TestElementKeyValueEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + elementKeyValue ElementKeyValue + other AttributePathStep + expected bool + }{ + "AttributeName-different": { + elementKeyValue: ElementKeyValue(NewValue(String, "test")), + other: AttributeName("test"), + expected: false, + }, + "ElementKeyInt": { + elementKeyValue: ElementKeyValue(NewValue(String, "test")), + other: ElementKeyInt(1), + expected: false, + }, + "ElementKeyString": { + elementKeyValue: ElementKeyValue(NewValue(String, "test")), + other: ElementKeyString("test"), + expected: false, + }, + "ElementKeyValue-different": { + elementKeyValue: ElementKeyValue(NewValue(String, "test")), + other: ElementKeyValue(NewValue(String, "other")), + expected: false, + }, + "ElementKeyValue-equal": { + elementKeyValue: ElementKeyValue(NewValue(String, "test")), + other: ElementKeyValue(NewValue(String, "test")), + expected: true, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tc.elementKeyValue.Equal(tc.other) + + if diff := cmp.Diff(tc.expected, got); diff != "" { + t.Errorf("Unexpected results (-wanted, +got): %s", diff) + } + }) + } +}