summaryrefslogtreecommitdiff
path: root/vendor/codeberg.org/gruf/go-cache/v3/result/key.go
blob: 0dc1276a5c73ce53d0a062037a8f130d773fe47b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
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 structKey field combinations used for cache keys.
type structKeys []structKey

// get fetches the structKey info for given lookup name (else, panics).
func (sk structKeys) get(name string) *structKey {
	for i := range sk {
		if sk[i].name == name {
			return &sk[i]
		}
	}
	panic("unknown lookup: \"" + name + "\"")
}

// generate will calculate and produce a slice of cache keys the given value
// can be stored under in the, as determined by receiving struct keys.
func (sk structKeys) generate(a any) []cachedKey {
	var keys []cachedKey

	// 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()
	}

	// Acquire byte buffer
	buf := bufpool.Get().(*byteutil.Buffer)
	defer bufpool.Put(buf)

	for i := range sk {
		// Reset buffer
		buf.B = buf.B[:0]

		// Append each field value to buffer.
		for _, idx := range sk[i].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)

		// Don't generate keys for zero values
		if allowZero := sk[i].zero == ""; // nocollapse
		!allowZero && buf.String() == sk[i].zero {
			continue
		}

		// Append new cached key to slice
		keys = append(keys, cachedKey{
			key:   &sk[i],
			value: string(buf.B), // copy
		})
	}

	return keys
}

// cachedKey represents an actual cached key.
type cachedKey struct {
	// key is a reference to the structKey this
	// cacheKey is representing. This is a shared
	// reference and as such only the structKey.pkeys
	// lookup map is expecting to be modified.
	key *structKey

	// value is the actual string representing
	// this cache key for hashmap lookups.
	value string
}

// structKey represents a list of struct fields
// encompassing a single cache key, the string name
// of the lookup, the lookup map to primary cache
// keys, and the key's possible zero value string.
type structKey struct {
	// name is the provided cache lookup name for
	// this particular struct key, consisting of
	// period ('.') separated struct field names.
	name string

	// zero is the possible zero value for this key.
	// if set, this will _always_ be non-empty, as
	// the mangled cache key will never be empty.
	//
	// i.e. zero = ""  --> allow zero value keys
	//      zero != "" --> don't allow zero value keys
	zero 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
}

// genStructKey will generate a structKey{} information object for user-given lookup
// key information, and the receiving generic paramter's type information. Panics on error.
func genStructKey(lk Lookup, t reflect.Type) structKey {
	var zeros []any

	// Split dot-separated lookup to get
	// the individual struct field names
	names := strings.Split(lk.Name, ".")
	if len(names) == 0 {
		panic("no key fields specified")
	}

	// Pre-allocate slice of expected length
	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
		fields[i] = ft.Index[0]

		// Allocate new instance of field
		v := reflect.New(ft.Type)
		v = v.Elem()

		if !lk.AllowZero {
			// Append the zero value interface
			zeros = append(zeros, v.Interface())
		}
	}

	var zvalue string

	if len(zeros) > 0 {
		// Generate zero value string
		zvalue = genKey(zeros...)
	}

	return structKey{
		name:   lk.Name,
		zero:   zvalue,
		fields: fields,
		pkeys:  make(map[string]int64),
	}
}

// genKey generates a cache key for given key values.
func genKey(parts ...any) string {
	if len(parts) == 0 {
		// 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)}
	},
}