Skip to content
This repository was archived by the owner on Nov 21, 2023. It is now read-only.

Commit e875f28

Browse files
Merge pull request #1 from goinsane/develop
v0.1.0
2 parents 186b403 + ae80c32 commit e875f28

File tree

6 files changed

+496
-0
lines changed

6 files changed

+496
-0
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# erf
2+
3+
[![Go Reference](https://pkg.go.dev/badge/github.com/goinsane/erf.svg)](https://pkg.go.dev/github.com/goinsane/erf)
4+
5+
Package erf provides error management with stack trace.
6+
Erf is an error type that wraps the underlying error that stores and formats the stack trace.
7+
Please see [godoc](https://pkg.go.dev/github.com/goinsane/erf) and [example](https://github.com/goinsane/erf/tree/master/example).

erf.go

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// Package erf provides error management with stack trace.
2+
package erf
3+
4+
import (
5+
"bytes"
6+
"errors"
7+
"fmt"
8+
"unsafe"
9+
)
10+
11+
// Erf is an error type that wraps the underlying error that stores and formats the stack trace.
12+
type Erf struct {
13+
err error
14+
format string
15+
args []interface{}
16+
tagIndexes map[string]int
17+
pc []uintptr
18+
}
19+
20+
// Error is implementation of error.
21+
func (e *Erf) Error() string {
22+
return e.err.Error()
23+
}
24+
25+
// Unwrap returns the underlying error.
26+
func (e *Erf) Unwrap() error {
27+
if err, ok := e.err.(WrappedError); ok {
28+
return err.Unwrap()
29+
}
30+
return nil
31+
}
32+
33+
// Format is implementation of fmt.Formatter.
34+
func (e *Erf) Format(f fmt.State, verb rune) {
35+
buf := bytes.NewBuffer(nil)
36+
switch verb {
37+
case 's', 'v':
38+
buf.WriteString(e.err.Error())
39+
if f.Flag('+') {
40+
format := "%+"
41+
for _, r := range []rune{'-', '#'} {
42+
if f.Flag(int(r)) {
43+
format += string(r)
44+
}
45+
}
46+
wid, prec := 1, 1
47+
if f.Flag('-') {
48+
wid, prec = 2, 2
49+
}
50+
if w, ok := f.Width(); ok {
51+
wid = w
52+
}
53+
if p, ok := f.Precision(); ok {
54+
prec = p
55+
}
56+
format += fmt.Sprintf("%d.%d", wid, prec)
57+
format += "s"
58+
buf.WriteRune('\n')
59+
buf.WriteString(fmt.Sprintf(format, e.StackTrace()))
60+
buf.WriteRune('\n')
61+
if !f.Flag('0') {
62+
for err := e.Unwrap(); err != nil; {
63+
if e2, ok := err.(*Erf); ok {
64+
buf.WriteRune('\n')
65+
buf.WriteString(e2.Error())
66+
buf.WriteRune('\n')
67+
buf.WriteString(fmt.Sprintf(format, e2.StackTrace()))
68+
buf.WriteRune('\n')
69+
}
70+
if wErr, ok := err.(WrappedError); ok {
71+
err = wErr.Unwrap()
72+
} else {
73+
err = nil
74+
}
75+
}
76+
}
77+
}
78+
}
79+
if buf.Len() > 0 {
80+
_, _ = f.Write(buf.Bytes())
81+
}
82+
}
83+
84+
// Fmt returns the format argument of the formatting functions (Newf, Errorf or Wrap) that created Erf.
85+
func (e *Erf) Fmt() string {
86+
return e.format
87+
}
88+
89+
// Len returns the length of the arguments slice.
90+
func (e *Erf) Len() int {
91+
return len(e.args)
92+
}
93+
94+
// Arg returns an argument value on the given index. It panics if index is out of range.
95+
func (e *Erf) Arg(index int) interface{} {
96+
if index < 0 || index >= e.Len() {
97+
panic("index is out of range")
98+
}
99+
return e.args[index]
100+
}
101+
102+
// Args returns all argument values. It returns nil if Erf didn't create with formatting functions.
103+
func (e *Erf) Args() []interface{} {
104+
if e.args == nil {
105+
return nil
106+
}
107+
result := make([]interface{}, len(e.args))
108+
copy(result, e.args)
109+
return result
110+
}
111+
112+
// Attach attaches tags to arguments, if arguments are given. It panics if an error occurs.
113+
func (e *Erf) Attach(tags ...string) *Erf {
114+
if e.args == nil {
115+
panic("args are not using")
116+
}
117+
if e.tagIndexes != nil {
118+
panic("tags are already attached")
119+
}
120+
if len(tags) > len(e.args) {
121+
panic("tags are more than args")
122+
}
123+
tagIndexes := make(map[string]int, len(tags))
124+
for index, tag := range tags {
125+
if _, ok := tagIndexes[tag]; ok {
126+
panic("tag is already defined")
127+
}
128+
tagIndexes[tag] = index
129+
}
130+
e.tagIndexes = tagIndexes
131+
return e
132+
}
133+
134+
// Tag returns an argument value on the given tag. It panics if tag is not found.
135+
func (e *Erf) Tag(tag string) interface{} {
136+
index := -1
137+
if idx, ok := e.tagIndexes[tag]; ok {
138+
index = idx
139+
}
140+
if index < 0 || index >= e.Len() {
141+
panic("tag is not found")
142+
}
143+
return e.args[index]
144+
}
145+
146+
// PC returns program counters.
147+
func (e *Erf) PC() []uintptr {
148+
result := make([]uintptr, len(e.pc))
149+
copy(result, e.pc)
150+
return result
151+
}
152+
153+
// StackTrace returns a StackTrace of Erf.
154+
func (e *Erf) StackTrace() *StackTrace {
155+
return NewStackTrace(e.pc...)
156+
}
157+
158+
func (e *Erf) initialize(skip int) {
159+
e.pc = getPC(int(4096/unsafe.Sizeof(uintptr(0))), skip)
160+
}
161+
162+
// New creates a new Erf object with the given text.
163+
func New(text string) *Erf {
164+
e := &Erf{
165+
err: errors.New(text),
166+
}
167+
e.initialize(4)
168+
return e
169+
}
170+
171+
func newf(format string, args ...interface{}) *Erf {
172+
e := &Erf{
173+
err: fmt.Errorf(format, args...),
174+
format: format,
175+
args: make([]interface{}, len(args)),
176+
}
177+
copy(e.args, args)
178+
return e
179+
}
180+
181+
// Newf creates a new Erf object with the given format and args.
182+
func Newf(format string, args ...interface{}) *Erf {
183+
e := newf(format, args...)
184+
e.initialize(4)
185+
return e
186+
}
187+
188+
// Errorf is similar with Newf except that it returns the error interface instead of the Erf pointer.
189+
func Errorf(format string, a ...interface{}) error {
190+
e := newf(format, a...)
191+
e.initialize(4)
192+
return e
193+
}
194+
195+
// Wrap wraps the given error as the underlying error and returns a new Erf object as the error interface.
196+
func Wrap(err error) error {
197+
e := newf("%w", err)
198+
e.initialize(4)
199+
return e
200+
}

example/main.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// +build ignore
2+
3+
package main
4+
5+
import (
6+
"fmt"
7+
8+
"github.com/goinsane/erf"
9+
)
10+
11+
var (
12+
ErrValueBelowZero = erf.New("value below zero")
13+
)
14+
15+
type InvalidArgumentError struct{ *erf.Erf }
16+
17+
func NewInvalidArgumentError(name string, err error) error {
18+
return (&InvalidArgumentError{erf.Newf("invalid argument %q: %w", name, err)}).Attach("name")
19+
}
20+
21+
func Foo(x int) error {
22+
if x < 0 {
23+
return NewInvalidArgumentError("x", ErrValueBelowZero)
24+
}
25+
return nil
26+
}
27+
28+
func Bar(y int) error {
29+
if y < 0 {
30+
return erf.Errorf("%w: %d", ErrValueBelowZero, y)
31+
}
32+
return nil
33+
}
34+
35+
func Baz(z int) error {
36+
if z < 0 {
37+
return erf.Wrap(ErrValueBelowZero)
38+
}
39+
return nil
40+
}
41+
42+
func main() {
43+
fmt.Println("### Foo: just show error text")
44+
if err := Foo(-1); err != nil {
45+
fmt.Printf("%v\n", err)
46+
}
47+
fmt.Println("### \n")
48+
49+
fmt.Println("### Foo: show with stack trace")
50+
if err := Foo(-2); err != nil {
51+
fmt.Printf("%+v\n", err)
52+
}
53+
fmt.Println("### \n")
54+
55+
fmt.Println("### Foo: show with stack trace, and use space as whitespace instead of tab")
56+
if err := Foo(-3); err != nil {
57+
fmt.Printf("%+-v\n", err)
58+
}
59+
fmt.Println("### \n")
60+
61+
fmt.Println("### Foo: show with last stack trace")
62+
if err := Foo(-4); err != nil {
63+
fmt.Printf("%+0v\n", err)
64+
}
65+
fmt.Println("### \n")
66+
67+
fmt.Println("### Foo: show with stack trace with only file names except full path")
68+
if err := Foo(-5); err != nil {
69+
fmt.Printf("%+#v\n", err)
70+
}
71+
fmt.Println("### \n")
72+
73+
fmt.Println("### Foo: show with stack trace with 2 whitespace chars of padding and 1 whitespace char of indent")
74+
if err := Foo(-6); err != nil {
75+
fmt.Printf("%+2.1v\n", err)
76+
}
77+
fmt.Println("### \n")
78+
79+
fmt.Println("### Bar: show with stack trace")
80+
if err := Bar(-7); err != nil {
81+
fmt.Printf("%+v\n", err)
82+
}
83+
fmt.Println("### \n")
84+
85+
fmt.Println("### Baz: show with stack trace")
86+
if err := Baz(-8); err != nil {
87+
fmt.Printf("%+v\n", err)
88+
}
89+
fmt.Println("### \n")
90+
}

interfaces.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package erf
2+
3+
type Wrapped interface {
4+
Unwrap() error
5+
}
6+
7+
type WrappedError interface {
8+
Wrapped
9+
error
10+
}

0 commit comments

Comments
 (0)