Skip to content

Commit d2028f1

Browse files
committed
Add WithStack and rename Error to Err to allow composition
1 parent a9c0873 commit d2028f1

File tree

4 files changed

+71
-22
lines changed

4 files changed

+71
-22
lines changed

error.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ package errors
22

33
import "fmt"
44

5-
// Error is the error struct used internally by the package. This type should only be used for type assertions.
6-
type Error struct {
5+
// Err is the error struct used internally by the package. This type should only be used for type assertions.
6+
type Err struct {
77
Message string `json:"message"`
88
Data Data `json:"data,omitempty"`
99
Stack Stack `json:"stack"`
1010
Cause error `json:"cause,omitempty"`
1111
}
1212

13-
func (e Error) Error() string {
13+
func (e Err) Error() string {
1414
if e.Cause != nil {
1515
return fmt.Sprintf("%s: %s", e.Message, e.Cause.Error())
1616
}
@@ -19,14 +19,27 @@ func (e Error) Error() string {
1919
}
2020

2121
// Format implements fmt.Formatter. It only accepts the '+v' and 's' formats.
22-
func (e Error) Format(s fmt.State, verb rune) {
22+
func (e Err) Format(s fmt.State, verb rune) {
2323
if verb == 'v' && s.Flag('+') {
2424
fmt.Fprintf(s, "%s", format(e, 0))
2525
} else {
2626
fmt.Fprintf(s, "%s", e.Error())
2727
}
2828
}
2929

30-
func (e Error) Unwrap() error {
30+
func (e Err) Unwrap() error {
3131
return e.Cause
3232
}
33+
34+
// WithStack adds a stack trace to the provided error if it is an Err or *Err.
35+
func WithStack(err error) error {
36+
if e, ok := err.(Err); ok {
37+
e.Stack = callers()
38+
return e
39+
} else if e, ok := err.(*Err); ok {
40+
e.Stack = callers()
41+
return e
42+
} else {
43+
return err
44+
}
45+
}

errors.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ type Data map[string]any
66

77
// New returns an error with the provided message.
88
func New(msg string) error {
9-
return &Error{
9+
return &Err{
1010
Message: msg,
1111
Stack: callers(),
1212
}
1313
}
1414

1515
// Errord returns an error with additional data and the provided message.
1616
func Errord(data Data, msg string) error {
17-
return &Error{
17+
return &Err{
1818
Message: msg,
1919
Data: data,
2020
Stack: callers(),
@@ -23,15 +23,15 @@ func Errord(data Data, msg string) error {
2323

2424
// Errorf returns an error with the provided format specifier.
2525
func Errorf(format string, args ...any) error {
26-
return &Error{
26+
return &Err{
2727
Message: fmt.Sprintf(format, args...),
2828
Stack: callers(),
2929
}
3030
}
3131

3232
// Errordf returns an error with additional data and the provided format specifier.
3333
func Errordf(data Data, format string, args ...any) error {
34-
return &Error{
34+
return &Err{
3535
Message: fmt.Sprintf(format, args...),
3636
Data: data,
3737
Stack: callers(),
@@ -40,7 +40,7 @@ func Errordf(data Data, format string, args ...any) error {
4040

4141
// Wrap returns an error wrapping err and adding the provided format specifier.
4242
func Wrap(err error, msg string) error {
43-
return &Error{
43+
return &Err{
4444
Message: msg,
4545
Stack: callers(),
4646
Cause: err,
@@ -49,7 +49,7 @@ func Wrap(err error, msg string) error {
4949

5050
// Wrapd returns an error wrapping err, adding additional data and the provided message.
5151
func Wrapd(err error, data Data, msg string) error {
52-
return &Error{
52+
return &Err{
5353
Message: msg,
5454
Data: data,
5555
Stack: callers(),
@@ -59,7 +59,7 @@ func Wrapd(err error, data Data, msg string) error {
5959

6060
// Wrapf returns an error wrapping err and adding the provided format specifier.
6161
func Wrapf(err error, format string, args ...any) error {
62-
return &Error{
62+
return &Err{
6363
Message: fmt.Sprintf(format, args...),
6464
Stack: callers(),
6565
Cause: err,
@@ -68,7 +68,7 @@ func Wrapf(err error, format string, args ...any) error {
6868

6969
// Wrapdf returns an error wrapping err, adding additional data and the provided format specifier.
7070
func Wrapdf(err error, data Data, format string, args ...any) error {
71-
return &Error{
71+
return &Err{
7272
Message: fmt.Sprintf(format, args...),
7373
Data: data,
7474
Stack: callers(),

errors_test.go

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55
"fmt"
66
"reflect"
7+
"strings"
78
"testing"
89
)
910

@@ -28,7 +29,7 @@ func TestErrorc(t *testing.T) {
2829
return
2930
}
3031

31-
if e := err.(*Error); !reflect.DeepEqual(e.Data, data) {
32+
if e := err.(*Err); !reflect.DeepEqual(e.Data, data) {
3233
t.Errorf(`wrong data, got %+v, expected %+v`, e.Data, data)
3334
return
3435
}
@@ -81,3 +82,36 @@ func TestWrapc(t *testing.T) {
8182
return
8283
}
8384
}
85+
86+
// CustomError is a custom error type composed with Err.
87+
type CustomError struct {
88+
*Err
89+
}
90+
91+
// NewCustomError returns a new CustomError and adds a stack trace.
92+
func NewCustomError(message string) error {
93+
customError := CustomError{Err: &Err{Message: message}}
94+
WithStack(customError.Err)
95+
return customError
96+
}
97+
98+
func TestWithStack(t *testing.T) {
99+
t.Run("when WithStack is called on a custom error type composed with Err, it should add a stack trace", func(t *testing.T) {
100+
err := NewCustomError("this is a custom error type with stack")
101+
102+
if err.(CustomError).Stack == nil {
103+
t.Errorf(`expected stack to be not nil, got nil`)
104+
return
105+
}
106+
107+
outputStr := fmt.Sprintf("%+v", err)
108+
if !strings.Contains(outputStr, "message:") {
109+
t.Errorf(`expected "message:" to be in the output string, got %v`, outputStr)
110+
return
111+
}
112+
if !strings.Contains(outputStr, "stack:") {
113+
t.Errorf(`expected "stack:" to be in the output string, got %v`, outputStr)
114+
return
115+
}
116+
})
117+
}

format.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@ import (
1010
// format returns a formatted string representation of the error and its cause.
1111
func format(err error, lvl int) string {
1212
t := reflect.TypeOf(err)
13-
if t != reflect.TypeOf(Error{}) && t != reflect.TypeOf(&Error{}) {
13+
if t != reflect.TypeOf(Err{}) && t != reflect.TypeOf(&Err{}) {
1414
return fmt.Sprintf("\t%s", err.Error())
1515
}
1616

17-
var e Error
17+
var e Err
1818
if t.Kind() == reflect.Ptr {
19-
ep := err.(*Error)
19+
ep := err.(*Err)
2020
e = *ep
2121
} else {
22-
e = err.(Error)
22+
e = err.(Err)
2323
}
2424

2525
var b bytes.Buffer
@@ -32,10 +32,12 @@ func format(err error, lvl int) string {
3232
}
3333
}
3434

35-
firstStackLine := e.Stack[0]
36-
b.WriteString(fmt.Sprintf("\nstack:\n%s", indent(firstStackLine, 1)))
37-
for i := 1; i < len(e.Stack); i++ {
38-
b.WriteString(fmt.Sprintf("\n%s", indent(e.Stack[i], 1)))
35+
if e.Stack != nil && len(e.Stack) > 0 {
36+
firstStackLine := e.Stack[0]
37+
b.WriteString(fmt.Sprintf("\nstack:\n%s", indent(firstStackLine, 1)))
38+
for i := 1; i < len(e.Stack); i++ {
39+
b.WriteString(fmt.Sprintf("\n%s", indent(e.Stack[i], 1)))
40+
}
3941
}
4042

4143
if e.Cause != nil {

0 commit comments

Comments
 (0)