Skip to content

Commit

Permalink
Add WithStack and rename Error to Err to allow composition
Browse files Browse the repository at this point in the history
  • Loading branch information
zignd committed Jul 3, 2024
1 parent a9c0873 commit d2028f1
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 22 deletions.
23 changes: 18 additions & 5 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ package errors

import "fmt"

// Error is the error struct used internally by the package. This type should only be used for type assertions.
type Error struct {
// Err is the error struct used internally by the package. This type should only be used for type assertions.
type Err struct {
Message string `json:"message"`
Data Data `json:"data,omitempty"`
Stack Stack `json:"stack"`
Cause error `json:"cause,omitempty"`
}

func (e Error) Error() string {
func (e Err) Error() string {
if e.Cause != nil {
return fmt.Sprintf("%s: %s", e.Message, e.Cause.Error())
}
Expand All @@ -19,14 +19,27 @@ func (e Error) Error() string {
}

// Format implements fmt.Formatter. It only accepts the '+v' and 's' formats.
func (e Error) Format(s fmt.State, verb rune) {
func (e Err) Format(s fmt.State, verb rune) {
if verb == 'v' && s.Flag('+') {
fmt.Fprintf(s, "%s", format(e, 0))
} else {
fmt.Fprintf(s, "%s", e.Error())
}
}

func (e Error) Unwrap() error {
func (e Err) Unwrap() error {
return e.Cause
}

// WithStack adds a stack trace to the provided error if it is an Err or *Err.
func WithStack(err error) error {
if e, ok := err.(Err); ok {
e.Stack = callers()
return e
} else if e, ok := err.(*Err); ok {
e.Stack = callers()
return e
} else {
return err
}
}
16 changes: 8 additions & 8 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ type Data map[string]any

// New returns an error with the provided message.
func New(msg string) error {
return &Error{
return &Err{
Message: msg,
Stack: callers(),
}
}

// Errord returns an error with additional data and the provided message.
func Errord(data Data, msg string) error {
return &Error{
return &Err{
Message: msg,
Data: data,
Stack: callers(),
Expand All @@ -23,15 +23,15 @@ func Errord(data Data, msg string) error {

// Errorf returns an error with the provided format specifier.
func Errorf(format string, args ...any) error {
return &Error{
return &Err{
Message: fmt.Sprintf(format, args...),
Stack: callers(),
}
}

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

// Wrap returns an error wrapping err and adding the provided format specifier.
func Wrap(err error, msg string) error {
return &Error{
return &Err{
Message: msg,
Stack: callers(),
Cause: err,
Expand All @@ -49,7 +49,7 @@ func Wrap(err error, msg string) error {

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

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

// Wrapdf returns an error wrapping err, adding additional data and the provided format specifier.
func Wrapdf(err error, data Data, format string, args ...any) error {
return &Error{
return &Err{
Message: fmt.Sprintf(format, args...),
Data: data,
Stack: callers(),
Expand Down
36 changes: 35 additions & 1 deletion errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"reflect"
"strings"
"testing"
)

Expand All @@ -28,7 +29,7 @@ func TestErrorc(t *testing.T) {
return
}

if e := err.(*Error); !reflect.DeepEqual(e.Data, data) {
if e := err.(*Err); !reflect.DeepEqual(e.Data, data) {
t.Errorf(`wrong data, got %+v, expected %+v`, e.Data, data)
return
}
Expand Down Expand Up @@ -81,3 +82,36 @@ func TestWrapc(t *testing.T) {
return
}
}

// CustomError is a custom error type composed with Err.
type CustomError struct {
*Err
}

// NewCustomError returns a new CustomError and adds a stack trace.
func NewCustomError(message string) error {
customError := CustomError{Err: &Err{Message: message}}
WithStack(customError.Err)
return customError
}

func TestWithStack(t *testing.T) {
t.Run("when WithStack is called on a custom error type composed with Err, it should add a stack trace", func(t *testing.T) {
err := NewCustomError("this is a custom error type with stack")

if err.(CustomError).Stack == nil {
t.Errorf(`expected stack to be not nil, got nil`)
return
}

outputStr := fmt.Sprintf("%+v", err)
if !strings.Contains(outputStr, "message:") {
t.Errorf(`expected "message:" to be in the output string, got %v`, outputStr)
return
}
if !strings.Contains(outputStr, "stack:") {
t.Errorf(`expected "stack:" to be in the output string, got %v`, outputStr)
return
}
})
}
18 changes: 10 additions & 8 deletions format.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ import (
// format returns a formatted string representation of the error and its cause.
func format(err error, lvl int) string {
t := reflect.TypeOf(err)
if t != reflect.TypeOf(Error{}) && t != reflect.TypeOf(&Error{}) {
if t != reflect.TypeOf(Err{}) && t != reflect.TypeOf(&Err{}) {
return fmt.Sprintf("\t%s", err.Error())
}

var e Error
var e Err
if t.Kind() == reflect.Ptr {
ep := err.(*Error)
ep := err.(*Err)
e = *ep
} else {
e = err.(Error)
e = err.(Err)
}

var b bytes.Buffer
Expand All @@ -32,10 +32,12 @@ func format(err error, lvl int) string {
}
}

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

if e.Cause != nil {
Expand Down

0 comments on commit d2028f1

Please sign in to comment.