File: helper.go

package info (click to toggle)
golang-github-olekukonko-errors 1.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental, forky, sid
  • size: 448 kB
  • sloc: makefile: 2
file content (432 lines) | stat: -rw-r--r-- 11,214 bytes parent folder | download | duplicates (2)
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
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
package errors

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"
)

// As wraps errors.As, using custom type assertion for *Error types.
// Falls back to standard errors.As for non-*Error types.
// Returns false if either err or target is nil.
func As(err error, target interface{}) bool {
	if err == nil || target == nil {
		return false
	}

	// First try our custom *Error handling
	if e, ok := err.(*Error); ok {
		return e.As(target)
	}

	// Fall back to standard errors.As
	return errors.As(err, target)
}

// Code returns the status code of an error, if it is an *Error.
// Returns 500 as a default for non-*Error types to indicate an internal error.
func Code(err error) int {
	if e, ok := err.(*Error); ok {
		return e.Code()
	}
	return DefaultCode
}

// Context extracts the context map from an error, if it is an *Error.
// Returns nil for non-*Error types or if no context is present.
func Context(err error) map[string]interface{} {
	if e, ok := err.(*Error); ok {
		return e.Context()
	}
	return nil
}

// Convert transforms any error into an *Error, preserving its message and wrapping it if needed.
// Returns nil if the input is nil; returns the original if already an *Error.
// Uses multiple strategies: direct assertion, errors.As, manual unwrapping, and fallback creation.
func Convert(err error) *Error {
	if err == nil {
		return nil
	}

	// First try direct type assertion (fast path)
	if e, ok := err.(*Error); ok {
		return e
	}

	// Try using errors.As (more flexible)
	var e *Error
	if errors.As(err, &e) {
		return e
	}

	// Manual unwrapping as fallback
	visited := make(map[error]bool)
	for unwrapped := err; unwrapped != nil; {
		if visited[unwrapped] {
			break // Cycle detected
		}
		visited[unwrapped] = true
		if e, ok := unwrapped.(*Error); ok {
			return e
		}
		unwrapped = errors.Unwrap(unwrapped)
	}

	// Final fallback: create new error with original message and wrap it
	return New(err.Error()).Wrap(err)
}

// Count returns the occurrence count of an error, if it is an *Error.
// Returns 0 for non-*Error types.
func Count(err error) uint64 {
	if e, ok := err.(*Error); ok {
		return e.Count()
	}
	return 0
}

// Find searches the error chain for the first error matching pred.
// Returns nil if no match is found or pred is nil; traverses both Unwrap() and Cause() chains.
func Find(err error, pred func(error) bool) error {
	for current := err; current != nil; {
		if pred(current) {
			return current
		}

		// Attempt to unwrap using Unwrap() or Cause()
		switch v := current.(type) {
		case interface{ Unwrap() error }:
			current = v.Unwrap()
		case interface{ Cause() error }:
			current = v.Cause()
		default:
			return nil
		}
	}
	return nil
}

// From transforms any error into an *Error, preserving its message and wrapping it if needed.
// Alias of Convert; returns nil if input is nil, original if already an *Error.
func From(err error) *Error {
	return Convert(err)
}

// FromContext creates an *Error from a context and an existing error.
// Enhances the error with context info: timeout status, deadline, or cancellation.
// Returns nil if input error is nil; does not store context values directly.
func FromContext(ctx context.Context, err error) *Error {
	if err == nil {
		return nil
	}

	e := New(err.Error())

	// Handle context errors
	switch ctx.Err() {
	case context.DeadlineExceeded:
		e.WithTimeout()
		if deadline, ok := ctx.Deadline(); ok {
			e.With("deadline", deadline.Format(time.RFC3339))
		}
	case context.Canceled:
		e.With("cancelled", true)
	}

	return e
}

// Category returns the category of an error, if it is an *Error.
// Returns an empty string for non-*Error types or unset categories.
func Category(err error) string {
	if e, ok := err.(*Error); ok {
		return e.category
	}
	return ""
}

