mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-04 03:02:26 -06:00 
			
		
		
		
	
		
			
				
	
	
		
			176 lines
		
	
	
	
		
			4.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			176 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
 | 
						|
}
 |