mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-12-02 00:22:45 +00:00
177 lines
4.1 KiB
Go
177 lines
4.1 KiB
Go
|
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
|
||
|
}
|