pax_global_header00006660000000000000000000000064137563411370014524gustar00rootroot0000000000000052 comment=191a1e82bbeb456444f1a9bb9db2e4a20989cd98 hashstructure-1.1.0/000077500000000000000000000000001375634113700144275ustar00rootroot00000000000000hashstructure-1.1.0/.github/000077500000000000000000000000001375634113700157675ustar00rootroot00000000000000hashstructure-1.1.0/.github/workflows/000077500000000000000000000000001375634113700200245ustar00rootroot00000000000000hashstructure-1.1.0/.github/workflows/test.yml000066400000000000000000000006041375634113700215260ustar00rootroot00000000000000on: [push, pull_request] name: Test jobs: test: strategy: matrix: go-version: [1.15.x] os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - name: Install Go uses: actions/setup-go@v2 with: go-version: ${{ matrix.go-version }} - name: Checkout code uses: actions/checkout@v2 - name: Test run: go test ./... hashstructure-1.1.0/LICENSE000066400000000000000000000020751375634113700154400ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2016 Mitchell Hashimoto Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. hashstructure-1.1.0/README.md000066400000000000000000000034211375634113700157060ustar00rootroot00000000000000# hashstructure [![GoDoc](https://godoc.org/github.com/mitchellh/hashstructure?status.svg)](https://godoc.org/github.com/mitchellh/hashstructure) hashstructure is a Go library for creating a unique hash value for arbitrary values in Go. This can be used to key values in a hash (for use in a map, set, etc.) that are complex. The most common use case is comparing two values without sending data across the network, caching values locally (de-dup), and so on. ## Features * Hash any arbitrary Go value, including complex types. * Tag a struct field to ignore it and not affect the hash value. * Tag a slice type struct field to treat it as a set where ordering doesn't affect the hash code but the field itself is still taken into account to create the hash value. * Optionally, specify a custom hash function to optimize for speed, collision avoidance for your data set, etc. * Optionally, hash the output of `.String()` on structs that implement fmt.Stringer, allowing effective hashing of time.Time * Optionally, override the hashing process by implementing `Hashable`. ## Installation Standard `go get`: ``` $ go get github.com/mitchellh/hashstructure ``` ## Usage & Example For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/hashstructure). A quick code example is shown below: ```go type ComplexStruct struct { Name string Age uint Metadata map[string]interface{} } v := ComplexStruct{ Name: "mitchellh", Age: 64, Metadata: map[string]interface{}{ "car": true, "location": "California", "siblings": []string{"Bob", "John"}, }, } hash, err := hashstructure.Hash(v, nil) if err != nil { panic(err) } fmt.Printf("%d", hash) // Output: // 2307517237273902113 ``` hashstructure-1.1.0/go.mod000066400000000000000000000000631375634113700155340ustar00rootroot00000000000000module github.com/mitchellh/hashstructure go 1.14 hashstructure-1.1.0/hashstructure.go000066400000000000000000000231151375634113700176640ustar00rootroot00000000000000package hashstructure import ( "encoding/binary" "fmt" "hash" "hash/fnv" "reflect" "time" ) // ErrNotStringer is returned when there's an error with hash:"string" type ErrNotStringer struct { Field string } // Error implements error for ErrNotStringer func (ens *ErrNotStringer) Error() string { return fmt.Sprintf("hashstructure: %s has hash:\"string\" set, but does not implement fmt.Stringer", ens.Field) } // HashOptions are options that are available for hashing. type HashOptions struct { // Hasher is the hash function to use. If this isn't set, it will // default to FNV. Hasher hash.Hash64 // TagName is the struct tag to look at when hashing the structure. // By default this is "hash". TagName string // ZeroNil is flag determining if nil pointer should be treated equal // to a zero value of pointed type. By default this is false. ZeroNil bool // IgnoreZeroValue is determining if zero value fields should be // ignored for hash calculation. IgnoreZeroValue bool // SlicesAsSets assumes that a `set` tag is always present for slices. // Default is false (in which case the tag is used instead) SlicesAsSets bool // UseStringer will attempt to use fmt.Stringer aways. If the struct // doesn't implement fmt.Stringer, it'll fall back to trying usual tricks. // If this is true, and the "string" tag is also set, the tag takes // precedense (meaning that if the type doesn't implement fmt.Stringer, we // panic) UseStringer bool } // Hash returns the hash value of an arbitrary value. // // If opts is nil, then default options will be used. See HashOptions // for the default values. The same *HashOptions value cannot be used // concurrently. None of the values within a *HashOptions struct are // safe to read/write while hashing is being done. // // Notes on the value: // // * Unexported fields on structs are ignored and do not affect the // hash value. // // * Adding an exported field to a struct with the zero value will change // the hash value. // // For structs, the hashing can be controlled using tags. For example: // // struct { // Name string // UUID string `hash:"ignore"` // } // // The available tag values are: // // * "ignore" or "-" - The field will be ignored and not affect the hash code. // // * "set" - The field will be treated as a set, where ordering doesn't // affect the hash code. This only works for slices. // // * "string" - The field will be hashed as a string, only works when the // field implements fmt.Stringer // func Hash(v interface{}, opts *HashOptions) (uint64, error) { // Create default options if opts == nil { opts = &HashOptions{} } if opts.Hasher == nil { opts.Hasher = fnv.New64() } if opts.TagName == "" { opts.TagName = "hash" } // Reset the hash opts.Hasher.Reset() // Create our walker and walk the structure w := &walker{ h: opts.Hasher, tag: opts.TagName, zeronil: opts.ZeroNil, ignorezerovalue: opts.IgnoreZeroValue, sets: opts.SlicesAsSets, stringer: opts.UseStringer, } return w.visit(reflect.ValueOf(v), nil) } type walker struct { h hash.Hash64 tag string zeronil bool ignorezerovalue bool sets bool stringer bool } type visitOpts struct { // Flags are a bitmask of flags to affect behavior of this visit Flags visitFlag // Information about the struct containing this field Struct interface{} StructField string } var timeType = reflect.TypeOf(time.Time{}) func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) { t := reflect.TypeOf(0) // Loop since these can be wrapped in multiple layers of pointers // and interfaces. for { // If we have an interface, dereference it. We have to do this up // here because it might be a nil in there and the check below must // catch that. if v.Kind() == reflect.Interface { v = v.Elem() continue } if v.Kind() == reflect.Ptr { if w.zeronil { t = v.Type().Elem() } v = reflect.Indirect(v) continue } break } // If it is nil, treat it like a zero. if !v.IsValid() { v = reflect.Zero(t) } // Binary writing can use raw ints, we have to convert to // a sized-int, we'll choose the largest... switch v.Kind() { case reflect.Int: v = reflect.ValueOf(int64(v.Int())) case reflect.Uint: v = reflect.ValueOf(uint64(v.Uint())) case reflect.Bool: var tmp int8 if v.Bool() { tmp = 1 } v = reflect.ValueOf(tmp) } k := v.Kind() // We can shortcut numeric values by directly binary writing them if k >= reflect.Int && k <= reflect.Complex64 { // A direct hash calculation w.h.Reset() err := binary.Write(w.h, binary.LittleEndian, v.Interface()) return w.h.Sum64(), err } switch v.Type() { case timeType: w.h.Reset() b, err := v.Interface().(time.Time).MarshalBinary() if err != nil { return 0, err } err = binary.Write(w.h, binary.LittleEndian, b) return w.h.Sum64(), err } switch k { case reflect.Array: var h uint64 l := v.Len() for i := 0; i < l; i++ { current, err := w.visit(v.Index(i), nil) if err != nil { return 0, err } h = hashUpdateOrdered(w.h, h, current) } return h, nil case reflect.Map: var includeMap IncludableMap if opts != nil && opts.Struct != nil { if v, ok := opts.Struct.(IncludableMap); ok { includeMap = v } } // Build the hash for the map. We do this by XOR-ing all the key // and value hashes. This makes it deterministic despite ordering. var h uint64 for _, k := range v.MapKeys() { v := v.MapIndex(k) if includeMap != nil { incl, err := includeMap.HashIncludeMap( opts.StructField, k.Interface(), v.Interface()) if err != nil { return 0, err } if !incl { continue } } kh, err := w.visit(k, nil) if err != nil { return 0, err } vh, err := w.visit(v, nil) if err != nil { return 0, err } fieldHash := hashUpdateOrdered(w.h, kh, vh) h = hashUpdateUnordered(h, fieldHash) } return h, nil case reflect.Struct: parent := v.Interface() var include Includable if impl, ok := parent.(Includable); ok { include = impl } if impl, ok := parent.(Hashable); ok { return impl.Hash() } // If we can address this value, check if the pointer value // implements our interfaces and use that if so. if v.CanAddr() { vptr := v.Addr() parentptr := vptr.Interface() if impl, ok := parentptr.(Includable); ok { include = impl } if impl, ok := parentptr.(Hashable); ok { return impl.Hash() } } t := v.Type() h, err := w.visit(reflect.ValueOf(t.Name()), nil) if err != nil { return 0, err } l := v.NumField() for i := 0; i < l; i++ { if innerV := v.Field(i); v.CanSet() || t.Field(i).Name != "_" { var f visitFlag fieldType := t.Field(i) if fieldType.PkgPath != "" { // Unexported continue } tag := fieldType.Tag.Get(w.tag) if tag == "ignore" || tag == "-" { // Ignore this field continue } if w.ignorezerovalue { zeroVal := reflect.Zero(reflect.TypeOf(innerV.Interface())).Interface() if innerV.Interface() == zeroVal { continue } } // if string is set, use the string value if tag == "string" || w.stringer { if impl, ok := innerV.Interface().(fmt.Stringer); ok { innerV = reflect.ValueOf(impl.String()) } else if tag == "string" { // We only show this error if the tag explicitly // requests a stringer. return 0, &ErrNotStringer{ Field: v.Type().Field(i).Name, } } } // Check if we implement includable and check it if include != nil { incl, err := include.HashInclude(fieldType.Name, innerV) if err != nil { return 0, err } if !incl { continue } } switch tag { case "set": f |= visitFlagSet } kh, err := w.visit(reflect.ValueOf(fieldType.Name), nil) if err != nil { return 0, err } vh, err := w.visit(innerV, &visitOpts{ Flags: f, Struct: parent, StructField: fieldType.Name, }) if err != nil { return 0, err } fieldHash := hashUpdateOrdered(w.h, kh, vh) h = hashUpdateUnordered(h, fieldHash) } } return h, nil case reflect.Slice: // We have two behaviors here. If it isn't a set, then we just // visit all the elements. If it is a set, then we do a deterministic // hash code. var h uint64 var set bool if opts != nil { set = (opts.Flags & visitFlagSet) != 0 } l := v.Len() for i := 0; i < l; i++ { current, err := w.visit(v.Index(i), nil) if err != nil { return 0, err } if set || w.sets { h = hashUpdateUnordered(h, current) } else { h = hashUpdateOrdered(w.h, h, current) } } return h, nil case reflect.String: // Directly hash w.h.Reset() _, err := w.h.Write([]byte(v.String())) return w.h.Sum64(), err default: return 0, fmt.Errorf("unknown kind to hash: %s", k) } } func hashUpdateOrdered(h hash.Hash64, a, b uint64) uint64 { // For ordered updates, use a real hash function h.Reset() // We just panic if the binary writes fail because we are writing // an int64 which should never be fail-able. e1 := binary.Write(h, binary.LittleEndian, a) e2 := binary.Write(h, binary.LittleEndian, b) if e1 != nil { panic(e1) } if e2 != nil { panic(e2) } return h.Sum64() } func hashUpdateUnordered(a, b uint64) uint64 { return a ^ b } // visitFlag is used as a bitmask for affecting visit behavior type visitFlag uint const ( visitFlagInvalid visitFlag = iota visitFlagSet = iota << 1 ) hashstructure-1.1.0/hashstructure_examples_test.go000066400000000000000000000007341375634113700226230ustar00rootroot00000000000000package hashstructure import ( "fmt" ) func ExampleHash() { type ComplexStruct struct { Name string Age uint Metadata map[string]interface{} } v := ComplexStruct{ Name: "mitchellh", Age: 64, Metadata: map[string]interface{}{ "car": true, "location": "California", "siblings": []string{"Bob", "John"}, }, } hash, err := Hash(v, nil) if err != nil { panic(err) } fmt.Printf("%d", hash) // Output: // 6691276962590150517 } hashstructure-1.1.0/hashstructure_test.go000066400000000000000000000315561375634113700207330ustar00rootroot00000000000000package hashstructure import ( "fmt" "strings" "testing" "time" ) func TestHash_identity(t *testing.T) { cases := []interface{}{ nil, "foo", 42, true, false, []string{"foo", "bar"}, []interface{}{1, nil, "foo"}, map[string]string{"foo": "bar"}, map[interface{}]string{"foo": "bar"}, map[interface{}]interface{}{"foo": "bar", "bar": 0}, struct { Foo string Bar []interface{} }{ Foo: "foo", Bar: []interface{}{nil, nil, nil}, }, &struct { Foo string Bar []interface{} }{ Foo: "foo", Bar: []interface{}{nil, nil, nil}, }, } for _, tc := range cases { // We run the test 100 times to try to tease out variability // in the runtime in terms of ordering. valuelist := make([]uint64, 100) for i := range valuelist { v, err := Hash(tc, nil) if err != nil { t.Fatalf("Error: %s\n\n%#v", err, tc) } valuelist[i] = v } // Zero is always wrong if valuelist[0] == 0 { t.Fatalf("zero hash: %#v", tc) } // Make sure all the values match t.Logf("%#v: %d", tc, valuelist[0]) for i := 1; i < len(valuelist); i++ { if valuelist[i] != valuelist[0] { t.Fatalf("non-matching: %d, %d\n\n%#v", i, 0, tc) } } } } func TestHash_equal(t *testing.T) { type testFoo struct{ Name string } type testBar struct{ Name string } now := time.Now() cases := []struct { One, Two interface{} Match bool }{ { map[string]string{"foo": "bar"}, map[interface{}]string{"foo": "bar"}, true, }, { map[string]interface{}{"1": "1"}, map[string]interface{}{"1": "1", "2": "2"}, false, }, { struct{ Fname, Lname string }{"foo", "bar"}, struct{ Fname, Lname string }{"bar", "foo"}, false, }, { struct{ Lname, Fname string }{"foo", "bar"}, struct{ Fname, Lname string }{"foo", "bar"}, false, }, { struct{ Lname, Fname string }{"foo", "bar"}, struct{ Fname, Lname string }{"bar", "foo"}, true, }, { testFoo{"foo"}, testBar{"foo"}, false, }, { struct { Foo string unexported string }{ Foo: "bar", unexported: "baz", }, struct { Foo string unexported string }{ Foo: "bar", unexported: "bang", }, true, }, { struct { testFoo Foo string }{ Foo: "bar", testFoo: testFoo{Name: "baz"}, }, struct { testFoo Foo string }{ Foo: "bar", }, true, }, { struct { Foo string }{ Foo: "bar", }, struct { testFoo Foo string }{ Foo: "bar", }, true, }, { now, // contains monotonic clock time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), now.Nanosecond(), now.Location()), // does not contain monotonic clock true, }, } for i, tc := range cases { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { t.Logf("Hashing: %#v", tc.One) one, err := Hash(tc.One, nil) t.Logf("Result: %d", one) if err != nil { t.Fatalf("Failed to hash %#v: %s", tc.One, err) } t.Logf("Hashing: %#v", tc.Two) two, err := Hash(tc.Two, nil) t.Logf("Result: %d", two) 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) } }) } } func TestHash_equalIgnore(t *testing.T) { type Test1 struct { Name string UUID string `hash:"ignore"` } type Test2 struct { Name string UUID string `hash:"-"` } type TestTime struct { Name string Time time.Time `hash:"string"` } type TestTime2 struct { Name string Time time.Time } now := time.Now() cases := []struct { One, Two interface{} Match bool }{ { Test1{Name: "foo", UUID: "foo"}, Test1{Name: "foo", UUID: "bar"}, true, }, { Test1{Name: "foo", UUID: "foo"}, Test1{Name: "foo", UUID: "foo"}, true, }, { Test2{Name: "foo", UUID: "foo"}, Test2{Name: "foo", UUID: "bar"}, true, }, { Test2{Name: "foo", UUID: "foo"}, Test2{Name: "foo", UUID: "foo"}, true, }, { TestTime{Name: "foo", Time: now}, TestTime{Name: "foo", Time: time.Time{}}, false, }, { TestTime{Name: "foo", Time: now}, TestTime{Name: "foo", Time: now}, true, }, { TestTime2{Name: "foo", Time: now}, TestTime2{Name: "foo", Time: time.Time{}}, false, }, { TestTime2{Name: "foo", Time: now}, TestTime2{Name: "foo", Time: time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), now.Nanosecond(), now.Location()), }, true, }, } for _, tc := range cases { one, err := Hash(tc.One, nil) if err != nil { t.Fatalf("Failed to hash %#v: %s", tc.One, err) } two, err := Hash(tc.Two, 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) } } } func TestHash_stringTagError(t *testing.T) { type Test1 struct { Name string BrokenField string `hash:"string"` } type Test2 struct { Name string BustedField int `hash:"string"` } type Test3 struct { Name string Time time.Time `hash:"string"` } cases := []struct { Test interface{} Field string }{ { Test1{Name: "foo", BrokenField: "bar"}, "BrokenField", }, { Test2{Name: "foo", BustedField: 23}, "BustedField", }, { Test3{Name: "foo", Time: time.Now()}, "", }, } for _, tc := range cases { _, err := Hash(tc.Test, nil) if err != nil { if ens, ok := err.(*ErrNotStringer); ok { if ens.Field != tc.Field { t.Fatalf("did not get expected field %#v: got %s wanted %s", tc.Test, ens.Field, tc.Field) } } else { t.Fatalf("unknown error %#v: got %s", tc, err) } } } } func TestHash_equalNil(t *testing.T) { type Test struct { Str *string Int *int Map map[string]string Slice []string } cases := []struct { One, Two interface{} ZeroNil bool Match bool }{ { Test{ Str: nil, Int: nil, Map: nil, Slice: nil, }, Test{ Str: new(string), Int: new(int), Map: make(map[string]string), Slice: make([]string, 0), }, true, true, }, { Test{ Str: nil, Int: nil, Map: nil, Slice: nil, }, Test{ Str: new(string), Int: new(int), Map: make(map[string]string), Slice: make([]string, 0), }, false, false, }, { nil, 0, true, true, }, { nil, 0, false, true, }, } for _, tc := range cases { one, err := Hash(tc.One, &HashOptions{ZeroNil: tc.ZeroNil}) if err != nil { t.Fatalf("Failed to hash %#v: %s", tc.One, err) } two, err := Hash(tc.Two, &HashOptions{ZeroNil: tc.ZeroNil}) 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) } } } func TestHash_equalSet(t *testing.T) { type Test struct { Name string Friends []string `hash:"set"` } cases := []struct { One, Two interface{} Match bool }{ { Test{Name: "foo", Friends: []string{"foo", "bar"}}, Test{Name: "foo", Friends: []string{"bar", "foo"}}, true, }, { Test{Name: "foo", Friends: []string{"foo", "bar"}}, Test{Name: "foo", Friends: []string{"foo", "bar"}}, true, }, } for _, tc := range cases { one, err := Hash(tc.One, nil) if err != nil { t.Fatalf("Failed to hash %#v: %s", tc.One, err) } two, err := Hash(tc.Two, 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) } } } func TestHash_includable(t *testing.T) { cases := []struct { One, Two interface{} Match bool }{ { testIncludable{Value: "foo"}, testIncludable{Value: "foo"}, true, }, { testIncludable{Value: "foo", Ignore: "bar"}, testIncludable{Value: "foo"}, true, }, { testIncludable{Value: "foo", Ignore: "bar"}, testIncludable{Value: "bar"}, false, }, } for _, tc := range cases { one, err := Hash(tc.One, nil) if err != nil { t.Fatalf("Failed to hash %#v: %s", tc.One, err) } two, err := Hash(tc.Two, 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) } } } func TestHash_ignoreZeroValue(t *testing.T) { cases := []struct { IgnoreZeroValue bool }{ { IgnoreZeroValue: true, }, { IgnoreZeroValue: false, }, } structA := struct { Foo string Bar string }{ Foo: "foo", Bar: "bar", } structB := struct { Foo string Bar string Baz string }{ Foo: "foo", Bar: "bar", } for _, tc := range cases { hashA, err := Hash(structA, &HashOptions{IgnoreZeroValue: tc.IgnoreZeroValue}) if err != nil { t.Fatalf("Failed to hash %#v: %s", structA, err) } hashB, err := Hash(structB, &HashOptions{IgnoreZeroValue: tc.IgnoreZeroValue}) if err != nil { t.Fatalf("Failed to hash %#v: %s", structB, err) } if (hashA == hashB) != tc.IgnoreZeroValue { t.Fatalf("bad, expected: %#v\n\n%d\n\n%d", tc.IgnoreZeroValue, hashA, hashB) } } } func TestHash_includableMap(t *testing.T) { cases := []struct { One, Two interface{} Match bool }{ { testIncludableMap{Map: map[string]string{"foo": "bar"}}, testIncludableMap{Map: map[string]string{"foo": "bar"}}, true, }, { testIncludableMap{Map: map[string]string{"foo": "bar", "ignore": "true"}}, testIncludableMap{Map: map[string]string{"foo": "bar"}}, true, }, { testIncludableMap{Map: map[string]string{"foo": "bar", "ignore": "true"}}, testIncludableMap{Map: map[string]string{"bar": "baz"}}, false, }, } for _, tc := range cases { one, err := Hash(tc.One, nil) if err != nil { t.Fatalf("Failed to hash %#v: %s", tc.One, err) } two, err := Hash(tc.Two, 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) } } } func TestHash_hashable(t *testing.T) { cases := []struct { One, Two interface{} Match bool Err string }{ { testHashable{Value: "foo"}, &testHashablePointer{Value: "foo"}, true, "", }, { testHashable{Value: "foo1"}, &testHashablePointer{Value: "foo2"}, true, "", }, { testHashable{Value: "foo"}, &testHashablePointer{Value: "bar"}, false, "", }, { testHashable{Value: "nofoo"}, &testHashablePointer{Value: "bar"}, true, "", }, { testHashable{Value: "bar", Err: fmt.Errorf("oh no")}, testHashable{Value: "bar"}, true, "oh no", }, } for i, tc := range cases { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { one, err := Hash(tc.One, nil) if tc.Err != "" { if err == nil { t.Fatal("expected error") } if !strings.Contains(err.Error(), tc.Err) { t.Fatalf("expected error to contain %q, got: %s", tc.Err, err) } return } if err != nil { t.Fatalf("Failed to hash %#v: %s", tc.One, err) } two, err := Hash(tc.Two, 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 } func (t testIncludable) HashInclude(field string, v interface{}) (bool, error) { return field != "Ignore", nil } type testIncludableMap struct { Map map[string]string } func (t testIncludableMap) HashIncludeMap(field string, k, v interface{}) (bool, error) { if field != "Map" { return true, nil } if s, ok := k.(string); ok && s == "ignore" { return false, nil } return true, nil } type testHashable struct { Value string Err error } func (t testHashable) Hash() (uint64, error) { if t.Err != nil { return 0, t.Err } if strings.HasPrefix(t.Value, "foo") { return 500, nil } return 100, nil } type testHashablePointer struct { Value string } func (t *testHashablePointer) Hash() (uint64, error) { if strings.HasPrefix(t.Value, "foo") { return 500, nil } return 100, nil } hashstructure-1.1.0/include.go000066400000000000000000000015311375634113700164010ustar00rootroot00000000000000package hashstructure // Includable is an interface that can optionally be implemented by // a struct. It will be called for each field in the struct to check whether // it should be included in the hash. type Includable interface { HashInclude(field string, v interface{}) (bool, error) } // IncludableMap is an interface that can optionally be implemented by // a struct. It will be called when a map-type field is found to ask the // struct if the map item should be included in the hash. type IncludableMap interface { HashIncludeMap(field string, k, v interface{}) (bool, error) } // Hashable is an interface that can optionally be implemented by a struct // to override the hash value. This value will override the hash value for // the entire struct. Entries in the struct will not be hashed. type Hashable interface { Hash() (uint64, error) }