Skip to content

Commit

Permalink
Merge pull request #10 from m-mizutani/feature/unstack
Browse files Browse the repository at this point in the history
Add Unstack method
  • Loading branch information
m-mizutani authored Aug 3, 2024
2 parents cbba540 + acb2999 commit 2f192e6
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 11 deletions.
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,16 @@ if err := someAction("no_such_file.txt"); err != nil {
exit status 1
```

NOTE: If the error is wrapped by `goerr` multiply, `%+v` will print the stack trace of the deepest error.
**NOTE**: If the error is wrapped by `goerr` multiply, `%+v` will print the stack trace of the deepest error.

**Tips**: If you want not to print the stack trace for current stack frame, you can use `Unstack` method. Also, `UnstackN` method removes the top multiple stack frames.

```go
if err := someAction("no_such_file.txt"); err != nil {
// Unstack() removes the current stack frame from the error message.
return goerr.Wrap(err, "failed to someAction").Unstack()
}
```

### Add/Extract contextual variables

Expand Down
12 changes: 12 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,18 @@ func (x *Error) With(key string, value any) *Error {
return x
}

// Unstack trims stack trace by 1. It can be used for internal helper or utility functions.
func (x *Error) Unstack() *Error {
x.st = unstack(x.st, 1)
return x
}

// UnstackN trims stack trace by n. It can be used for internal helper or utility functions.
func (x *Error) UnstackN(n int) *Error {
x.st = unstack(x.st, n)
return x
}

// Is returns true if target is goerr.Error and Error.id of two errors are matched. It's for errors.Is. If Error.id is empty, it always returns false.
func (x *Error) Is(target error) bool {
var err *Error
Expand Down
44 changes: 44 additions & 0 deletions errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,47 @@ func TestLoggerWithNil(t *testing.T) {
t.Errorf("Expected log output to contain '\"error\":null', got '%s'", out.String())
}
}

func TestUnstack(t *testing.T) {
t.Run("original stack", func(t *testing.T) {
err := oops()
st := err.Stacks()
if st == nil {
t.Error("Expected stack trace to be nil")
}
if len(st) == 0 {
t.Error("Expected stack trace length to be 0")
}
if st[0].Func != "github.com/m-mizutani/goerr_test.oops" {
t.Errorf("Not expected stack trace func name (github.com/m-mizutani/goerr_test.oops): %s", st[0].Func)
}
})

t.Run("unstacked", func(t *testing.T) {
err := oops().Unstack()
st1 := err.Stacks()
if st1 == nil {
t.Error("Expected stack trace to be non-nil")
}
if len(st1) == 0 {
t.Error("Expected stack trace length to be non-zero")
}
if st1[0].Func != "github.com/m-mizutani/goerr_test.TestUnstack.func2" {
t.Errorf("Not expected stack trace func name (github.com/m-mizutani/goerr_test.TestUnstack.func2): %s", st1[0].Func)
}
})

t.Run("unstackN with 2", func(t *testing.T) {
err := oops().UnstackN(2)
st2 := err.Stacks()
if st2 == nil {
t.Error("Expected stack trace to be non-nil")
}
if len(st2) == 0 {
t.Error("Expected stack trace length to be non-zero")
}
if st2[0].Func != "testing.tRunner" {
t.Errorf("Not expected stack trace func name (testing.tRunner): %s", st2[0].Func)
}
})
}
34 changes: 24 additions & 10 deletions stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,16 +105,16 @@ func (f frame) name() string {

// Format of frame formats the frame according to the fmt.Formatter interface.
//
// %s source file
// %d source line
// %n function name
// %v equivalent to %s:%d
// %s source file
// %d source line
// %n function name
// %v equivalent to %s:%d
//
// Format accepts flags that alter the printing of some verbs, as follows:
//
// %+s function name and path of source file relative to the compile time
// GOPATH separated by \n\t (<funcname>\n\t<path>)
// %+v equivalent to %+s:%d
// %+s function name and path of source file relative to the compile time
// GOPATH separated by \n\t (<funcname>\n\t<path>)
// %+v equivalent to %+s:%d
func (f frame) Format(s fmt.State, verb rune) {
switch verb {
case 's':
Expand Down Expand Up @@ -149,12 +149,12 @@ func (f frame) MarshalText() ([]byte, error) {

// Format formats the stack of Frames according to the fmt.Formatter interface.
//
// %s lists source files for each Frame in the stack
// %v lists the source file and line number for each Frame in the stack
// %s lists source files for each Frame in the stack
// %v lists the source file and line number for each Frame in the stack
//
// Format accepts flags that alter the printing of some verbs, as follows:
//
// %+v Prints filename, function, and line number for each Frame in the stack.
// %+v Prints filename, function, and line number for each Frame in the stack.
func (st StackTrace) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
Expand Down Expand Up @@ -208,6 +208,20 @@ func (s *stack) StackTrace() StackTrace {
return frames
}

func unstack(st *stack, n int) *stack {
switch {
case n <= 0:
var ret stack
return &ret
case n >= len(*st):
return st

default:
ret := (*st)[n:]
return &ret
}
}

func callers() *stack {
const depth = 32
var pcs [depth]uintptr
Expand Down

0 comments on commit 2f192e6

Please sign in to comment.