diff options
Diffstat (limited to 'vendor/codeberg.org/gruf/go-structr/hasher.go')
-rw-r--r-- | vendor/codeberg.org/gruf/go-structr/hasher.go | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/vendor/codeberg.org/gruf/go-structr/hasher.go b/vendor/codeberg.org/gruf/go-structr/hasher.go new file mode 100644 index 000000000..77b8a0991 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-structr/hasher.go @@ -0,0 +1,176 @@ +package structr + +import ( + "reflect" + "strings" + + "github.com/zeebo/xxh3" +) + +// Hasher provides hash checksumming for a configured +// index, based on an arbitrary combination of generic +// paramter struct type's fields. This provides hashing +// both by input of the fields separately, or passing +// an instance of the generic paramter struct type. +// +// Supported field types by the hasher include: +// - ~int +// - ~int8 +// - ~int16 +// - ~int32 +// - ~int64 +// - ~float32 +// - ~float64 +// - ~string +// - slices / ptrs of the above +type Hasher[StructType any] struct { + + // fields contains our representation + // of struct fields contained in the + // creation of sums by this hasher. + fields []structfield + + // zero specifies whether zero + // value fields are permitted. + zero bool +} + +// NewHasher returns a new initialized Hasher for the receiving generic +// parameter type, comprising of the given field strings, and whether to +// allow zero values to be incldued within generated hash checksum values. +func NewHasher[T any](fields []string, allowZero bool) Hasher[T] { + var h Hasher[T] + + // Preallocate expected struct field slice. + h.fields = make([]structfield, len(fields)) + + // Get the reflected struct ptr type. + t := reflect.TypeOf((*T)(nil)).Elem() + + for i, fieldName := range fields { + // Split name to account for nesting. + names := strings.Split(fieldName, ".") + + // Look for a usable struct field from type. + sfield, ok := findField(t, names, allowZero) + if !ok { + panicf("failed finding field: %s", fieldName) + } + + // Set parsed struct field. + h.fields[i] = sfield + } + + // Set config flags. + h.zero = allowZero + + return h +} + +// FromParts generates hash checksum (used as index key) from individual key parts. +func (h *Hasher[T]) FromParts(parts ...any) (sum uint64, ok bool) { + hh := getHasher() + sum, ok = h.fromParts(hh, parts...) + putHasher(hh) + return + +} + +func (h *Hasher[T]) fromParts(hh *xxh3.Hasher, parts ...any) (sum uint64, ok bool) { + if len(parts) != len(h.fields) { + // User must provide correct number of parts for key. + panicf("incorrect number key parts: want=%d received=%d", + len(parts), + len(h.fields), + ) + } + + if h.zero { + // Zero values are permitted, + // mangle all values and ignore + // zero value return booleans. + for i, part := range parts { + + // Write mangled part to hasher. + _ = h.fields[i].hasher(hh, part) + } + } else { + // Zero values are NOT permitted. + for i, part := range parts { + + // Write mangled field to hasher. + z := h.fields[i].hasher(hh, part) + + if z { + // The value was zero for + // this type, return early. + return 0, false + } + } + } + + return hh.Sum64(), true +} + +// FromValue generates hash checksum (used as index key) from a value, via reflection. +func (h *Hasher[T]) FromValue(value T) (sum uint64, ok bool) { + rvalue := reflect.ValueOf(value) + hh := getHasher() + sum, ok = h.fromRValue(hh, rvalue) + putHasher(hh) + return +} + +func (h *Hasher[T]) fromRValue(hh *xxh3.Hasher, rvalue reflect.Value) (uint64, bool) { + // Follow any ptrs leading to value. + for rvalue.Kind() == reflect.Pointer { + rvalue = rvalue.Elem() + } + + if h.zero { + // Zero values are permitted, + // mangle all values and ignore + // zero value return booleans. + for i := range h.fields { + + // Get the reflect value's field at idx. + fv := rvalue.FieldByIndex(h.fields[i].index) + fi := fv.Interface() + + // Write mangled field to hasher. + _ = h.fields[i].hasher(hh, fi) + } + } else { + // Zero values are NOT permitted. + for i := range h.fields { + + // Get the reflect value's field at idx. + fv := rvalue.FieldByIndex(h.fields[i].index) + fi := fv.Interface() + + // Write mangled field to hasher. + z := h.fields[i].hasher(hh, fi) + + if z { + // The value was zero for + // this type, return early. + return 0, false + } + } + } + + return hh.Sum64(), true +} + +type structfield struct { + // index is the reflected index + // of this field (this takes into + // account struct nesting). + index []int + + // hasher is the relevant function + // for hashing value of structfield + // into the supplied hashbuf, where + // return value indicates if zero. + hasher func(*xxh3.Hasher, any) bool +} |