Skip to content

Commit 453654e

Browse files
author
Dean Karn
committed
Initial Commit
0 parents  commit 453654e

File tree

9 files changed

+654
-0
lines changed

9 files changed

+654
-0
lines changed

.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Created by .ignore support plugin (hsz.mobi)
2+
### Go template
3+
# Binaries for programs and plugins
4+
*.exe
5+
*.exe~
6+
*.dll
7+
*.so
8+
*.dylib
9+
10+
# Test binary, build with `go test -c`
11+
*.test
12+
13+
# Output of the go coverage tool, specifically when used with LiteIDE
14+
*.out
15+

README.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
Package errors
2+
============
3+
![Project status](https://img.shields.io/badge/version-1.0.0-green.svg)
4+
[![GoDoc](https://godoc.org/github.com/go-playground/errors?status.svg)](https://godoc.org/github.com/go-playground/errors)
5+
![License](https://img.shields.io/dub/l/vibe-d.svg)
6+
7+
Package errors is an errors wrapping package to help propogate not only produce a chain of errors, but also
8+
stack trace, any tags(additional information) and even a Type classification system to categorize errors into types eg. Permanent vs Transient.
9+
10+
11+
Common Questions
12+
13+
Why another package?
14+
Because most of the existing packages either don't take the error handling far enough, too far or down right unfriendly to use/consume.
15+
16+
Features
17+
--------
18+
- [x] works with go-playground/log, the Tags will be added as Field Key Values and Types will be concatenated as well when using `WithError`
19+
20+
Installation
21+
------------
22+
23+
Use go get.
24+
25+
go get -u github.com/go-playground/errors
26+
27+
Usage
28+
-----
29+
```go
30+
package main
31+
32+
import (
33+
"fmt"
34+
"io"
35+
36+
"github.com/go-playground/errors"
37+
)
38+
39+
func main() {
40+
err := level1("testing error")
41+
fmt.Println(err)
42+
if errors.HasType(err, "Permanent") {
43+
// os.Exit(1)
44+
}
45+
46+
// root error
47+
cause := errors.Cause(err)
48+
fmt.Println(cause)
49+
50+
// can even still inspect the internal error
51+
fmt.Println(errors.IsErr(err, io.EOF)) // will extract the cause for you
52+
fmt.Println(errors.IsErr(cause, io.EOF))
53+
54+
// or manually with access to base fields
55+
wrapped := cause.(*errors.Wrapped)
56+
fmt.Println(wrapped.Err == io.EOF)
57+
}
58+
59+
func level1(value string) error {
60+
if err := level2(value); err != nil {
61+
return errors.Wrap(err, "level2 call failed")
62+
}
63+
return nil
64+
}
65+
66+
func level2(value string) error {
67+
err := fmt.Errorf("this is an %s", "error")
68+
return errors.Wrap(err, "failed to do something").WithTypes("Permanent").WithTags(errors.T("value", value))
69+
}
70+
```
71+
72+
or using stack only
73+
74+
```go
75+
package main
76+
77+
import (
78+
"fmt"
79+
80+
"strings"
81+
82+
"github.com/go-playground/errors"
83+
)
84+
85+
func main() {
86+
// maybe you just want to grab a stack trace and process on your own like go-playground/log
87+
// uses it to produce a stack trace log message
88+
frame := errors.Stack()
89+
name := fmt.Sprintf("%n", frame)
90+
file := fmt.Sprintf("%+s", frame)
91+
line := fmt.Sprintf("%d", frame)
92+
parts := strings.Split(file, "\n\t")
93+
if len(parts) > 1 {
94+
file = parts[1]
95+
}
96+
97+
fmt.Printf("Name: %s File: %s Line: %s\n", name, file, line)
98+
}
99+
```
100+
101+
Package Versioning
102+
----------
103+
I'm jumping on the vendoring bandwagon, you should vendor this package as I will not
104+
be creating different version with gopkg.in like allot of my other libraries.
105+
106+
Why? because my time is spread pretty thin maintaining all of the libraries I have + LIFE,
107+
it is so freeing not to worry about it and will help me keep pouring out bigger and better
108+
things for you the community.
109+
110+
License
111+
------
112+
Distributed under MIT License, please see license file in code for more details.

_examples/basic/main.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io"
6+
7+
"github.com/go-playground/errors"
8+
)
9+
10+
func main() {
11+
err := level1("testing error")
12+
fmt.Println(err)
13+
if errors.HasType(err, "Permanent") {
14+
// os.Exit(1)
15+
}
16+
17+
// root error
18+
cause := errors.Cause(err)
19+
fmt.Println(cause)
20+
21+
// can even still inspect the internal error
22+
fmt.Println(errors.IsErr(err, io.EOF)) // will extract the cause for you
23+
fmt.Println(errors.IsErr(cause, io.EOF))
24+
25+
// or manually with access to base fields
26+
wrapped := cause.(*errors.Wrapped)
27+
fmt.Println(wrapped.Err == io.EOF)
28+
}
29+
30+
func level1(value string) error {
31+
if err := level2(value); err != nil {
32+
return errors.Wrap(err, "level2 call failed")
33+
}
34+
return nil
35+
}
36+
37+
func level2(value string) error {
38+
err := fmt.Errorf("this is an %s", "error")
39+
return errors.Wrap(err, "failed to do something").WithTypes("Permanent").WithTags(errors.T("value", value))
40+
}

_examples/stack/main.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
6+
"strings"
7+
8+
"github.com/go-playground/errors"
9+
)
10+
11+
func main() {
12+
// maybe you just want to grab a stack trace and process on your own like go-playground/log
13+
// uses it to produce a stack trace log message
14+
frame := errors.Stack()
15+
name := fmt.Sprintf("%n", frame)
16+
file := fmt.Sprintf("%+s", frame)
17+
line := fmt.Sprintf("%d", frame)
18+
parts := strings.Split(file, "\n\t")
19+
if len(parts) > 1 {
20+
file = parts[1]
21+
}
22+
23+
fmt.Printf("Name: %s File: %s Line: %s\n", name, file, line)
24+
}

errors.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package errors
2+
3+
// Wrap encapsulates the error, stores a contextual prefix and automatically obtains
4+
// a stack trace.
5+
func Wrap(err error, prefix string) *Wrapped {
6+
if w, ok := err.(*Wrapped); ok {
7+
w.errors = append(w.errors, newWrapped(err, prefix))
8+
return w
9+
}
10+
return &Wrapped{
11+
errors: []*Wrapped{newWrapped(err, prefix)},
12+
}
13+
}
14+
15+
// HasType is a helper function that will recurse up from the root error and check that the provided type
16+
// is present using an equality check
17+
func HasType(err error, typ string) bool {
18+
w, ok := err.(*Wrapped)
19+
if !ok {
20+
return false
21+
}
22+
for i := len(w.errors) - 1; i >= 0; i-- {
23+
for j := 0; j < len(w.errors[i].Types); j++ {
24+
if w.errors[i].Types[j] == typ {
25+
return true
26+
}
27+
}
28+
}
29+
return false
30+
}
31+
32+
// Cause extracts and returns the root error
33+
func Cause(err error) error {
34+
if w, ok := err.(*Wrapped); ok {
35+
return w.errors[0]
36+
}
37+
return err
38+
}
39+
40+
// IsErr will fetch the root error, and check the original error against the provided type
41+
// eg. errors.IsErr(io.EOF)
42+
func IsErr(err, errType error) bool {
43+
if w, ok := err.(*Wrapped); ok {
44+
// if root level error
45+
if len(w.errors) > 0 {
46+
return w.errors[0].Err == errType
47+
}
48+
// already extracted error
49+
return w.Err == errType
50+
}
51+
return err == errType
52+
}

errors_test.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package errors
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"strings"
7+
"testing"
8+
)
9+
10+
func TestWrap(t *testing.T) {
11+
tests := []struct {
12+
pre string
13+
suf string
14+
}{
15+
{
16+
pre: "source=TestWrap: ",
17+
suf: "errors_test.go:24 prefix: this is an error",
18+
},
19+
}
20+
21+
defaultErr := fmt.Errorf("this is an %s", "error")
22+
23+
for i, tt := range tests {
24+
err := Wrap(defaultErr, "prefix")
25+
if !strings.HasSuffix(err.Error(), tt.suf) || !strings.HasPrefix(err.Error(), tt.pre) {
26+
t.Fatalf("IDX: %d want %s<path>%s got %s", i, tt.pre, tt.suf, err.Error())
27+
}
28+
}
29+
}
30+
31+
func TestTags(t *testing.T) {
32+
tests := []struct {
33+
err string
34+
tags []Tag
35+
}{
36+
{
37+
err: "key=value key2=value2",
38+
tags: []Tag{T("key", "value"), T("key2", "value2")},
39+
},
40+
}
41+
42+
defaultErr := fmt.Errorf("this is an %s", "error")
43+
44+
for i, tt := range tests {
45+
err := Wrap(defaultErr, "prefix").WithTags(tt.tags...)
46+
if !strings.HasSuffix(err.Error(), tt.err) {
47+
t.Fatalf("IDX: %d want %s got %s", i, tt.err, err.Error())
48+
}
49+
}
50+
}
51+
52+
func TestTypes(t *testing.T) {
53+
tests := []struct {
54+
err string
55+
tags []Tag
56+
types []string
57+
}{
58+
{
59+
err: "types=Permanent,InternalError",
60+
tags: []Tag{T("key", "value"), T("key2", "value2")},
61+
types: []string{"Permanent", "InternalError"},
62+
},
63+
}
64+
65+
defaultErr := fmt.Errorf("this is an %s", "error")
66+
67+
for i, tt := range tests {
68+
err := Wrap(defaultErr, "prefix").WithTags(tt.tags...).WithTypes(tt.types...)
69+
if !strings.HasSuffix(err.Error(), tt.err) {
70+
t.Fatalf("IDX: %d want %s got %s", i, tt.err, err.Error())
71+
}
72+
}
73+
}
74+
75+
func TestHasType(t *testing.T) {
76+
tests := []struct {
77+
types []string
78+
typ string
79+
}{
80+
{
81+
types: []string{"Permanent", "internalError"},
82+
typ: "Permanent",
83+
},
84+
}
85+
86+
defaultErr := fmt.Errorf("this is an %s", "error")
87+
88+
for i, tt := range tests {
89+
err := Wrap(defaultErr, "prefix").WithTypes(tt.types...)
90+
if !HasType(err, tt.typ) {
91+
t.Fatalf("IDX: %d want %t got %t", i, true, false)
92+
}
93+
}
94+
}
95+
96+
func TestCause(t *testing.T) {
97+
defaultErr := fmt.Errorf("this is an %s", "error")
98+
err := Wrap(defaultErr, "prefix")
99+
err = Wrap(err, "prefix2")
100+
cause := Cause(err)
101+
expect := "prefix: this is an error"
102+
if !strings.HasSuffix(cause.Error(), expect) {
103+
t.Fatalf("want %s got %s", expect, err.Error())
104+
}
105+
}
106+
107+
func TestIsErr(t *testing.T) {
108+
err := Wrap(io.EOF, "prefix")
109+
err = Wrap(err, "prefix2")
110+
111+
if !IsErr(err, io.EOF) {
112+
t.Fatalf("want %t got %t", true, false)
113+
}
114+
cause := Cause(err)
115+
if !IsErr(cause, io.EOF) {
116+
t.Fatalf("want %t got %t", true, false)
117+
}
118+
if !IsErr(io.EOF, io.EOF) {
119+
t.Fatalf("want %t got %t", true, false)
120+
}
121+
}

0 commit comments

Comments
 (0)