"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
// 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.
+// 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:
//
//
// The available tag values are:
//
-// * "ignore" - The field will be ignored and not affect the hash code.
+// * "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 {
// Create our walker and walk the structure
w := &walker{
- h: opts.Hasher,
- tag: opts.TagName,
+ h: opts.Hasher,
+ tag: opts.TagName,
+ zeronil: opts.ZeroNil,
}
return w.visit(reflect.ValueOf(v), nil)
}
type walker struct {
- h hash.Hash64
- tag string
+ h hash.Hash64
+ tag string
+ zeronil bool
}
type visitOpts struct {
}
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 v.Kind() == reflect.Ptr {
+ if w.zeronil {
+ t = v.Type().Elem()
+ }
v = reflect.Indirect(v)
continue
}
// If it is nil, treat it like a zero.
if !v.IsValid() {
- var tmp int8
- v = reflect.ValueOf(tmp)
+ v = reflect.Zero(t)
}
// Binary writing can use raw ints, we have to convert to
return h, nil
case reflect.Struct:
- var include Includable
parent := v.Interface()
+ var include Includable
if impl, ok := parent.(Includable); ok {
include = impl
}
l := v.NumField()
for i := 0; i < l; i++ {
- if v := v.Field(i); v.CanSet() || t.Field(i).Name != "_" {
+ if innerV := v.Field(i); v.CanSet() || t.Field(i).Name != "_" {
var f visitFlag
fieldType := t.Field(i)
if fieldType.PkgPath != "" {
}
tag := fieldType.Tag.Get(w.tag)
- if tag == "ignore" {
+ 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, v)
+ incl, err := include.HashInclude(fieldType.Name, innerV)
if err != nil {
return 0, err
}
return 0, err
}
- vh, err := w.visit(v, &visitOpts{
+ vh, err := w.visit(innerV, &visitOpts{
Flags: f,
Struct: parent,
StructField: fieldType.Name,
return 0, fmt.Errorf("unknown kind to hash: %s", k)
}
- return 0, nil
}
func hashUpdateOrdered(h hash.Hash64, a, b uint64) uint64 {