Skip to content
This repository was archived by the owner on Jul 22, 2024. It is now read-only.

Commit caa7370

Browse files
committed
Make use of binaryencoder interface too
1 parent 8412676 commit caa7370

File tree

2 files changed

+98
-41
lines changed

2 files changed

+98
-41
lines changed

hashstructure.go

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package hashstructure
22

33
import (
4+
"encoding"
45
"encoding/binary"
56
"fmt"
67
"hash"
@@ -38,9 +39,10 @@ type HashOptions struct {
3839
// panic)
3940
UseStringer bool
4041

41-
// StringIgnoredStructs will attempt to .String() a struct if all of the
42-
// members of the struct are ignored for the purposes of hashing.
43-
StringIgnoredStructs bool
42+
// UnhashedStructFallback will attempt to make use of the BinaryEncoder and
43+
// Stringer interfaces (in that order) to hash structs that contain no
44+
// exported fields.
45+
UnhashedStructFallback bool
4446
}
4547

4648
// Format specifies the hashing process used. Different formats typically
@@ -120,27 +122,27 @@ func Hash(v interface{}, format Format, opts *HashOptions) (uint64, error) {
120122

121123
// Create our walker and walk the structure
122124
w := &walker{
123-
format: format,
124-
h: opts.Hasher,
125-
tag: opts.TagName,
126-
zeronil: opts.ZeroNil,
127-
ignorezerovalue: opts.IgnoreZeroValue,
128-
sets: opts.SlicesAsSets,
129-
stringer: opts.UseStringer,
130-
stringignoredstructs: opts.StringIgnoredStructs,
125+
format: format,
126+
h: opts.Hasher,
127+
tag: opts.TagName,
128+
zeronil: opts.ZeroNil,
129+
ignorezerovalue: opts.IgnoreZeroValue,
130+
sets: opts.SlicesAsSets,
131+
stringer: opts.UseStringer,
132+
unhashedstructfallback: opts.UnhashedStructFallback,
131133
}
132134
return w.visit(reflect.ValueOf(v), nil)
133135
}
134136

135137
type walker struct {
136-
format Format
137-
h hash.Hash64
138-
tag string
139-
zeronil bool
140-
ignorezerovalue bool
141-
sets bool
142-
stringer bool
143-
stringignoredstructs bool
138+
format Format
139+
h hash.Hash64
140+
tag string
141+
zeronil bool
142+
ignorezerovalue bool
143+
sets bool
144+
stringer bool
145+
unhashedstructfallback bool
144146
}
145147

146148
type visitOpts struct {
@@ -390,16 +392,26 @@ func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) {
390392
h = hashFinishUnordered(w.h, h)
391393
}
392394
}
393-
// no fields involved in the hash! try and string instead.
394-
if unhashedfields == l && w.stringignoredstructs {
395-
if impl, ok := parent.(fmt.Stringer); ok {
396-
w.h.Reset()
397-
_, err := w.h.Write([]byte(impl.String()))
395+
// no fields involved in the hash! try binary and string instead.
396+
if unhashedfields == l && w.unhashedstructfallback {
397+
var data []byte
398+
if impl, ok := parent.(encoding.BinaryMarshaler); ok {
399+
data, err = impl.MarshalBinary()
398400
if err != nil {
399401
return 0, err
400402
}
401-
return w.h.Sum64(), nil
402403
}
404+
405+
if impl, ok := parent.(fmt.Stringer); ok {
406+
data = []byte(impl.String())
407+
}
408+
409+
w.h.Reset()
410+
_, err := w.h.Write(data)
411+
if err != nil {
412+
return 0, err
413+
}
414+
return w.h.Sum64(), nil
403415
}
404416

405417
return h, nil

hashstructure_test.go

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -730,61 +730,106 @@ func (t *testHashablePointer) Hash() (uint64, error) {
730730
return 100, nil
731731
}
732732

733-
type Unexported struct {
733+
type UnexportedStringer struct {
734734
n int
735735
}
736736

737-
func (u Unexported) String() string {
737+
func (u UnexportedStringer) String() string {
738738
return fmt.Sprintf("%d", u.n)
739739
}
740740

741+
type UnexportedBinaryer struct {
742+
n int
743+
}
744+
745+
func (u UnexportedBinaryer) MarshalBinary() (data []byte, err error) {
746+
fmt.Println("Out:", []byte(fmt.Sprintf("%d", u.n)))
747+
return []byte(fmt.Sprintf("%d", u.n)), nil
748+
}
749+
741750
func TestHash_StringIgnoredStructs(t *testing.T) {
742751
cases := []struct {
743752
One, Two interface{}
744753
Match bool
745754
Err string
746755
}{
747756
{
748-
Unexported{n: 1},
749-
Unexported{n: 1},
757+
UnexportedStringer{n: 1},
758+
UnexportedStringer{n: 1},
759+
true,
760+
"",
761+
},
762+
{
763+
UnexportedStringer{n: 1},
764+
UnexportedStringer{n: 2},
765+
false,
766+
"",
767+
},
768+
{
769+
[]interface{}{UnexportedStringer{n: 1}},
770+
[]interface{}{UnexportedStringer{n: 1}},
771+
true,
772+
"",
773+
},
774+
{
775+
[]interface{}{UnexportedStringer{n: 1}},
776+
[]interface{}{UnexportedStringer{n: 2}},
777+
false,
778+
"",
779+
},
780+
{
781+
map[string]interface{}{"v": UnexportedStringer{n: 1}},
782+
map[string]interface{}{"v": UnexportedStringer{n: 1}},
783+
true,
784+
"",
785+
},
786+
{
787+
map[string]interface{}{"v": UnexportedStringer{n: 1}},
788+
map[string]interface{}{"v": UnexportedStringer{n: 2}},
789+
false,
790+
"",
791+
},
792+
{
793+
UnexportedBinaryer{n: 1},
794+
UnexportedBinaryer{n: 1},
750795
true,
751796
"",
752797
},
753798
{
754-
Unexported{n: 1},
755-
Unexported{n: 2},
799+
UnexportedBinaryer{n: 1},
800+
UnexportedBinaryer{n: 2},
756801
false,
757802
"",
758803
},
759804
{
760-
[]interface{}{Unexported{n: 1}},
761-
[]interface{}{Unexported{n: 1}},
805+
[]interface{}{UnexportedBinaryer{n: 1}},
806+
[]interface{}{UnexportedBinaryer{n: 1}},
762807
true,
763808
"",
764809
},
765810
{
766-
[]interface{}{Unexported{n: 1}},
767-
[]interface{}{Unexported{n: 2}},
811+
[]interface{}{UnexportedBinaryer{n: 1}},
812+
[]interface{}{UnexportedBinaryer{n: 2}},
768813
false,
769814
"",
770815
},
771816
{
772-
map[string]interface{}{"v": Unexported{n: 1}},
773-
map[string]interface{}{"v": Unexported{n: 1}},
817+
map[string]interface{}{"v": UnexportedBinaryer{n: 1}},
818+
map[string]interface{}{"v": UnexportedBinaryer{n: 1}},
774819
true,
775820
"",
776821
},
777822
{
778-
map[string]interface{}{"v": Unexported{n: 1}},
779-
map[string]interface{}{"v": Unexported{n: 2}},
823+
map[string]interface{}{"v": UnexportedBinaryer{n: 1}},
824+
map[string]interface{}{"v": UnexportedBinaryer{n: 2}},
780825
false,
781826
"",
782827
},
783828
}
784829

785830
for i, tc := range cases {
786831
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
787-
one, err := Hash(tc.One, testFormat, &HashOptions{StringIgnoredStructs: true})
832+
one, err := Hash(tc.One, testFormat, &HashOptions{UnhashedStructFallback: true})
788833
if tc.Err != "" {
789834
if err == nil {
790835
t.Fatal("expected error")
@@ -800,7 +845,7 @@ func TestHash_StringIgnoredStructs(t *testing.T) {
800845
t.Fatalf("Failed to hash %#v: %s", tc.One, err)
801846
}
802847

803-
two, err := Hash(tc.Two, testFormat, &HashOptions{StringIgnoredStructs: true})
848+
two, err := Hash(tc.Two, testFormat, &HashOptions{UnhashedStructFallback: true})
804849
if err != nil {
805850
t.Fatalf("Failed to hash %#v: %s", tc.Two, err)
806851
}

0 commit comments

Comments
 (0)