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

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: mitchellh/hashstructure
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.0.0
Choose a base ref
...
head repository: mitchellh/hashstructure
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.1.0
Choose a head ref
  • 19 commits
  • 6 files changed
  • 6 contributors

Commits on Feb 13, 2019

  1. Fix time.Time hashes

    F21 committed Feb 13, 2019
    Copy the full SHA
    20ffafb View commit details

Commits on May 22, 2019

  1. Copy the full SHA
    b188d19 View commit details
  2. Copy the full SHA
    1610f71 View commit details

Commits on Aug 28, 2019

  1. Add options to default string and set tags

    Adam Hasselbalch Hansen committed Aug 28, 2019
    Copy the full SHA
    c0db000 View commit details
  2. reorder option values

    Adam Hasselbalch Hansen committed Aug 28, 2019
    Copy the full SHA
    6d3e983 View commit details
  3. don't continue

    Adam Hasselbalch Hansen committed Aug 28, 2019
    Copy the full SHA
    f451e67 View commit details

Commits on May 8, 2020

  1. Copy the full SHA
    8fdbea4 View commit details

Commits on Nov 22, 2020

  1. Merge pull request #22 from adamhassel/set_option

    Options to always attempts `fmt.Stringer` and to always assume `hash:"set"`
    mitchellh authored Nov 22, 2020
    Copy the full SHA
    705d03c View commit details
  2. simplify w.stringer check

    mitchellh committed Nov 22, 2020
    Copy the full SHA
    73afff4 View commit details
  3. feat: optionally override hash

    override the hash either by an unexported `hash` field or by implementing a new interface `hashstructure.Hashable`
    
    closes #14
    matfax authored and mitchellh committed Nov 22, 2020
    Copy the full SHA
    26faa52 View commit details
  4. Copy the full SHA
    7791672 View commit details
  5. Merge branch 'master' of https://github.com/LarsFronius/hashstructure

    …into LarsFronius-master
    mitchellh committed Nov 22, 2020
    Copy the full SHA
    5f56cab View commit details
  6. Copy the full SHA
    c2db438 View commit details
  7. Copy the full SHA
    079c88a View commit details
  8. Copy the full SHA
    ea9e674 View commit details
  9. Merge pull request #21 from F21/support-time.Time

    Fix time.Time hashes
    mitchellh authored Nov 22, 2020
    Copy the full SHA
    ef219f8 View commit details
  10. gofmt -s

    mitchellh committed Nov 22, 2020
    Copy the full SHA
    0cb468a View commit details
  11. add GH actions

    mitchellh committed Nov 22, 2020
    Copy the full SHA
    b3f24a2 View commit details
  12. Merge pull request #27 from mitchellh/gh-actions

    GitHub Actions for Tests
    mitchellh authored Nov 22, 2020
    Copy the full SHA
    191a1e8 View commit details
Showing with 268 additions and 13 deletions.
  1. +18 −0 .github/workflows/test.yml
  2. +5 −3 README.md
  3. +2 −0 go.mod
  4. +73 −9 hashstructure.go
  5. +163 −1 hashstructure_test.go
  6. +7 −0 include.go
18 changes: 18 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
on: [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 ./...
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -17,12 +17,14 @@ sending data across the network, caching values locally (de-dup), and so on.
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
* 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,

* 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`:
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
module github.com/mitchellh/hashstructure

go 1.14
82 changes: 73 additions & 9 deletions hashstructure.go
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import (
"hash"
"hash/fnv"
"reflect"
"time"
)

// ErrNotStringer is returned when there's an error with hash:"string"
@@ -31,6 +32,21 @@ type HashOptions struct {
// 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.
@@ -82,17 +98,23 @@ func Hash(v interface{}, opts *HashOptions) (uint64, error) {

// Create our walker and walk the structure
w := &walker{
h: opts.Hasher,
tag: opts.TagName,
zeronil: opts.ZeroNil,
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
h hash.Hash64
tag string
zeronil bool
ignorezerovalue bool
sets bool
stringer bool
}

type visitOpts struct {
@@ -104,6 +126,8 @@ type visitOpts struct {
StructField string
}

var timeType = reflect.TypeOf(time.Time{})

func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) {
t := reflect.TypeOf(0)

@@ -159,6 +183,18 @@ func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) {
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
@@ -220,6 +256,24 @@ func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) {
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 {
@@ -229,6 +283,7 @@ func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) {
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 != "" {
@@ -242,11 +297,20 @@ func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) {
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" {
if tag == "string" || w.stringer {
if impl, ok := innerV.Interface().(fmt.Stringer); ok {
innerV = reflect.ValueOf(impl.String())
} else {
} 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,
}
@@ -306,7 +370,7 @@ func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) {
return 0, err
}

if set {
if set || w.sets {
h = hashUpdateUnordered(h, current)
} else {
h = hashUpdateOrdered(w.h, h, current)
Loading