Skip to content

Commit ca50fdd

Browse files
committed
adds stacktrace behavior identical to the one provided by pkg/errors
1 parent 1cf4e56 commit ca50fdd

File tree

8 files changed

+145
-1
lines changed

8 files changed

+145
-1
lines changed

builder_gen_opts.go

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

errors.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
package errutil
22

3+
import (
4+
"fmt"
5+
"strings"
6+
)
7+
38
// New creates a new error with the ability to add multiple behavior to that error
49
// e.g.
510
//
@@ -13,6 +18,9 @@ func New(err error, opts ...OptsFunc) error {
1318
if len(e.opts.Tags) > 0 {
1419
e.error = NewTagged(err, e.opts.Tags...)
1520
}
21+
if e.opts.StackTrace != nil {
22+
e.error = NewStacked(err, *e.opts.StackTrace+1)
23+
}
1624

1725
return e
1826
}
@@ -36,6 +44,7 @@ type Opts struct {
3644
TooLarge bool
3745
RateLimit bool
3846
Tags []Tag
47+
StackTrace *int
3948
}
4049

4150
type multiKindErr struct {
@@ -56,6 +65,21 @@ func (m multiKindErr) Exists() bool {
5665
return m.opts.Exists
5766
}
5867

68+
func (m multiKindErr) Format(s fmt.State, verb rune) {
69+
if formatter, ok := m.error.(fmt.Formatter); ok {
70+
formatter.Format(s, verb)
71+
return
72+
} else {
73+
var builder strings.Builder
74+
builder.WriteString("%")
75+
if s.Flag('+') {
76+
builder.WriteRune('+')
77+
}
78+
builder.WriteRune(verb)
79+
fmt.Fprintf(s, builder.String(), m.error)
80+
}
81+
}
82+
5983
func (m multiKindErr) NotFound() bool {
6084
return m.opts.NotFound
6185
}

errors_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@ func TestNewError(t *testing.T) {
107107
})
108108

109109
})
110+
111+
t.Run("should generate stack trace when option provided", func(t *testing.T) {
112+
err := New(errors.New("hey there"), WithStackTrace(0))
113+
fmt.Printf("%+v\n", err)
114+
})
110115
}
111116

112117
var allFuncs = []CheckerFn{

functions.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,20 @@ func IsTaggable(err error) bool {
104104
return IsTaggable(errors.Unwrap(err))
105105
}
106106

107+
// IsStackTraceable checks if an error exhibits taggable behavior
108+
func IsStackTraceable(err error) bool {
109+
if err == nil {
110+
return false
111+
}
112+
113+
var e StackTraceable
114+
if errors.As(err, &e) {
115+
return true
116+
}
117+
118+
return IsStackTraceable(errors.Unwrap(err))
119+
}
120+
107121
// GetTags returns all the tags for a given error
108122
func GetTags(err error) []Tag {
109123
var tags []Tag

go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ module github.com/darwayne/errutil
22

33
go 1.17
44

5-
require github.com/darwayne/builder-gen v1.6.0
5+
require (
6+
github.com/darwayne/builder-gen v1.6.0
7+
github.com/pkg/errors v0.9.1
8+
)
69

710
require (
811
golang.org/x/mod v0.3.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
github.com/darwayne/builder-gen v1.6.0 h1:eGEeNIcQiu7zKMklOe5cl0TuRmC9uq18FelMeOkSvZ8=
22
github.com/darwayne/builder-gen v1.6.0/go.mod h1:mGpbS+5yboRzmqxmN8XpkqeptLqxHB57YUaJdG5DgEU=
3+
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
4+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
35
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
46
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
57
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=

interfaces.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package errutil
22

3+
import "github.com/pkg/errors"
4+
35
// AccessDenier determines if an error exhibits access denied behavior
46
type AccessDenier interface {
57
AccessDenied() bool
@@ -34,3 +36,8 @@ type TooLarge interface {
3436
type Taggable interface {
3537
Tags() []Tag
3638
}
39+
40+
// StackTraceable determines if an error exhibits stacktrace behavior from the pkg/errors package
41+
type StackTraceable interface {
42+
StackTrace() errors.StackTrace
43+
}

stacktrace.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package errutil
2+
3+
import (
4+
"fmt"
5+
"github.com/pkg/errors"
6+
"io"
7+
"runtime"
8+
)
9+
10+
func NewStacked(err error, skip ...int) error {
11+
if err == nil {
12+
return nil
13+
}
14+
15+
return &Stacked{
16+
error: err,
17+
stack: Callers(skip...),
18+
}
19+
}
20+
21+
type Stacked struct {
22+
error
23+
*stack
24+
}
25+
26+
func (w *Stacked) Unwrap() error { return w.error }
27+
28+
func (w *Stacked) Format(s fmt.State, verb rune) {
29+
switch verb {
30+
case 'v':
31+
if s.Flag('+') {
32+
fmt.Fprintf(s, "%+v", w.error)
33+
w.stack.Format(s, verb)
34+
return
35+
}
36+
fallthrough
37+
case 's':
38+
io.WriteString(s, w.Error())
39+
case 'q':
40+
fmt.Fprintf(s, "%q", w.Error())
41+
}
42+
}
43+
44+
// stack represents a stack of program counters.
45+
type stack []uintptr
46+
47+
func (s *stack) Format(st fmt.State, verb rune) {
48+
switch verb {
49+
case 'v':
50+
switch {
51+
case st.Flag('+'):
52+
for _, pc := range *s {
53+
f := errors.Frame(pc)
54+
fmt.Fprintf(st, "\n%+v", f)
55+
}
56+
}
57+
}
58+
}
59+
60+
func (s *stack) StackTrace() errors.StackTrace {
61+
f := make([]errors.Frame, len(*s))
62+
for i := 0; i < len(f); i++ {
63+
f[i] = errors.Frame((*s)[i])
64+
}
65+
return f
66+
}
67+
68+
func Callers(skip ...int) *stack {
69+
const depth = 32
70+
var pcs [depth]uintptr
71+
s := 0
72+
if len(skip) > 0 {
73+
s = skip[0]
74+
}
75+
76+
n := runtime.Callers(s+3, pcs[:])
77+
var st stack = pcs[0:n]
78+
return &st
79+
}

0 commit comments

Comments
 (0)