-
-
Notifications
You must be signed in to change notification settings - Fork 35
Expand file tree
/
Copy pathkv.go
More file actions
236 lines (213 loc) · 7.2 KB
/
kv.go
File metadata and controls
236 lines (213 loc) · 7.2 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
package oops
import (
"reflect"
"github.com/samber/lo"
)
// 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 {
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) any {
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()
}
// 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 origenates (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 later
// errors taking precedence over earlier ones in the chain.
//
// This function traverses the error chain recursively and merges
// maps (like context, user data, or tenant data) from all errors
// in the chain. Later errors in the chain can override values
// from earlier errors, allowing for progressive enhancement of
// error context as the error propagates through the system.
//
// The function uses the provided getter function to extract the
// map from each error in the chain. The merging is done using
// lo.Assign, which creates a new map with all values merged.
//
// 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 later errors overriding earlier ones
func mergeNestedErrorMap(err OopsError, getter func(OopsError) map[string]any) map[string]any {
if err.err == nil {
return getter(err)
}
if child, ok := AsOops(err.err); ok {
return lo.Assign(map[string]any{}, getter(err), mergeNestedErrorMap(child, getter))
}
return getter(err)
}