pax_global_header00006660000000000000000000000064150716725040014521gustar00rootroot0000000000000052 comment=1f6d247d157644b6ebd8057219523d52c7a903b9 gohugoio-hashstructure-1f6d247/000077500000000000000000000000001507167250400166025ustar00rootroot00000000000000gohugoio-hashstructure-1f6d247/.github/000077500000000000000000000000001507167250400201425ustar00rootroot00000000000000gohugoio-hashstructure-1f6d247/.github/workflows/000077500000000000000000000000001507167250400221775ustar00rootroot00000000000000gohugoio-hashstructure-1f6d247/.github/workflows/test.yml000066400000000000000000000021501507167250400236770ustar00rootroot00000000000000on: push: branches: [master] pull_request: name: Test permissions: contents: read jobs: test: strategy: matrix: go-version: [1.22.x, 1.23.x] platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} steps: - name: Install Go uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} - name: Install staticcheck run: go install honnef.co/go/tools/cmd/staticcheck@latest shell: bash - name: Install golint run: go install golang.org/x/lint/golint@latest shell: bash - name: Update PATH run: echo "$(go env GOPATH)/bin" >> $GITHUB_PATH shell: bash - name: Checkout code uses: actions/checkout@v4 - name: Fmt if: matrix.platform != 'windows-latest' # :( run: "diff <(gofmt -d .) <(printf '')" shell: bash - name: Vet run: go vet ./... - name: Staticcheck run: staticcheck ./... - name: Lint run: golint ./... - name: Test run: go test -race ./... gohugoio-hashstructure-1f6d247/.gitignore000066400000000000000000000000061507167250400205660ustar00rootroot00000000000000*.testgohugoio-hashstructure-1f6d247/LICENSE000066400000000000000000000020751507167250400176130ustar00rootroot00000000000000The 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. gohugoio-hashstructure-1f6d247/README.md000066400000000000000000000034201507167250400200600ustar00rootroot00000000000000# 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/gohugoio/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 ``` gohugoio-hashstructure-1f6d247/errors.go000066400000000000000000000011201507167250400204370ustar00rootroot00000000000000package hashstructure import ( "fmt" ) // 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) } // ErrFormat is returned when an invalid format is given to the Hash function. type ErrFormat struct{} func (*ErrFormat) Error() string { return "format must be one of the defined Format values in the hashstructure library" } gohugoio-hashstructure-1f6d247/go.mod000066400000000000000000000001371507167250400177110ustar00rootroot00000000000000module github.com/gohugoio/hashstructure go 1.20 require github.com/cespare/xxhash/v2 v2.3.0 gohugoio-hashstructure-1f6d247/go.sum000066400000000000000000000002571507167250400177410ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= gohugoio-hashstructure-1f6d247/hashstructure.go000066400000000000000000000331301507167250400220350ustar00rootroot00000000000000package hashstructure import ( "encoding/binary" "fmt" "hash" "hash/fnv" "math" "reflect" "time" "unsafe" ) // 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 always. 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 // precedence (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. // // The "format" is required and must be one of the format values defined // by this library. You should probably just use "FormatV2". This allows // generated hashes uses alternate logic to maintain compatibility with // older versions. // // 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() // Fast path for strings. if s, ok := v.(string); ok { return hashString(opts.Hasher, s) } // 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 buf [16]byte // Reusable buffer for binary encoding } 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{}) // A direct hash calculation used for numeric and bool values. func (w *walker) hashDirect(v any) (uint64, error) { w.h.Reset() // Use direct byte manipulation for numbers instead of binary.Write to avoid allocations switch val := v.(type) { case int64: binary.LittleEndian.PutUint64(w.buf[:8], uint64(val)) w.h.Write(w.buf[:8]) case uint64: binary.LittleEndian.PutUint64(w.buf[:8], val) w.h.Write(w.buf[:8]) case int8: w.buf[0] = byte(val) w.h.Write(w.buf[:1]) case uint8: w.buf[0] = val w.h.Write(w.buf[:1]) case int16: binary.LittleEndian.PutUint16(w.buf[:2], uint16(val)) w.h.Write(w.buf[:2]) case uint16: binary.LittleEndian.PutUint16(w.buf[:2], val) w.h.Write(w.buf[:2]) case int32: binary.LittleEndian.PutUint32(w.buf[:4], uint32(val)) w.h.Write(w.buf[:4]) case uint32: binary.LittleEndian.PutUint32(w.buf[:4], val) w.h.Write(w.buf[:4]) case float32: binary.LittleEndian.PutUint32(w.buf[:4], math.Float32bits(val)) w.h.Write(w.buf[:4]) case float64: binary.LittleEndian.PutUint64(w.buf[:8], math.Float64bits(val)) w.h.Write(w.buf[:8]) case complex64: binary.LittleEndian.PutUint32(w.buf[:4], math.Float32bits(real(val))) binary.LittleEndian.PutUint32(w.buf[4:8], math.Float32bits(imag(val))) w.h.Write(w.buf[:8]) case complex128: binary.LittleEndian.PutUint64(w.buf[:8], math.Float64bits(real(val))) binary.LittleEndian.PutUint64(w.buf[8:16], math.Float64bits(imag(val))) w.h.Write(w.buf[:16]) default: // Fallback to binary.Write for unsupported types, for instance enums err := binary.Write(w.h, binary.LittleEndian, v) return w.h.Sum64(), err } return w.h.Sum64(), nil } // A direct hash calculation used for strings. func (w *walker) hashString(s string) (uint64, error) { return hashString(w.h, s) } // A direct hash calculation used for strings. func hashString(h hash.Hash64, s string) (uint64, error) { h.Reset() // Use zero-copy conversion from string to []byte using unsafe if len(s) > 0 { b := unsafe.Slice(unsafe.StringData(s), len(s)) h.Write(b) } return h.Sum64(), nil } 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) } if v.CanInt() { i := v.Int() switch v.Kind() { case reflect.Int: return w.hashDirect(i) case reflect.Int8: return w.hashDirect(int8(i)) case reflect.Int16: return w.hashDirect(int16(i)) case reflect.Int32: return w.hashDirect(int32(i)) case reflect.Int64: return w.hashDirect(i) } } if v.CanUint() { u := v.Uint() switch v.Kind() { case reflect.Uint: return w.hashDirect(u) case reflect.Uint8: return w.hashDirect(uint8(u)) case reflect.Uint16: return w.hashDirect(uint16(u)) case reflect.Uint32: return w.hashDirect(uint32(u)) case reflect.Uint64: return w.hashDirect(u) } } if v.CanFloat() { f := v.Float() switch v.Kind() { case reflect.Float32: return w.hashDirect(float32(f)) case reflect.Float64: return w.hashDirect(f) } } if v.CanComplex() { c := v.Complex() switch v.Kind() { case reflect.Complex64: return w.hashDirect(complex64(c)) case reflect.Complex128: return w.hashDirect(c) } } k := v.Kind() if k == reflect.Bool { var tmp int8 if v.Bool() { tmp = 1 } return w.hashDirect(tmp) } switch v.Type() { case timeType: w.h.Reset() b, err := v.Interface().(time.Time).MarshalBinary() if err != nil { return 0, err } w.h.Write(b) return w.h.Sum64(), nil } 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 var field string if v, ok := v.Interface().(IncludableMap); ok { includeMap = v } else if opts != nil && opts.Struct != nil { if v, ok := opts.Struct.(IncludableMap); ok { includeMap, field = v, opts.StructField } } // 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 k := reflect.New(v.Type().Key()).Elem() vv := reflect.New(v.Type().Elem()).Elem() iter := v.MapRange() for iter.Next() { k.SetIterKey(iter) vv.SetIterValue(iter) if includeMap != nil { incl, err := includeMap.HashIncludeMap(field, k.Interface(), vv.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(vv, nil) if err != nil { return 0, err } fieldHash := hashUpdateOrdered(w.h, kh, vh) h = hashUpdateUnordered(h, fieldHash) } // Important: read the docs for hashFinishUnordered h = hashFinishUnordered(w.h, h) return h, nil case reflect.Struct: var include Includable var parent interface{} // Check if we can address this value first (more common case for pointer receivers) 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() } // Only set parent if we'll need it for IncludableMap parent = parentptr } // Only box the value if we haven't already found an implementation via pointer if include == nil && parent == nil { parent = v.Interface() if impl, ok := parent.(Includable); ok { include = impl } if impl, ok := parent.(Hashable); ok { return impl.Hash() } } t := v.Type() h, err := w.hashString(t.Name()) if err != nil { return 0, err } l := v.NumField() var fieldOpts visitOpts // Defer boxing parent until we know we need it if parent == nil { parent = v.Interface() } fieldOpts.Struct = parent for i := 0; i < l; i++ { if innerV := v.Field(i); v.CanSet() || t.Field(i).Name != "_" { 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 { if innerV.IsZero() { 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 } } fieldOpts.Flags = 0 if tag == "set" { fieldOpts.Flags |= visitFlagSet } kh, err := w.hashString(fieldType.Name) if err != nil { return 0, err } fieldOpts.StructField = fieldType.Name vh, err := w.visit(innerV, &fieldOpts) if err != nil { return 0, err } fieldHash := hashUpdateOrdered(w.h, kh, vh) h = hashUpdateUnordered(h, fieldHash) } // Important: read the docs for hashFinishUnordered h = hashFinishUnordered(w.h, h) } 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) } } if set { // Important: read the docs for hashFinishUnordered h = hashFinishUnordered(w.h, h) } return h, nil case reflect.String: return w.hashString(v.String()) 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() var buf [16]byte binary.LittleEndian.PutUint64(buf[0:8], a) binary.LittleEndian.PutUint64(buf[8:16], b) h.Write(buf[:]) return h.Sum64() } func hashUpdateUnordered(a, b uint64) uint64 { return a ^ b } // After mixing a group of unique hashes with hashUpdateUnordered, it's always // necessary to call hashFinishUnordered. Why? Because hashUpdateUnordered // is a simple XOR, and calling hashUpdateUnordered on hashes produced by // hashUpdateUnordered can effectively cancel out a previous change to the hash // result if the same hash value appears later on. For example, consider: // // hashUpdateUnordered(hashUpdateUnordered("A", "B"), hashUpdateUnordered("A", "C")) = // H("A") ^ H("B")) ^ (H("A") ^ H("C")) = // (H("A") ^ H("A")) ^ (H("B") ^ H(C)) = // H(B) ^ H(C) = // hashUpdateUnordered(hashUpdateUnordered("Z", "B"), hashUpdateUnordered("Z", "C")) // // hashFinishUnordered "hardens" the result, so that encountering partially // overlapping input data later on in a different context won't cancel out. func hashFinishUnordered(h hash.Hash64, a uint64) uint64 { h.Reset() var buf [8]byte binary.LittleEndian.PutUint64(buf[:], a) h.Write(buf[:]) return h.Sum64() } // visitFlag is used as a bitmask for affecting visit behavior type visitFlag uint const ( visitFlagInvalid visitFlag = iota visitFlagSet = iota << 1 ) gohugoio-hashstructure-1f6d247/hashstructure_examples_test.go000066400000000000000000000007341507167250400247760ustar00rootroot00000000000000package 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: // 1839806922502695369 } gohugoio-hashstructure-1f6d247/hashstructure_test.go000066400000000000000000000423761507167250400231100ustar00rootroot00000000000000package hashstructure import ( "fmt" "strings" "testing" "time" "github.com/cespare/xxhash/v2" ) 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"}, false, }, { 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, }, { int16(-32768), uint16(32768), 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 Map map[string]int }{ Foo: "foo", Bar: "bar", } structB := struct { Foo string Bar string Baz string Map map[string]int }{ 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, }, { testIncludableMapMap{"foo": "bar"}, testIncludableMapMap{"foo": "bar"}, true, }, { testIncludableMapMap{"foo": "bar", "ignore": "true"}, testIncludableMapMap{"foo": "bar"}, true, }, { testIncludableMapMap{"foo": "bar", "ignore": "true"}, testIncludableMapMap{"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) } }) } } func TestHash_golden(t *testing.T) { foo := "foo" cases := []struct { In any Expect uint64 }{ { In: nil, Expect: 12161962213042174405, }, { In: "foo", Expect: 15621798640163566899, }, { In: 42, Expect: 11375694726533372055, }, { In: uint8(42), Expect: 12638153115695167477, }, { In: int16(42), Expect: 590708257076254031, }, { In: int16(-32768), Expect: 590684067820433261, }, { In: int16(32767), Expect: 590474061099445023, }, { In: int32(42), Expect: 843871326190827175, }, { In: int64(42), Expect: 11375694726533372055, }, { In: uint16(0), Expect: 590684067820433389, }, { In: uint16(42), Expect: 590708257076254031, }, { In: uint16(65535), Expect: 590474061099445151, }, { In: uint32(42), Expect: 843871326190827175, }, { In: uint64(42), Expect: 11375694726533372055, }, { In: float32(42), Expect: 5558953217260120943, }, { In: float64(42), Expect: 12162027084228238918, }, { In: float64(3.14159265359), Expect: 999115755352816086, }, { In: complex64(42), Expect: 13187391128804187615, }, { In: complex64(complex(1.2, 3.4)), Expect: 12862333766589160118, }, { In: complex128(42), Expect: 4635205179288363782, }, { In: true, Expect: 12638153115695167454, }, { In: false, Expect: 12638153115695167455, }, { In: []string{"foo", "bar"}, Expect: 18333885979647637445, }, { In: []interface{}{1, nil, "foo"}, Expect: 636613494442026145, }, { In: map[string]string{"foo": "bar"}, Expect: 5334326627423288605, }, { In: map[string]*string{"foo": &foo}, Expect: 4615367350888355399, }, { In: map[*string]string{&foo: "bar"}, Expect: 5334326627423288605, }, { In: map[interface{}]string{"foo": "bar"}, Expect: 5334326627423288605, }, { In: map[interface{}]interface{}{"foo": "bar", "bar": 0}, Expect: 10207098687398820730, }, { In: map[interface{}]interface{}{"foo": "bar", "bar": map[interface{}]interface{}{"foo": "bar", "bar": map[interface{}]interface{}{"foo": "bar", "bar": map[interface{}]interface{}{&foo: "bar", "bar": 0}}}}, Expect: 18346441822047112296, }, { In: struct { Foo string Bar []interface{} }{ Foo: "foo", Bar: []interface{}{nil, nil, nil}, }, Expect: 14887393564066082535, }, } for i, tc := range cases { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { got, err := Hash(tc.In, nil) if err != nil { t.Fatalf("unexpected error: %s", err) } if got != tc.Expect { t.Fatalf("expected: %d, got: %d", tc.Expect, got) } }) } } func BenchmarkMap(b *testing.B) { m := map[string]any{ "int16": int16(42), "int32": int32(42), "int64": int64(42), "int": int(42), "uint16": uint16(42), "uint32": uint32(42), "uint64": uint64(42), "uint": uint(42), "float32": float32(42), "float64": float64(42), "complex64": complex64(42), "complex128": complex128(42), "string": "foo", "bool": true, "slice": []string{"foo", "bar"}, "sliceint": []int{1, 2, 3}, "map": map[string]string{"foo": "bar"}, "struct": struct { Foo string Bar []interface{} }{ Foo: "foo", Bar: []interface{}{nil, nil, nil}, }, } for i := 0; i < b.N; i++ { Hash(m, nil) } } func BenchmarkString(b *testing.B) { s := "lorem ipsum dolor sit amet" b.Run("default", func(b *testing.B) { for i := 0; i < b.N; i++ { Hash(s, nil) } }) b.Run("xxhash", func(b *testing.B) { opts := &HashOptions{Hasher: xxhash.New()} for i := 0; i < b.N; i++ { Hash(s, opts) } }) } 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 } type testIncludableMapMap map[string]string func (t testIncludableMapMap) HashIncludeMap(_ string, k, _ interface{}) (bool, error) { return k.(string) != "ignore", nil } gohugoio-hashstructure-1f6d247/include.go000066400000000000000000000015311507167250400205540ustar00rootroot00000000000000package 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) }