-
-
Notifications
You must be signed in to change notification settings - Fork 35
Expand file tree
/
Copy pathkv.go
More file actions
305 lines (274 loc) · 9 KB
/
kv.go
File metadata and controls
305 lines (274 loc) · 9 KB
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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
package oops
import (
"fmt"
"reflect"
"time"
)
// dereferencePointers recursively dereferences pointer values in a map
// to provide more useful logging and debugging information.
//
// This function processes all values in the input map and recursively
// dereferences pointer types to extract their underlying values. This
// is particularly useful for logging where you want to see the actual
// data rather than pointer addresses.
//
// The function respects the global DereferencePointers configuration
// flag. If this flag is false, the function returns the input map
// unchanged.
//
// Example usage:
//
// data := map[string]any{
// "user": &User{Name: "John"},
// "count": lo.ToPtr(42),
// }
// result := dereferencePointers(data)
// // result["user"] will be User{Name: "John"} instead of *User
// // result["count"] will be 42 instead of *int
func dereferencePointers(data map[string]any) map[string]any {
if !DereferencePointers {
return data
}
for key, value := range data {
// Fast path: only use reflect for types that could be pointers
switch value.(type) {
case nil, string, int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64,
float32, float64, bool, []byte,
map[string]any, []any:
continue // not a pointer, skip
}
val := reflect.ValueOf(value)
if val.Kind() == reflect.Ptr {
data[key] = dereferencePointerRecursive(val, 0)
}
}
return data
}
// dereferencePointerRecursive recursively dereferences pointer values
// to extract their underlying data, with protection against infinite
// recursion and nil pointers.
//
// This function handles complex pointer structures by recursively
// following pointer chains until it reaches a non-pointer value or
// hits safety limits. It includes several safety mechanisms:
// - Depth limiting to prevent infinite recursion (max 10 levels)
// - Nil pointer handling to prevent panics
// - Invalid value handling for edge cases
//
// Example usage:
//
// var ptr1 *int = lo.ToPtr(42)
// var ptr2 **int = &ptr1
// var ptr3 ***int = &ptr2
//
// val := reflect.ValueOf(ptr3)
// result := dereferencePointerRecursive(val, 0)
// // result will be 42 (int), not ***int
func dereferencePointerRecursive(val reflect.Value, depth int) (ret any) {
defer func() {
if r := recover(); r != nil {
ret = nil
}
}()
if !val.IsValid() {
return nil
}
if val.Kind() != reflect.Ptr {
return val.Interface()
}
if val.IsNil() {
return nil
}
// Prevent infinite recursion with circular references
if depth > 10 {
return val.Interface()
}
elem := val.Elem()
if !elem.IsValid() {
return nil
}
// Recursively handle nested pointers
if elem.Kind() == reflect.Ptr {
return dereferencePointerRecursive(elem, depth+1)
}
return elem.Interface()
}
// lazyMapEvaluation processes a map and evaluates any lazy evaluation
// functions (functions with no arguments and one return value) to
// extract their computed values.
//
// This function enables lazy evaluation of expensive operations in
// error context. Instead of computing values immediately when the
// error is created, you can provide functions that will be evaluated
// only when the error data is actually accessed (e.g., during logging).
//
// The function recursively processes nested maps to handle complex
// data structures. It identifies functions by checking if they have
// no input parameters and exactly one output parameter.
//
// Example usage:
//
// data := map[string]any{
// "timestamp": time.Now,
// "expensive": func() any { return computeExpensiveValue() },
// "simple": "static value",
// }
// result := lazyMapEvaluation(data)
// // result["timestamp"] will be the actual time.Time value
// // result["expensive"] will be the computed value
// // result["simple"] will remain "static value"
func lazyMapEvaluation(data map[string]any) map[string]any {
for key, value := range data {
switch v := value.(type) {
case map[string]any:
data[key] = lazyMapEvaluation(v)
default:
data[key] = lazyValueEvaluation(value)
}
}
return data
}
// lazyValueEvaluation evaluates a single value, checking if it's a
// lazy evaluation function and executing it if so.
//
// This function identifies lazy evaluation functions by checking their
// signature: they must have no input parameters and exactly one output
// parameter. When such a function is found, it's executed and the
// result is returned instead of the function itself.
//
// This enables deferred computation of expensive values, which is
// particularly useful for error context where you want to capture
// the current state but defer expensive computations until they're
// actually needed.
//
// Example usage:
//
// value := func() any { return expensiveComputation() }
// result := lazyValueEvaluation(value)
// // result will be the result of expensiveComputation()
//
// value := "static string"
// result := lazyValueEvaluation(value)
// // result will be "static string" (unchanged)
func lazyValueEvaluation(value any) (ret any) {
// Fast path: common types are never lazy evaluation functions
switch value.(type) {
case nil, string, int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64,
float32, float64, bool, []byte,
time.Time, time.Duration:
return value
}
defer func() {
if r := recover(); r != nil {
ret = fmt.Sprintf("panic in lazy evaluation: %v", r)
}
}()
v := reflect.ValueOf(value)
if !v.IsValid() || v.Kind() != reflect.Func {
return value
}
// Check if this is a lazy evaluation function (no args, one return)
if v.Type().NumIn() != 0 || v.Type().NumOut() != 1 {
return value
}
return v.Call([]reflect.Value{})[0].Interface()
}
// recursive is a helper function that traverses the error chain
// and applies a function to each OopsError in the chain.
func recursive(err OopsError, tap func(OopsError) bool) {
if !tap(err) {
return
}
if err.err == nil {
return
}
if child, ok := AsOops(err.err); ok {
recursive(child, tap)
}
}
// // recursive is a helper function that traverses the error chain
// // and applies a function to each OopsError in the chain.
// func recursiveBackward(err OopsError, tap func(OopsError)) {
// if err.err == nil {
// tap(err)
// return
// }
// if child, ok := AsOops(err.err); ok {
// recursiveBackward(child, tap)
// }
// tap(err)
// }
// getDeepestErrorAttribute extracts an attribute from the deepest error
// in an error chain, with fallback to the current error if the deepest
// error doesn't have the attribute set.
//
// This function traverses the error chain recursively to find the
// deepest oops.OopsError and extracts the specified attribute using
// the provided getter function. If the deepest error doesn't have
// the attribute set (returns a zero value), it falls back to the
// current error's attribute.
//
// This behavior is useful for attributes that should be set at the
// point where the error originates (like error codes, hints, or
// public messages) but can be overridden by wrapping errors.
//
// Example usage:
//
// code := getDeepestErrorAttribute(err, func(e OopsError) string {
// return e.code
// })
// // Returns the error code from the deepest error in the chain
func getDeepestErrorAttribute[T comparable](err OopsError, getter func(OopsError) T) T {
if err.err == nil {
return getter(err)
}
if child, ok := AsOops(err.err); ok {
return coalesceOrEmpty(getDeepestErrorAttribute(child, getter), getter(err))
}
return getter(err)
}
// mergeNestedErrorMap merges maps from an error chain, with deeper errors
// taking precedence over shallower ones (matching the original lo.Assign
// left-to-right semantics where the child/deeper map was the last argument).
//
// This function traverses the error chain and collects all maps via
// collectMaps, then merges them in a single pass — avoiding the O(n)
// intermediate map allocations that the recursive lo.Assign approach incurred.
//
// Example usage:
//
// context := mergeNestedErrorMap(err, func(e OopsError) map[string]any {
// return e.context
// })
// // Returns a merged map with context from all errors in the chain,
// // with deeper errors overriding shallower ones.
func mergeNestedErrorMap(err OopsError, getter func(OopsError) map[string]any) map[string]any {
// Collect all maps from the chain (shallower first, deeper last).
var maps []map[string]any
collectMaps(err, getter, &maps)
if len(maps) == 0 {
return map[string]any{}
}
// Merge into a single result: deeper maps (appended last) overwrite
// shallower ones, preserving the original semantics.
// Preallocate with the first map's length as a rough capacity hint.
result := make(map[string]any, len(maps[0]))
for _, m := range maps {
for k, v := range m {
result[k] = v
}
}
return result
}
// collectMaps appends maps from the error chain to result in shallow-to-deep
// order so that the final merge loop gives deeper errors higher precedence.
func collectMaps(err OopsError, getter func(OopsError) map[string]any, result *[]map[string]any) {
if m := getter(err); len(m) > 0 {
*result = append(*result, m)
}
if child, ok := AsOops(err.err); ok {
collectMaps(child, getter, result)
}
}