summaryrefslogtreecommitdiff
path: root/vendor/codeberg.org/gruf/go-cache/v3/result/key.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/codeberg.org/gruf/go-cache/v3/result/key.go')
-rw-r--r--vendor/codeberg.org/gruf/go-cache/v3/result/key.go184
1 files changed, 184 insertions, 0 deletions
diff --git a/vendor/codeberg.org/gruf/go-cache/v3/result/key.go b/vendor/codeberg.org/gruf/go-cache/v3/result/key.go
new file mode 100644
index 000000000..ec58e0ef9
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-cache/v3/result/key.go
@@ -0,0 +1,184 @@
+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)}
+ },
+}