diff --git a/cty/path.go b/cty/path.go index bf1a7c15..b3144495 100644 --- a/cty/path.go +++ b/cty/path.go @@ -71,6 +71,48 @@ func (p Path) GetAttr(name string) Path { return ret } +// Equals compares 2 Paths for exact equality. +func (p Path) Equals(other Path) bool { + if len(p) != len(other) { + return false + } + + for i := range p { + pv := p[i] + switch pv := pv.(type) { + case GetAttrStep: + ov, ok := other[i].(GetAttrStep) + if !ok || pv != ov { + return false + } + case IndexStep: + ov, ok := other[i].(IndexStep) + if !ok { + return false + } + + if !pv.Key.RawEquals(ov.Key) { + return false + } + default: + // Any invalid steps default to evaluating false. + return false + } + } + + return true + +} + +// HasPrefix determines if the path p contains the provided prefix. +func (p Path) HasPrefix(prefix Path) bool { + if len(prefix) > len(p) { + return false + } + + return p[:len(prefix)].Equals(prefix) +} + // GetAttrPath is a convenience method to start a new Path with a GetAttrStep. func GetAttrPath(name string) Path { return Path{}.GetAttr(name) diff --git a/cty/path_test.go b/cty/path_test.go index 9c2472af..4db9cfee 100644 --- a/cty/path_test.go +++ b/cty/path_test.go @@ -123,3 +123,135 @@ func TestPathApply(t *testing.T) { }) } } + +func TestPathEquals(t *testing.T) { + tests := []struct { + A, B cty.Path + Equal bool + Prefix bool + }{ + { + A: nil, + B: nil, + Equal: true, + Prefix: true, + }, + { + A: cty.Path{}, + B: cty.Path{}, + Equal: true, + Prefix: true, + }, + { + A: cty.Path{nil}, + B: cty.Path{cty.GetAttrStep{Name: "attr"}}, + }, + { + A: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.UnknownVal(cty.String)}, + cty.GetAttrStep{Name: "attr"}, + }, + B: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.StringVal("key")}, + cty.GetAttrStep{Name: "attr"}, + }, + }, + { + A: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.ListVal([]cty.Value{cty.UnknownVal(cty.String)})}, + cty.GetAttrStep{Name: "attr"}, + }, + B: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.ListVal([]cty.Value{cty.StringVal("known")})}, + cty.GetAttrStep{Name: "attr"}, + }, + }, + { + A: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.UnknownVal(cty.String)}, + }, + B: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.StringVal("known")}, + cty.GetAttrStep{Name: "attr"}, + }, + }, + { + A: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.StringVal("known")}, + }, + B: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.StringVal("known")}, + cty.GetAttrStep{Name: "attr"}, + }, + }, + { + A: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.StringVal("known")}, + cty.GetAttrStep{Name: "attr"}, + }, + B: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.StringVal("known")}, + }, + Prefix: true, + }, + { + A: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.UnknownVal(cty.String)}, + }, + B: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.UnknownVal(cty.String)}, + }, + Prefix: true, + Equal: true, + }, + { + A: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.NumberFloatVal(0)}, + cty.GetAttrStep{Name: "attr"}, + }, + B: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.NumberIntVal(0)}, + cty.GetAttrStep{Name: "attr"}, + }, + Equal: true, + Prefix: true, + }, + { + A: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.NumberIntVal(1)}, + cty.GetAttrStep{Name: "attr"}, + }, + B: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.NumberIntVal(0)}, + cty.GetAttrStep{Name: "attr"}, + }, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("%d-%#v", i, test.A), func(t *testing.T) { + if test.Equal != test.A.Equals(test.B) { + t.Fatalf("%#v.Equals(%#v) != %t", test.A, test.B, test.Equal) + } + if test.Prefix != test.A.HasPrefix(test.B) { + t.Fatalf("%#v.HasPrefix(%#v) != %t", test.A, test.B, test.Prefix) + } + }) + } +}