mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-12-24 11:12:11 +00:00
205 lines
5.1 KiB
Go
205 lines
5.1 KiB
Go
|
package structr
|
||
|
|
||
|
import (
|
||
|
"reflect"
|
||
|
"strings"
|
||
|
|
||
|
"codeberg.org/gruf/go-byteutil"
|
||
|
"codeberg.org/gruf/go-mangler"
|
||
|
)
|
||
|
|
||
|
// KeyGen is the underlying index key generator
|
||
|
// used within Index, and therefore Cache itself.
|
||
|
type KeyGen[StructType any] struct {
|
||
|
|
||
|
// fields contains our representation of
|
||
|
// the struct fields contained in the
|
||
|
// creation of keys by this generator.
|
||
|
fields []structfield
|
||
|
|
||
|
// zero specifies whether zero
|
||
|
// value fields are permitted.
|
||
|
zero bool
|
||
|
}
|
||
|
|
||
|
// NewKeyGen returns a new initialized KeyGen for the receiving generic
|
||
|
// parameter type, comprising of the given field strings, and whether to
|
||
|
// allow zero values to be included within generated output strings.
|
||
|
func NewKeyGen[T any](fields []string, allowZero bool) KeyGen[T] {
|
||
|
var kgen KeyGen[T]
|
||
|
|
||
|
// Preallocate expected struct field slice.
|
||
|
kgen.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.
|
||
|
kgen.fields[i] = sfield
|
||
|
}
|
||
|
|
||
|
// Set config flags.
|
||
|
kgen.zero = allowZero
|
||
|
|
||
|
return kgen
|
||
|
}
|
||
|
|
||
|
// FromParts generates key string from individual key parts.
|
||
|
func (kgen *KeyGen[T]) FromParts(parts ...any) (key string, ok bool) {
|
||
|
buf := getBuf()
|
||
|
if ok = kgen.AppendFromParts(buf, parts...); ok {
|
||
|
key = string(buf.B)
|
||
|
}
|
||
|
putBuf(buf)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// FromValue generates key string from a value, via reflection.
|
||
|
func (kgen *KeyGen[T]) FromValue(value T) (key string, ok bool) {
|
||
|
buf := getBuf()
|
||
|
rvalue := reflect.ValueOf(value)
|
||
|
if ok = kgen.appendFromRValue(buf, rvalue); ok {
|
||
|
key = string(buf.B)
|
||
|
}
|
||
|
putBuf(buf)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// AppendFromParts generates key string into provided buffer, from individual key parts.
|
||
|
func (kgen *KeyGen[T]) AppendFromParts(buf *byteutil.Buffer, parts ...any) bool {
|
||
|
if len(parts) != len(kgen.fields) {
|
||
|
// User must provide correct number of parts for key.
|
||
|
panicf("incorrect number key parts: want=%d received=%d",
|
||
|
len(parts),
|
||
|
len(kgen.fields),
|
||
|
)
|
||
|
}
|
||
|
|
||
|
if kgen.zero {
|
||
|
// Zero values are permitted,
|
||
|
// mangle all values and ignore
|
||
|
// zero value return booleans.
|
||
|
for i, part := range parts {
|
||
|
|
||
|
// Mangle this value into buffer.
|
||
|
_ = kgen.fields[i].Mangle(buf, part)
|
||
|
|
||
|
// Append part separator.
|
||
|
buf.B = append(buf.B, '.')
|
||
|
}
|
||
|
} else {
|
||
|
// Zero values are NOT permitted.
|
||
|
for i, part := range parts {
|
||
|
|
||
|
// Mangle this value into buffer.
|
||
|
z := kgen.fields[i].Mangle(buf, part)
|
||
|
|
||
|
if z {
|
||
|
// The value was zero for
|
||
|
// this type, return early.
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// Append part separator.
|
||
|
buf.B = append(buf.B, '.')
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Drop the last separator.
|
||
|
buf.B = buf.B[:len(buf.B)-1]
|
||
|
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
// AppendFromValue generates key string into provided buffer, from a value via reflection.
|
||
|
func (kgen *KeyGen[T]) AppendFromValue(buf *byteutil.Buffer, value T) bool {
|
||
|
return kgen.appendFromRValue(buf, reflect.ValueOf(value))
|
||
|
}
|
||
|
|
||
|
// appendFromRValue is the underlying generator function for the exported ___FromValue() functions,
|
||
|
// accepting a reflected input. We do not expose this as the reflected value is EXPECTED to be right.
|
||
|
func (kgen *KeyGen[T]) appendFromRValue(buf *byteutil.Buffer, rvalue reflect.Value) bool {
|
||
|
// Follow any ptrs leading to value.
|
||
|
for rvalue.Kind() == reflect.Pointer {
|
||
|
rvalue = rvalue.Elem()
|
||
|
}
|
||
|
|
||
|
if kgen.zero {
|
||
|
// Zero values are permitted,
|
||
|
// mangle all values and ignore
|
||
|
// zero value return booleans.
|
||
|
for i := range kgen.fields {
|
||
|
|
||
|
// Get the reflect value's field at idx.
|
||
|
fv := rvalue.FieldByIndex(kgen.fields[i].index)
|
||
|
fi := fv.Interface()
|
||
|
|
||
|
// Mangle this value into buffer.
|
||
|
_ = kgen.fields[i].Mangle(buf, fi)
|
||
|
|
||
|
// Append part separator.
|
||
|
buf.B = append(buf.B, '.')
|
||
|
}
|
||
|
} else {
|
||
|
// Zero values are NOT permitted.
|
||
|
for i := range kgen.fields {
|
||
|
|
||
|
// Get the reflect value's field at idx.
|
||
|
fv := rvalue.FieldByIndex(kgen.fields[i].index)
|
||
|
fi := fv.Interface()
|
||
|
|
||
|
// Mangle this value into buffer.
|
||
|
z := kgen.fields[i].Mangle(buf, fi)
|
||
|
|
||
|
if z {
|
||
|
// The value was zero for
|
||
|
// this type, return early.
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// Append part separator.
|
||
|
buf.B = append(buf.B, '.')
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Drop the last separator.
|
||
|
buf.B = buf.B[:len(buf.B)-1]
|
||
|
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
type structfield struct {
|
||
|
// index is the reflected index
|
||
|
// of this field (this takes into
|
||
|
// account struct nesting).
|
||
|
index []int
|
||
|
|
||
|
// zero is the possible mangled
|
||
|
// zero value for this field.
|
||
|
zero string
|
||
|
|
||
|
// mangler is the mangler function for
|
||
|
// serializing values of this field.
|
||
|
mangler mangler.Mangler
|
||
|
}
|
||
|
|
||
|
// Mangle mangles the given value, using the determined type-appropriate
|
||
|
// field's type. The returned boolean indicates whether this is a zero value.
|
||
|
func (f *structfield) Mangle(buf *byteutil.Buffer, value any) (isZero bool) {
|
||
|
s := len(buf.B) // start pos.
|
||
|
buf.B = f.mangler(buf.B, value)
|
||
|
e := len(buf.B) // end pos.
|
||
|
isZero = (f.zero == string(buf.B[s:e]))
|
||
|
return
|
||
|
}
|