Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Unstack method #10

Merged
merged 1 commit into from
Aug 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading