summaryrefslogtreecommitdiff
path: root/vendor/codeberg.org/gruf/go-structr/key.go
diff options
context:
space:
mode:
authorLibravatar kim <89579420+NyaaaWhatsUpDoc@users.noreply.github.com>2024-01-19 12:57:29 +0000
committerLibravatar GitHub <noreply@github.com>2024-01-19 12:57:29 +0000
commit7ec1e1332e7d04e74451acef18b41f389722b698 (patch)
tree9c69eca7fc664ab5564279a2e065dfd5c2ddd17b /vendor/codeberg.org/gruf/go-structr/key.go
parent[chore] chore rationalise http return codes for activitypub handlers (#2540) (diff)
downloadgotosocial-7ec1e1332e7d04e74451acef18b41f389722b698.tar.xz
[performance] overhaul struct (+ result) caching library for simplicity, performance and multiple-result lookups (#2535)
* rewrite cache library as codeberg.org/gruf/go-structr, implement in gotosocial * use actual go-structr release version (not just commit hash) * revert go toolchain changes (damn you go for auto changing this) * fix go mod woes * ensure %w is used in calls to errs.Appendf() * fix error checking * fix possible panic * remove unnecessary start/stop functions, move to main Cache{} struct, add note regarding which caches require start/stop * fix copy-paste artifact... :innocent: * fix all comment copy-paste artifacts * remove dropID() function, now we can just use slices.DeleteFunc() * use util.Deduplicate() instead of collate(), move collate to util * move orderByIDs() to util package and "generify" * add a util.DeleteIf() function, use this to delete entries on failed population * use slices.DeleteFunc() instead of util.DeleteIf() (i had the logic mixed up in my head somehow lol) * add note about how collate differs from deduplicate
Diffstat (limited to 'vendor/codeberg.org/gruf/go-structr/key.go')
-rw-r--r--vendor/codeberg.org/gruf/go-structr/key.go204
1 files changed, 204 insertions, 0 deletions
diff --git a/vendor/codeberg.org/gruf/go-structr/key.go b/vendor/codeberg.org/gruf/go-structr/key.go
new file mode 100644
index 000000000..557a5f033
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-structr/key.go
@@ -0,0 +1,204 @@
+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
+}