pax_global_header00006660000000000000000000000064133412607750014522gustar00rootroot0000000000000052 comment=a38c50148365edc8df43c1580c48fb2b3a1e9cd7 hashstructure-1.0.0/000077500000000000000000000000001334126077500144245ustar00rootroot00000000000000hashstructure-1.0.0/LICENSE000066400000000000000000000020751334126077500154350ustar00rootroot00000000000000The 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.0.0/README.md000066400000000000000000000033071334126077500157060ustar00rootroot00000000000000# 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 ## 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.0.0/go.mod000066400000000000000000000000521334126077500155270ustar00rootroot00000000000000module github.com/mitchellh/hashstructure hashstructure-1.0.0/hashstructure.go000066400000000000000000000174761334126077500176760ustar00rootroot00000000000000package hashstructure import ( "encoding/binary" "fmt" "hash" "hash/fnv" "reflect" ) // 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 } // 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, } return w.visit(reflect.ValueOf(v), nil) } type walker struct { h hash.Hash64 tag string zeronil 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 } 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 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 } 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 string is set, use the string value if tag == "string" { if impl, ok := innerV.Interface().(fmt.Stringer); ok { innerV = reflect.ValueOf(impl.String()) } else { 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 { 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.0.0/hashstructure_examples_test.go000066400000000000000000000007341334126077500226200ustar00rootroot00000000000000package 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.0.0/hashstructure_test.go000066400000000000000000000235541334126077500207270ustar00rootroot00000000000000package hashstructure import ( "fmt" "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 } 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, }, } 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{}}, 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_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) } } } 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 } hashstructure-1.0.0/include.go000066400000000000000000000011161334126077500163750ustar00rootroot00000000000000package 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) }