185 lines
4.4 KiB
Go
185 lines
4.4 KiB
Go
package result
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
|
|
"codeberg.org/gruf/go-byteutil"
|
|
"codeberg.org/gruf/go-mangler"
|
|
)
|
|
|
|
// structKeys provides convience methods for a list
|
|
// of struct field combinations used for cache keys.
|
|
type structKeys []keyFields
|
|
|
|
// get fetches the key-fields for given lookup (else, panics).
|
|
func (sk structKeys) get(lookup string) *keyFields {
|
|
for i := range sk {
|
|
if sk[i].lookup == lookup {
|
|
return &sk[i]
|
|
}
|
|
}
|
|
panic("unknown lookup: \"" + lookup + "\"")
|
|
}
|
|
|
|
// generate will calculate the value string for each required
|
|
// cache key as laid-out by the receiving structKeys{}.
|
|
func (sk structKeys) generate(a any) []cacheKey {
|
|
// Get reflected value in order
|
|
// to access the struct fields
|
|
v := reflect.ValueOf(a)
|
|
|
|
// Iteratively deref pointer value
|
|
for v.Kind() == reflect.Pointer {
|
|
if v.IsNil() {
|
|
panic("nil ptr")
|
|
}
|
|
v = v.Elem()
|
|
}
|
|
|
|
// Preallocate expected slice of keys
|
|
keys := make([]cacheKey, len(sk))
|
|
|
|
// Acquire byte buffer
|
|
buf := bufpool.Get().(*byteutil.Buffer)
|
|
defer bufpool.Put(buf)
|
|
|
|
for i := range sk {
|
|
// Reset buffer
|
|
buf.B = buf.B[:0]
|
|
|
|
// Set the key-fields reference
|
|
keys[i].fields = &sk[i]
|
|
|
|
// Calculate cache-key value
|
|
keys[i].populate(buf, v)
|
|
}
|
|
|
|
return keys
|
|
}
|
|
|
|
// cacheKey represents an actual cache key.
|
|
type cacheKey struct {
|
|
// value is the actual string representing
|
|
// this cache key for hashmap lookups.
|
|
value string
|
|
|
|
// fieldsRO is a read-only slice (i.e. we should
|
|
// NOT be modifying them, only using for reference)
|
|
// of struct fields encapsulated by this cache key.
|
|
fields *keyFields
|
|
}
|
|
|
|
// populate will calculate the cache key's value string for given
|
|
// value's reflected information. Passed encoder is for string building.
|
|
func (k *cacheKey) populate(buf *byteutil.Buffer, v reflect.Value) {
|
|
// Append each field value to buffer.
|
|
for _, idx := range k.fields.fields {
|
|
fv := v.Field(idx)
|
|
fi := fv.Interface()
|
|
buf.B = mangler.Append(buf.B, fi)
|
|
buf.B = append(buf.B, '.')
|
|
}
|
|
|
|
// Drop last '.'
|
|
buf.Truncate(1)
|
|
|
|
// Create string copy from buf
|
|
k.value = string(buf.B)
|
|
}
|
|
|
|
// keyFields represents a list of struct fields
|
|
// encompassed in a single cache key, the string name
|
|
// of the lookup, and the lookup map to primary keys.
|
|
type keyFields struct {
|
|
// lookup is the calculated (well, provided)
|
|
// cache key lookup, consisting of dot sep'd
|
|
// struct field names.
|
|
lookup string
|
|
|
|
// fields is a slice of runtime struct field
|
|
// indices, of the fields encompassed by this key.
|
|
fields []int
|
|
|
|
// pkeys is a lookup of stored struct key values
|
|
// to the primary cache lookup key (int64).
|
|
pkeys map[string]int64
|
|
}
|
|
|
|
// populate will populate this keyFields{} object's .fields member by determining
|
|
// the field names from the given lookup, and querying given reflected type to get
|
|
// the runtime field indices for each of the fields. this speeds-up future value lookups.
|
|
func (kf *keyFields) populate(t reflect.Type) {
|
|
// Split dot-separated lookup to get
|
|
// the individual struct field names
|
|
names := strings.Split(kf.lookup, ".")
|
|
if len(names) == 0 {
|
|
panic("no key fields specified")
|
|
}
|
|
|
|
// Pre-allocate slice of expected length
|
|
kf.fields = make([]int, len(names))
|
|
|
|
for i, name := range names {
|
|
// Get field info for given name
|
|
ft, ok := t.FieldByName(name)
|
|
if !ok {
|
|
panic("no field found for name: \"" + name + "\"")
|
|
}
|
|
|
|
// Check field is usable
|
|
if !isExported(name) {
|
|
panic("field must be exported")
|
|
}
|
|
|
|
// Set the runtime field index
|
|
kf.fields[i] = ft.Index[0]
|
|
}
|
|
}
|
|
|
|
// genkey generates a cache key for given key values.
|
|
func genkey(parts ...any) string {
|
|
if len(parts) < 1 {
|
|
// Panic to prevent annoying usecase
|
|
// where user forgets to pass lookup
|
|
// and instead only passes a key part,
|
|
// e.g. cache.Get("key")
|
|
// which then always returns false.
|
|
panic("no key parts provided")
|
|
}
|
|
|
|
// Acquire buffer and reset
|
|
buf := bufpool.Get().(*byteutil.Buffer)
|
|
defer bufpool.Put(buf)
|
|
buf.Reset()
|
|
|
|
// Encode each key part
|
|
for _, part := range parts {
|
|
buf.B = mangler.Append(buf.B, part)
|
|
buf.B = append(buf.B, '.')
|
|
}
|
|
|
|
// Drop last '.'
|
|
buf.Truncate(1)
|
|
|
|
// Return string copy
|
|
return string(buf.B)
|
|
}
|
|
|
|
// isExported checks whether function name is exported.
|
|
func isExported(fnName string) bool {
|
|
r, _ := utf8.DecodeRuneInString(fnName)
|
|
return unicode.IsUpper(r)
|
|
}
|
|
|
|
// bufpool provides a memory pool of byte
|
|
// buffers use when encoding key types.
|
|
var bufpool = sync.Pool{
|
|
New: func() any {
|
|
return &byteutil.Buffer{B: make([]byte, 0, 512)}
|
|
},
|
|
}
|