aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/mitchellh/hashstructure/hashstructure.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/mitchellh/hashstructure/hashstructure.go')
-rw-r--r--vendor/github.com/mitchellh/hashstructure/hashstructure.go323
1 files changed, 323 insertions, 0 deletions
diff --git a/vendor/github.com/mitchellh/hashstructure/hashstructure.go b/vendor/github.com/mitchellh/hashstructure/hashstructure.go
new file mode 100644
index 0000000..6f586fa
--- /dev/null
+++ b/vendor/github.com/mitchellh/hashstructure/hashstructure.go
@@ -0,0 +1,323 @@
1package hashstructure
2
3import (
4 "encoding/binary"
5 "fmt"
6 "hash"
7 "hash/fnv"
8 "reflect"
9)
10
11// HashOptions are options that are available for hashing.
12type HashOptions struct {
13 // Hasher is the hash function to use. If this isn't set, it will
14 // default to FNV.
15 Hasher hash.Hash64
16
17 // TagName is the struct tag to look at when hashing the structure.
18 // By default this is "hash".
19 TagName string
20}
21
22// Hash returns the hash value of an arbitrary value.
23//
24// If opts is nil, then default options will be used. See HashOptions
25// for the default values.
26//
27// Notes on the value:
28//
29// * Unexported fields on structs are ignored and do not affect the
30// hash value.
31//
32// * Adding an exported field to a struct with the zero value will change
33// the hash value.
34//
35// For structs, the hashing can be controlled using tags. For example:
36//
37// struct {
38// Name string
39// UUID string `hash:"ignore"`
40// }
41//
42// The available tag values are:
43//
44// * "ignore" - The field will be ignored and not affect the hash code.
45//
46// * "set" - The field will be treated as a set, where ordering doesn't
47// affect the hash code. This only works for slices.
48//
49func Hash(v interface{}, opts *HashOptions) (uint64, error) {
50 // Create default options
51 if opts == nil {
52 opts = &HashOptions{}
53 }
54 if opts.Hasher == nil {
55 opts.Hasher = fnv.New64()
56 }
57 if opts.TagName == "" {
58 opts.TagName = "hash"
59 }
60
61 // Reset the hash
62 opts.Hasher.Reset()
63
64 // Create our walker and walk the structure
65 w := &walker{
66 h: opts.Hasher,
67 tag: opts.TagName,
68 }
69 return w.visit(reflect.ValueOf(v), nil)
70}
71
72type walker struct {
73 h hash.Hash64
74 tag string
75}
76
77type visitOpts struct {
78 // Flags are a bitmask of flags to affect behavior of this visit
79 Flags visitFlag
80
81 // Information about the struct containing this field
82 Struct interface{}
83 StructField string
84}
85
86func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) {
87 // Loop since these can be wrapped in multiple layers of pointers
88 // and interfaces.
89 for {
90 // If we have an interface, dereference it. We have to do this up
91 // here because it might be a nil in there and the check below must
92 // catch that.
93 if v.Kind() == reflect.Interface {
94 v = v.Elem()
95 continue
96 }
97
98 if v.Kind() == reflect.Ptr {
99 v = reflect.Indirect(v)
100 continue
101 }
102
103 break
104 }
105
106 // If it is nil, treat it like a zero.
107 if !v.IsValid() {
108 var tmp int8
109 v = reflect.ValueOf(tmp)
110 }
111
112 // Binary writing can use raw ints, we have to convert to
113 // a sized-int, we'll choose the largest...
114 switch v.Kind() {
115 case reflect.Int:
116 v = reflect.ValueOf(int64(v.Int()))
117 case reflect.Uint:
118 v = reflect.ValueOf(uint64(v.Uint()))
119 case reflect.Bool:
120 var tmp int8
121 if v.Bool() {
122 tmp = 1
123 }
124 v = reflect.ValueOf(tmp)
125 }
126
127 k := v.Kind()
128
129 // We can shortcut numeric values by directly binary writing them
130 if k >= reflect.Int && k <= reflect.Complex64 {
131 // A direct hash calculation
132 w.h.Reset()
133 err := binary.Write(w.h, binary.LittleEndian, v.Interface())
134 return w.h.Sum64(), err
135 }
136
137 switch k {
138 case reflect.Array:
139 var h uint64
140 l := v.Len()
141 for i := 0; i < l; i++ {
142 current, err := w.visit(v.Index(i), nil)
143 if err != nil {
144 return 0, err
145 }
146
147 h = hashUpdateOrdered(w.h, h, current)
148 }
149
150 return h, nil
151
152 case reflect.Map:
153 var includeMap IncludableMap
154 if opts != nil && opts.Struct != nil {
155 if v, ok := opts.Struct.(IncludableMap); ok {
156 includeMap = v
157 }
158 }
159
160 // Build the hash for the map. We do this by XOR-ing all the key
161 // and value hashes. This makes it deterministic despite ordering.
162 var h uint64
163 for _, k := range v.MapKeys() {
164 v := v.MapIndex(k)
165 if includeMap != nil {
166 incl, err := includeMap.HashIncludeMap(
167 opts.StructField, k.Interface(), v.Interface())
168 if err != nil {
169 return 0, err
170 }
171 if !incl {
172 continue
173 }
174 }
175
176 kh, err := w.visit(k, nil)
177 if err != nil {
178 return 0, err
179 }
180 vh, err := w.visit(v, nil)
181 if err != nil {
182 return 0, err
183 }
184
185 fieldHash := hashUpdateOrdered(w.h, kh, vh)
186 h = hashUpdateUnordered(h, fieldHash)
187 }
188
189 return h, nil
190
191 case reflect.Struct:
192 var include Includable
193 parent := v.Interface()
194 if impl, ok := parent.(Includable); ok {
195 include = impl
196 }
197
198 t := v.Type()
199 h, err := w.visit(reflect.ValueOf(t.Name()), nil)
200 if err != nil {
201 return 0, err
202 }
203
204 l := v.NumField()
205 for i := 0; i < l; i++ {
206 if v := v.Field(i); v.CanSet() || t.Field(i).Name != "_" {
207 var f visitFlag
208 fieldType := t.Field(i)
209 if fieldType.PkgPath != "" {
210 // Unexported
211 continue
212 }
213
214 tag := fieldType.Tag.Get(w.tag)
215 if tag == "ignore" {
216 // Ignore this field
217 continue
218 }
219
220 // Check if we implement includable and check it
221 if include != nil {
222 incl, err := include.HashInclude(fieldType.Name, v)
223 if err != nil {
224 return 0, err
225 }
226 if !incl {
227 continue
228 }
229 }
230
231 switch tag {
232 case "set":
233 f |= visitFlagSet
234 }
235
236 kh, err := w.visit(reflect.ValueOf(fieldType.Name), nil)
237 if err != nil {
238 return 0, err
239 }
240
241 vh, err := w.visit(v, &visitOpts{
242 Flags: f,
243 Struct: parent,
244 StructField: fieldType.Name,
245 })
246 if err != nil {
247 return 0, err
248 }
249
250 fieldHash := hashUpdateOrdered(w.h, kh, vh)
251 h = hashUpdateUnordered(h, fieldHash)
252 }
253 }
254
255 return h, nil
256
257 case reflect.Slice:
258 // We have two behaviors here. If it isn't a set, then we just
259 // visit all the elements. If it is a set, then we do a deterministic
260 // hash code.
261 var h uint64
262 var set bool
263 if opts != nil {
264 set = (opts.Flags & visitFlagSet) != 0
265 }
266 l := v.Len()
267 for i := 0; i < l; i++ {
268 current, err := w.visit(v.Index(i), nil)
269 if err != nil {
270 return 0, err
271 }
272
273 if set {
274 h = hashUpdateUnordered(h, current)
275 } else {
276 h = hashUpdateOrdered(w.h, h, current)
277 }
278 }
279
280 return h, nil
281
282 case reflect.String:
283 // Directly hash
284 w.h.Reset()
285 _, err := w.h.Write([]byte(v.String()))
286 return w.h.Sum64(), err
287
288 default:
289 return 0, fmt.Errorf("unknown kind to hash: %s", k)
290 }
291
292 return 0, nil
293}
294
295func hashUpdateOrdered(h hash.Hash64, a, b uint64) uint64 {
296 // For ordered updates, use a real hash function
297 h.Reset()
298
299 // We just panic if the binary writes fail because we are writing
300 // an int64 which should never be fail-able.
301 e1 := binary.Write(h, binary.LittleEndian, a)
302 e2 := binary.Write(h, binary.LittleEndian, b)
303 if e1 != nil {
304 panic(e1)
305 }
306 if e2 != nil {
307 panic(e2)
308 }
309
310 return h.Sum64()
311}
312
313func hashUpdateUnordered(a, b uint64) uint64 {
314 return a ^ b
315}
316
317// visitFlag is used as a bitmask for affecting visit behavior
318type visitFlag uint
319
320const (
321 visitFlagInvalid visitFlag = iota
322 visitFlagSet = iota << 1
323)