Skip to content
This repository was archived by the owner on Jul 22, 2024. It is now read-only.
Open
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
18 changes: 18 additions & 0 deletions hashstructure.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,10 @@ func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) {
if impl, ok := parent.(Includable); ok {
include = impl
}
var sliceAsSets SliceAsSetsable
if impl, ok := parent.(SliceAsSetsable); ok {
sliceAsSets = impl
}

if impl, ok := parent.(Hashable); ok {
return impl.Hash()
Expand All @@ -294,6 +298,9 @@ func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) {
if impl, ok := parentptr.(Includable); ok {
include = impl
}
if impl, ok := parentptr.(SliceAsSetsable); ok {
sliceAsSets = impl
}

if impl, ok := parentptr.(Hashable); ok {
return impl.Hash()
Expand Down Expand Up @@ -353,6 +360,17 @@ func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) {
}
}

// Check if we implement SliceAsSetsable
if sliceAsSets != nil {
sas, err := sliceAsSets.SliceAsSets(fieldType.Name, innerV)
if err != nil {
return 0, err
}
if sas {
f |= visitFlagSet
}
}

switch tag {
case "set":
f |= visitFlagSet
Expand Down
65 changes: 65 additions & 0 deletions hashstructure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,55 @@ func TestHash_hashable(t *testing.T) {
}
}

func TestHash_sliceAsSetsable(t *testing.T) {
cases := []struct {
One, Two interface{}
Match bool
}{
{
testSliceAsSetsable{Kind: "map", Slice: []string{"1", "2"}, SliceSet: []string{"a", "b"}},
testSliceAsSetsable{Kind: "map", Slice: []string{"2", "1"}, SliceSet: []string{"a", "b"}},
true,
},
{
testSliceAsSetsable{Kind: "map", Slice: []string{"1", "2"}, SliceSet: []string{"a", "b"}},
testSliceAsSetsable{Kind: "map", Slice: []string{"2", "1"}, SliceSet: []string{"b", "a"}},
true,
},
{
testSliceAsSetsable{Kind: "seq", Slice: []string{"1", "2"}, SliceSet: []string{"a", "b"}},
testSliceAsSetsable{Kind: "seq", Slice: []string{"2", "1"}, SliceSet: []string{"a", "b"}},
false,
},
{
testSliceAsSetsable{Kind: "seq", Slice: []string{"1", "2"}, SliceSet: []string{"a", "b"}},
testSliceAsSetsable{Kind: "seq", Slice: []string{"1", "2"}, SliceSet: []string{"b", "a"}},
true,
},
}

for _, tc := range cases {
one, err := Hash(tc.One, testFormat, nil)
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.One, err)
}
two, err := Hash(tc.Two, testFormat, nil)
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.Two, err)
}

// Zero is always wrong
if one == 0 {
t.Fatalf("zero hash: %#v", tc.One)
}

// Compare
if (one == two) != tc.Match {
t.Fatalf("bad, expected: %#v\n\n%#v\n\n%#v", tc.Match, tc.One, tc.Two)
}
}
}

type testIncludable struct {
Value string
Ignore string
Expand Down Expand Up @@ -727,3 +776,19 @@ func (t *testHashablePointer) Hash() (uint64, error) {

return 100, nil
}

type testSliceAsSetsable struct {
Kind string
Slice []string
SliceSet []string
}

func (t testSliceAsSetsable) SliceAsSets(field string, v interface{}) (bool, error) {
switch t.Kind {
case "map":
return true, nil
case "seq":
return field == "SliceSet", nil
}
return false, nil
}
7 changes: 7 additions & 0 deletions include.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,10 @@ type IncludableMap interface {
type Hashable interface {
Hash() (uint64, error)
}

// SliceAsSetsable is an interface that can optionally be implemented by
// a struct. It will be called for each field in the struct to check whether
// the filed should be treated as set.
type SliceAsSetsable interface {
SliceAsSets(field string, v interface{}) (bool, error)
}