diff --git a/hashstructure.go b/hashstructure.go index 9d102ce..f0e4de9 100644 --- a/hashstructure.go +++ b/hashstructure.go @@ -37,6 +37,10 @@ type HashOptions struct { // precedence (meaning that if the type doesn't implement fmt.Stringer, we // panic) UseStringer bool + + // StringIgnoredStructs will attempt to .String() a struct if all of the + // members of the struct are ignored for the purposes of hashing. + StringIgnoredStructs bool } // Format specifies the hashing process used. Different formats typically @@ -116,25 +120,27 @@ func Hash(v interface{}, format Format, opts *HashOptions) (uint64, error) { // Create our walker and walk the structure w := &walker{ - format: format, - h: opts.Hasher, - tag: opts.TagName, - zeronil: opts.ZeroNil, - ignorezerovalue: opts.IgnoreZeroValue, - sets: opts.SlicesAsSets, - stringer: opts.UseStringer, + format: format, + h: opts.Hasher, + tag: opts.TagName, + zeronil: opts.ZeroNil, + ignorezerovalue: opts.IgnoreZeroValue, + sets: opts.SlicesAsSets, + stringer: opts.UseStringer, + stringignoredstructs: opts.StringIgnoredStructs, } return w.visit(reflect.ValueOf(v), nil) } type walker struct { - format Format - h hash.Hash64 - tag string - zeronil bool - ignorezerovalue bool - sets bool - stringer bool + format Format + h hash.Hash64 + tag string + zeronil bool + ignorezerovalue bool + sets bool + stringer bool + stringignoredstructs bool } type visitOpts struct { @@ -385,11 +391,14 @@ func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) { } } // no fields involved in the hash! try and string instead. - if unhashedfields == l { - if impl, ok := v.Interface().(fmt.Stringer); ok { + if unhashedfields == l && w.stringignoredstructs { + if impl, ok := parent.(fmt.Stringer); ok { w.h.Reset() _, err := w.h.Write([]byte(impl.String())) - return w.h.Sum64(), err + if err != nil { + return 0, err + } + return w.h.Sum64(), nil } } diff --git a/hashstructure_test.go b/hashstructure_test.go index 650e353..f69b319 100644 --- a/hashstructure_test.go +++ b/hashstructure_test.go @@ -738,7 +738,7 @@ func (u Unexported) String() string { return fmt.Sprintf("%d", u.n) } -func TestHash_unexported_stringable(t *testing.T) { +func TestHash_StringIgnoredStructs(t *testing.T) { cases := []struct { One, Two interface{} Match bool @@ -784,7 +784,7 @@ func TestHash_unexported_stringable(t *testing.T) { for i, tc := range cases { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { - one, err := Hash(tc.One, testFormat, &HashOptions{}) + one, err := Hash(tc.One, testFormat, &HashOptions{StringIgnoredStructs: true}) if tc.Err != "" { if err == nil { t.Fatal("expected error") @@ -800,7 +800,7 @@ func TestHash_unexported_stringable(t *testing.T) { t.Fatalf("Failed to hash %#v: %s", tc.One, err) } - two, err := Hash(tc.Two, testFormat, nil) + two, err := Hash(tc.Two, testFormat, &HashOptions{StringIgnoredStructs: true}) if err != nil { t.Fatalf("Failed to hash %#v: %s", tc.Two, err) }