11 // ErrNotStringer is returned when there's an error with hash:"string"
12 type ErrNotStringer struct {
16 // Error implements error for ErrNotStringer
17 func (ens *ErrNotStringer) Error() string {
18 return fmt.Sprintf("hashstructure: %s has hash:\"string\" set, but does not implement fmt.Stringer", ens.Field)
21 // HashOptions are options that are available for hashing.
22 type HashOptions struct {
23 // Hasher is the hash function to use. If this isn't set, it will
27 // TagName is the struct tag to look at when hashing the structure.
28 // By default this is "hash".
31 // ZeroNil is flag determining if nil pointer should be treated equal
32 // to a zero value of pointed type. By default this is false.
36 // Hash returns the hash value of an arbitrary value.
38 // If opts is nil, then default options will be used. See HashOptions
39 // for the default values. The same *HashOptions value cannot be used
40 // concurrently. None of the values within a *HashOptions struct are
41 // safe to read/write while hashing is being done.
43 // Notes on the value:
45 // * Unexported fields on structs are ignored and do not affect the
48 // * Adding an exported field to a struct with the zero value will change
51 // For structs, the hashing can be controlled using tags. For example:
55 // UUID string `hash:"ignore"`
58 // The available tag values are:
60 // * "ignore" or "-" - The field will be ignored and not affect the hash code.
62 // * "set" - The field will be treated as a set, where ordering doesn't
63 // affect the hash code. This only works for slices.
65 // * "string" - The field will be hashed as a string, only works when the
66 // field implements fmt.Stringer
68 func Hash(v interface{}, opts *HashOptions) (uint64, error) {
69 // Create default options
73 if opts.Hasher == nil {
74 opts.Hasher = fnv.New64()
76 if opts.TagName == "" {
83 // Create our walker and walk the structure
87 zeronil: opts.ZeroNil,
89 return w.visit(reflect.ValueOf(v), nil)
98 type visitOpts struct {
99 // Flags are a bitmask of flags to affect behavior of this visit
102 // Information about the struct containing this field
107 func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) {
108 t := reflect.TypeOf(0)
110 // Loop since these can be wrapped in multiple layers of pointers
113 // If we have an interface, dereference it. We have to do this up
114 // here because it might be a nil in there and the check below must
116 if v.Kind() == reflect.Interface {
121 if v.Kind() == reflect.Ptr {
125 v = reflect.Indirect(v)
132 // If it is nil, treat it like a zero.
137 // Binary writing can use raw ints, we have to convert to
138 // a sized-int, we'll choose the largest...
141 v = reflect.ValueOf(int64(v.Int()))
143 v = reflect.ValueOf(uint64(v.Uint()))
149 v = reflect.ValueOf(tmp)
154 // We can shortcut numeric values by directly binary writing them
155 if k >= reflect.Int && k <= reflect.Complex64 {
156 // A direct hash calculation
158 err := binary.Write(w.h, binary.LittleEndian, v.Interface())
159 return w.h.Sum64(), err
166 for i := 0; i < l; i++ {
167 current, err := w.visit(v.Index(i), nil)
172 h = hashUpdateOrdered(w.h, h, current)
178 var includeMap IncludableMap
179 if opts != nil && opts.Struct != nil {
180 if v, ok := opts.Struct.(IncludableMap); ok {
185 // Build the hash for the map. We do this by XOR-ing all the key
186 // and value hashes. This makes it deterministic despite ordering.
188 for _, k := range v.MapKeys() {
190 if includeMap != nil {
191 incl, err := includeMap.HashIncludeMap(
192 opts.StructField, k.Interface(), v.Interface())
201 kh, err := w.visit(k, nil)
205 vh, err := w.visit(v, nil)
210 fieldHash := hashUpdateOrdered(w.h, kh, vh)
211 h = hashUpdateUnordered(h, fieldHash)
217 parent := v.Interface()
218 var include Includable
219 if impl, ok := parent.(Includable); ok {
224 h, err := w.visit(reflect.ValueOf(t.Name()), nil)
230 for i := 0; i < l; i++ {
231 if innerV := v.Field(i); v.CanSet() || t.Field(i).Name != "_" {
233 fieldType := t.Field(i)
234 if fieldType.PkgPath != "" {
239 tag := fieldType.Tag.Get(w.tag)
240 if tag == "ignore" || tag == "-" {
245 // if string is set, use the string value
247 if impl, ok := innerV.Interface().(fmt.Stringer); ok {
248 innerV = reflect.ValueOf(impl.String())
250 return 0, &ErrNotStringer{
251 Field: v.Type().Field(i).Name,
256 // Check if we implement includable and check it
258 incl, err := include.HashInclude(fieldType.Name, innerV)
272 kh, err := w.visit(reflect.ValueOf(fieldType.Name), nil)
277 vh, err := w.visit(innerV, &visitOpts{
280 StructField: fieldType.Name,
286 fieldHash := hashUpdateOrdered(w.h, kh, vh)
287 h = hashUpdateUnordered(h, fieldHash)
294 // We have two behaviors here. If it isn't a set, then we just
295 // visit all the elements. If it is a set, then we do a deterministic
300 set = (opts.Flags & visitFlagSet) != 0
303 for i := 0; i < l; i++ {
304 current, err := w.visit(v.Index(i), nil)
310 h = hashUpdateUnordered(h, current)
312 h = hashUpdateOrdered(w.h, h, current)
321 _, err := w.h.Write([]byte(v.String()))
322 return w.h.Sum64(), err
325 return 0, fmt.Errorf("unknown kind to hash: %s", k)
330 func hashUpdateOrdered(h hash.Hash64, a, b uint64) uint64 {
331 // For ordered updates, use a real hash function
334 // We just panic if the binary writes fail because we are writing
335 // an int64 which should never be fail-able.
336 e1 := binary.Write(h, binary.LittleEndian, a)
337 e2 := binary.Write(h, binary.LittleEndian, b)
348 func hashUpdateUnordered(a, b uint64) uint64 {
352 // visitFlag is used as a bitmask for affecting visit behavior
356 visitFlagInvalid visitFlag = iota
357 visitFlagSet = iota << 1