// Has checks if an error contains meaningful content.
// Returns true for non-nil standard errors or *Error with content (msg, name, template, or cause).
func Has(err error) bool {
	if e, ok := err.(*Error); ok {
		return e.Has()
	}
	return err != nil
}

// HasContextKey checks if the error's context contains the specified key.
// Returns false for non-*Error types or if the key is not present in the context.
func HasContextKey(err error, key string) bool {
	if e, ok := err.(*Error); ok {
		ctx := e.Context()
		if ctx != nil {
			_, exists := ctx[key]
			return exists
		}
	}
	return false
}

// Is wraps errors.Is, using custom matching for *Error types.
// Falls back to standard errors.Is for non-*Error types; returns true if err equals target.
func Is(err, target error) bool {
	if err == nil || target == nil {
		return err == target
	}

	if e, ok := err.(*Error); ok {
		return e.Is(target)
	}

	// Use standard errors.Is for non-Error types
	return errors.Is(err, target)
}

// IsError checks if an error is an instance of *Error.
// Returns true only for this package's custom error type; false for nil or other types.
func IsError(err error) bool {
	_, ok := err.(*Error)
	return ok
}

// IsEmpty checks if an error has no meaningful content.
// Returns true for nil errors, empty *Error instances, or standard errors with whitespace-only messages.
func IsEmpty(err error) bool {
	if err == nil {
		return true
	}
	if e, ok := err.(*Error); ok {
		return e.IsEmpty()
	}
	return strings.TrimSpace(err.Error()) == ""
}

// IsNull checks if an error is nil or represents a NULL value.
// Delegates to *Error’s IsNull for custom errors; uses sqlNull for others.
func IsNull(err error) bool {
	if err == nil {
		return true
	}
	if e, ok := err.(*Error); ok {
		return e.IsNull()
	}
	return sqlNull(err)
}

// IsRetryable checks if an error is retryable.
// For *Error, checks context for retry flag; for others, looks for "retry" or timeout in message.
// Returns false for nil errors; thread-safe for *Error types.
func IsRetryable(err error) bool {
	if err == nil {
		return false
	}
	if e, ok := err.(*Error); ok {
		e.mu.RLock()
		defer e.mu.RUnlock()
		// Check smallContext directly if context map isn’t populated
		for i := int32(0); i < e.smallCount; i++ {
			if e.smallContext[i].key == ctxRetry {
				if val, ok := e.smallContext[i].value.(bool); ok {
					return val
				}
			}
		}
		// Check regular context
		if e.context != nil {
			if val, ok := e.context[ctxRetry].(bool); ok {
				return val
			}
		}
		// Check cause recursively
		if e.cause != nil {
			return IsRetryable(e.cause)
		}
	}
	lowerMsg := strings.ToLower(err.Error())
	return IsTimeout(err) || strings.Contains(lowerMsg, "retry")
}

// IsTimeout checks if an error indicates a timeout.
// For *Error, checks context for timeout flag; for others, looks for "timeout" in message.
// Returns false for nil errors.
func IsTimeout(err error) bool {
	if err == nil {
		return false
	}
	if e, ok := err.(*Error); ok {
		if val, ok := e.Context()[ctxTimeout].(bool); ok {
			return val
		}
	}
	return strings.Contains(strings.ToLower(err.Error()), "timeout")
}

// Merge combines multiple errors into a single *Error.
// Aggregates messages with "; " separator, merges contexts and stacks; returns nil if no errors provided.
func Merge(errs ...error) *Error {
	if len(errs) == 0 {
		return nil
	}
	var messages []string
	combined := New("")
	for _, err := range errs {
		if err == nil {
			continue
		}
		messages = append(messages, err.Error())
		if e, ok := err.(*Error); ok {
			if e.stack != nil && combined.stack == nil {
				combined.WithStack() // Capture stack from first *Error with stack
			}
			if ctx := e.Context(); ctx != nil {
				for k, v := range ctx {
					combined.With(k, v)
				}
			}
			if e.cause != nil {
				combined.Wrap(e.cause)
			}
		} else {
			combined.Wrap(err)
		}
	}
	if len(messages) > 0 {
		combined.msg = strings.Join(messages, "; ")
	}
	return combined
}

