Skip to content

Commit

Permalink
refactor: insert frames during error wrapping instead of unpacking (#70)
Browse files Browse the repository at this point in the history
  • Loading branch information
morningvera authored Feb 6, 2020
1 parent 8e63388 commit fe18b71
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 174 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ release-tag: build fmt lint test
@echo Generating changelog
@git-chglog -o CHANGELOG.md
@git add CHANGELOG.md
@git commit -m "chore: update changelog for version '${VERSION}'"
@git commit -m "chore: update changelog for version '${VERSION}'" -S

## Push a release (warning: make sure the release was staged properly before doing this)
release-push:
Expand Down
50 changes: 15 additions & 35 deletions eris.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func wrap(err error, msg string) error {

// callers(4) skips this method, Wrap(f), stack.callers, and runtime.Callers
stack := callers(4)
frame := caller(4)
switch e := err.(type) {
case *rootError:
if e.global {
Expand All @@ -61,6 +62,10 @@ func wrap(err error, msg string) error {
}
}
case *wrapError:
// insert the frame into the stack
if root, ok := Cause(err).(*rootError); ok {
root.stack.insertPC(*stack)
}
default:
err = &rootError{
msg: e.Error(),
Expand All @@ -71,7 +76,7 @@ func wrap(err error, msg string) error {
return &wrapError{
msg: msg,
err: err,
stack: stack,
frame: frame,
}
}

Expand Down Expand Up @@ -126,18 +131,15 @@ func Cause(err error) error {

// StackFrames returns the trace of an error in the form of a program counter slice.
// Use this method if you want to pass the eris stack trace to some other error tracing library.
func StackFrames(e error) []uintptr {
if e == nil {
return []uintptr{}
}
switch err := e.(type) {
case *wrapError:
return err.StackFrames()
case *rootError:
return err.StackFrames()
default:
return []uintptr{}
func StackFrames(err error) []uintptr {
for err != nil {
switch err := err.(type) {
case *rootError:
return *err.stack
}
err = Unwrap(err)
}
return []uintptr{}
}

type rootError struct {
Expand All @@ -154,10 +156,6 @@ func (e *rootError) Format(s fmt.State, verb rune) {
printError(e, s, verb)
}

func (e *rootError) StackFrames() []uintptr {
return *e.stack
}

func (e *rootError) Is(target error) bool {
if err, ok := target.(*rootError); ok {
return e.msg == err.msg
Expand All @@ -168,7 +166,7 @@ func (e *rootError) Is(target error) bool {
type wrapError struct {
msg string
err error
stack *stack
frame *frame
}

func (e *wrapError) Error() string {
Expand All @@ -179,24 +177,6 @@ func (e *wrapError) Format(s fmt.State, verb rune) {
printError(e, s, verb)
}

func (e *wrapError) StackFrames() []uintptr {
var frames *stack
stackArr := [][]uintptr{*e.stack}
for {
nextErr := Unwrap(e)
switch nextErr := nextErr.(type) {
case *wrapError:
stackArr = append(stackArr, *nextErr.stack)
case *rootError:
rFrames := (stack)(nextErr.StackFrames())
frames = &rFrames
frames.insertPCs(stackArr)
return *frames
}
e = nextErr.(*wrapError)
}
}

func (e *wrapError) Is(target error) bool {
if err, ok := target.(*wrapError); ok {
return e.msg == err.msg
Expand Down
85 changes: 32 additions & 53 deletions format.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,38 @@ import (
"fmt"
)

// UnpackedError represents complete information about an error.
//
// This type can be used for custom error logging and parsing. Use `eris.Unpack` to build an UnpackedError
// from any error type. The ErrChain and ErrRoot fields correspond to `wrapError` and `rootError` types,
// respectively. If any other error type is unpacked, it will appear in the ExternalErr field.
type UnpackedError struct {
ErrRoot ErrRoot
ErrChain []ErrLink
ExternalErr string
}

// Unpack returns UnpackedError type for a given golang error type.
func Unpack(err error) UnpackedError {
var upErr UnpackedError
for err != nil {
switch err := err.(type) {
case *rootError:
upErr.ErrRoot.Msg = err.msg
upErr.ErrRoot.Stack = err.stack.get()
case *wrapError:
// prepend links in stack trace order
link := ErrLink{Msg: err.msg}
link.Frame = err.frame.get()
upErr.ErrChain = append([]ErrLink{link}, upErr.ErrChain...)
default:
upErr.ExternalErr = err.Error()
}
err = Unwrap(err)
}
return upErr
}

// Format defines an error output format to be used with the default formatter.
type Format struct {
WithTrace bool // Flag that enables stack trace output.
Expand All @@ -28,33 +60,6 @@ func NewDefaultFormat(withTrace bool) Format {
return stringFmt
}

// UnpackedError represents complete information about an error.
//
// This type can be used for custom error logging and parsing. Use `eris.Unpack` to build an UnpackedError
// from any error type. The ErrChain and ErrRoot fields correspond to `wrapError` and `rootError` types,
// respectively. If any other error type is unpacked, it will appear in the ExternalErr field.
type UnpackedError struct {
ErrRoot ErrRoot
ErrChain []ErrLink
ExternalErr string
}

// Unpack returns UnpackedError type for a given golang error type.
func Unpack(err error) UnpackedError {
upErr := UnpackedError{}
switch err := err.(type) {
case nil:
return upErr
case *rootError:
upErr.unpackRootErr(err)
case *wrapError:
upErr.unpackWrapErr(err)
default:
upErr.ExternalErr = err.Error()
}
return upErr
}

// ToCustomString returns a custom formatted string for a given eris error.
//
// To declare custom format, the Format object has to be passed as an argument.
Expand Down Expand Up @@ -209,32 +214,6 @@ func ToJSON(err error, withTrace bool) map[string]interface{} {
return ToCustomJSON(err, NewDefaultFormat(withTrace))
}

func (upErr *UnpackedError) unpackRootErr(err *rootError) {
upErr.ErrRoot.Msg = err.msg
upErr.ErrRoot.Stack = err.stack.get()
}

func (upErr *UnpackedError) unpackWrapErr(err *wrapError) {
// prepend links in stack trace order
link := ErrLink{Msg: err.msg}
wFrames := err.stack.get()
if len(wFrames) > 0 {
link.Frame = wFrames[0]
}
upErr.ErrChain = append([]ErrLink{link}, upErr.ErrChain...)

nextErr := err.Unwrap()
switch nextErr := nextErr.(type) {
case *rootError:
upErr.unpackRootErr(nextErr)
case *wrapError:
upErr.unpackWrapErr(nextErr)
}

// insert the wrap frame into the root stack
upErr.ErrRoot.Stack.insertFrame(wFrames)
}

// ErrRoot represents an error stack and the accompanying message.
type ErrRoot struct {
Msg string
Expand Down
38 changes: 19 additions & 19 deletions format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,6 @@ import (
"github.com/rotisserie/eris"
)

func errChainsEqual(a []eris.ErrLink, b []eris.ErrLink) bool {
// If one is nil, the other must also be nil.
if (a == nil) != (b == nil) {
return false
}

if len(a) != len(b) {
return false
}

for i := range a {
if a[i].Msg != b[i].Msg {
return false
}
}

return true
}

func TestUnpack(t *testing.T) {
tests := map[string]struct {
cause error
Expand Down Expand Up @@ -106,6 +87,25 @@ func TestUnpack(t *testing.T) {
}
}

func errChainsEqual(a []eris.ErrLink, b []eris.ErrLink) bool {
// If one is nil, the other must also be nil.
if (a == nil) != (b == nil) {
return false
}

if len(a) != len(b) {
return false
}

for i := range a {
if a[i].Msg != b[i].Msg {
return false
}
}

return true
}

func TestFormatStr(t *testing.T) {
tests := map[string]struct {
input error
Expand Down
Loading

0 comments on commit fe18b71

Please sign in to comment.