error.go

v0.2.0
Doc Versions Source
1
// Package culpa provides structured error handling with typed details,
2
// stacktrace capture, and chain traversal.
3
package culpa
4
5
import (
6
	"fmt"
7
	"strings"
8
)
9
10
// culpaError is the message-wrapping error type. It holds a message
11
// and an optional cause (inner error).
12
type culpaError struct {
13
	msg   string
14
	cause error
15
}
16
17
// Error returns the message chain: "outer: middle: inner".
18
func (e *culpaError) Error() string {
19
	if e.cause == nil {
20
		return e.msg
21
	}
22
	causeMsg := e.cause.Error()
23
	if e.msg == "" {
24
		return causeMsg
25
	}
26
	if causeMsg == "" {
27
		return e.msg
28
	}
29
	return e.msg + ": " + causeMsg
30
}
31
32
// Unwrap returns the wrapped cause error.
33
func (e *culpaError) Unwrap() error {
34
	return e.cause
35
}
36
37
// joinedError wraps multiple errors so that errors.Is/As can find any of them.
38
type joinedError struct {
39
	msg  string
40
	errs []error
41
}
42
43
func (e *joinedError) Error() string   { return e.msg }
44
func (e *joinedError) Unwrap() []error { return e.errs }
45
46
// Join combines multiple errors into one. Nil errors are filtered out.
47
// Returns nil if no non-nil errors are provided.
48
// Unlike [errors.Join], the returned error supports culpa formatting
49
// (fmt.Formatter, slog.LogValuer) and detail traversal.
50
func Join(errs ...error) error {
51
	var filtered []error
52
	for _, e := range errs {
53
		if e != nil {
54
			filtered = append(filtered, e)
55
		}
56
	}
57
	switch len(filtered) {
58
	case 0:
59
		return nil
60
	case 1:
61
		return filtered[0]
62
	default:
63
		msgs := make([]string, len(filtered))
64
		for i, e := range filtered {
65
			msgs[i] = e.Error()
66
		}
67
		return &joinedError{
68
			msg:  strings.Join(msgs, "\n"),
69
			errs: filtered,
70
		}
71
	}
72
}
73
74
// New creates a new error with a message.
75
func New(msg string) error {
76
	return &culpaError{msg: msg}
77
}
78
79
// Errorf creates a new error with a formatted message.
80
func Errorf(format string, args ...any) error {
81
	return &culpaError{msg: fmt.Sprintf(format, args...)}
82
}
83
84
// Wrap wraps an existing error with a message and captures a stacktrace.
85
// Returns nil if err is nil.
86
func Wrap(err error, msg string) error {
87
	if err == nil {
88
		return nil
89
	}
90
	return withStacktrace(&culpaError{msg: msg, cause: err})
91
}
92
93
// Wrapf wraps an existing error with a formatted message and captures
94
// a stacktrace. Returns nil if err is nil.
95
func Wrapf(err error, format string, args ...any) error {
96
	if err == nil {
97
		return nil
98
	}
99
	return withStacktrace(&culpaError{msg: fmt.Sprintf(format, args...), cause: err})
100
}
101

Source Files