// Name returns the name of an error, if it is an *Error.
// Returns an empty string for non-*Error types or unset names.
func Name(err error) string {
	if e, ok := err.(*Error); ok {
		return e.name
	}
	return ""
}

// UnwrapAll returns a slice of all errors in the chain, including the root error.
// Traverses both Unwrap() and Cause() chains; returns nil if err is nil.
func UnwrapAll(err error) []error {
	if err == nil {
		return nil
	}
	if e, ok := err.(*Error); ok {
		return e.UnwrapAll()
	}
	var result []error
	Walk(err, func(e error) {
		result = append(result, e)
	})
	return result
}

// Stack extracts the stack trace from an error, if it is an *Error.
// Returns nil for non-*Error types or if no stack is present.
func Stack(err error) []string {
	if e, ok := err.(*Error); ok {
		return e.Stack()
	}
	return nil
}

// Transform applies transformations to an error, returning a new *Error.
// Creates a new *Error from non-*Error types before applying fn; returns nil if err is nil.
func Transform(err error, fn func(*Error)) *Error {
	if err == nil {
		return nil
	}
	if e, ok := err.(*Error); ok {
		newErr := e.Copy()
		fn(newErr)
		return newErr
	}
	// If not an *Error, create a new one and transform it
	newErr := New(err.Error())
	fn(newErr)
	return newErr
}

// Unwrap returns the underlying cause of an error, if it implements Unwrap.
// For *Error, returns cause; for others, returns the error itself; nil if err is nil.
func Unwrap(err error) error {
	for current := err; current != nil; {
		if e, ok := current.(*Error); ok {
			if e.cause == nil {
				return current
			}
			current = e.cause
		} else {
			return current
		}
	}
	return nil
}

// Walk traverses the error chain, applying fn to each error.
// Supports both Unwrap() and Cause() interfaces; stops at nil or non-unwrappable errors.
func Walk(err error, fn func(error)) {
	for current := err; current != nil; {
		fn(current)

		// Attempt to unwrap using Unwrap() or Cause()
		switch v := current.(type) {
		case interface{ Unwrap() error }:
			current = v.Unwrap()
		case interface{ Cause() error }:
			current = v.Cause()
		default:
			return
		}
	}
}

// With adds a key-value pair to an error's context, if it is an *Error.
// Returns the original error unchanged if not an *Error; no-op for non-*Error types.
func With(err error, key string, value interface{}) error {
	if e, ok := err.(*Error); ok {
		return e.With(key, value)
	}
	return err
}

// WithStack converts any error to an *Error and captures a stack trace.
// Returns nil if input is nil; adds stack to existing *Error or wraps non-*Error types.
func WithStack(err error) *Error {
	if err == nil {
		return nil
	}
	if e, ok := err.(*Error); ok {
		return e.WithStack()
	}
	return New(err.Error()).WithStack().Wrap(err)
}

// Wrap creates a new *Error that wraps another error with additional context.
// Uses a copy of the provided wrapper *Error; returns nil if err is nil.
func Wrap(err error, wrapper *Error) *Error {
	if err == nil {
		return nil
	}
	if wrapper == nil {
		wrapper = newError()
	}
	newErr := wrapper.Copy()
	newErr.cause = err
	return newErr
}

// Wrapf creates a new formatted *Error that wraps another error.
// Formats the message and sets the cause; returns nil if err is nil.
func Wrapf(err error, format string, args ...interface{}) *Error {
	if err == nil {
		return nil
	}
	e := newError()
	e.msg = fmt.Sprintf(format, args...)
	e.cause = err
	return e
}