Skip to content

Commit 197d6da

Browse files
author
Dean Karn
committed
Added error helps for classification and information
1 parent 2468160 commit 197d6da

File tree

9 files changed

+204
-6
lines changed

9 files changed

+204
-6
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package errors
22
============
3-
![Project status](https://img.shields.io/badge/version-1.0.0-green.svg)
3+
![Project status](https://img.shields.io/badge/version-1.1.0-green.svg)
44
[![Build Status](https://semaphoreci.com/api/v1/joeybloggs/errors/branches/master/badge.svg)](https://semaphoreci.com/joeybloggs/errors)
55
[![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/errors)](https://goreportcard.com/report/github.com/go-playground/errors)
66
[![GoDoc](https://godoc.org/github.com/go-playground/errors?status.svg)](https://godoc.org/github.com/go-playground/errors)
@@ -18,6 +18,7 @@ Because IMO most of the existing packages either don't take the error handling f
1818
Features
1919
--------
2020
- [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`
21+
- [x] helpers to extract and classify error types using `RegisterHelper(...)`, many already existing such as ioerrors, neterrors, awserrors...
2122

2223
Installation
2324
------------

_examples/helpers/main.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"net"
6+
7+
"github.com/go-playground/errors"
8+
"github.com/go-playground/errors/helpers/neterrors"
9+
)
10+
11+
func main() {
12+
errors.RegisterHelper(neterrors.NETErrors)
13+
_, err := net.ResolveIPAddr("tcp", "foo")
14+
if err != nil {
15+
err = errors.Wrap(err, "failed to perform operation")
16+
}
17+
18+
// all that extra context, types and tags captured for free
19+
// there are more helpers and you can even create your own.
20+
fmt.Println(err)
21+
}

errors.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,33 @@
11
package errors
22

3+
var (
4+
helpers []Helper
5+
)
6+
7+
// RegisterHelper adds a new helper function to extract Type and Tag information.
8+
// errors will run all registered helpers until a match is found.
9+
// NOTE helpers are run in the order they are added.
10+
func RegisterHelper(helper Helper) {
11+
helpers = append(helpers, helper)
12+
}
13+
314
// Wrap encapsulates the error, stores a contextual prefix and automatically obtains
415
// a stack trace.
5-
func Wrap(err error, prefix string) *Wrapped {
6-
if w, ok := err.(*Wrapped); ok {
16+
func Wrap(err error, prefix string) (w *Wrapped) {
17+
var ok bool
18+
if w, ok = err.(*Wrapped); ok {
719
w.Errors = append(w.Errors, newWrapped(err, prefix))
8-
return w
20+
} else {
21+
w = &Wrapped{
22+
Errors: []*Wrapped{newWrapped(err, prefix)},
23+
}
924
}
10-
return &Wrapped{
11-
Errors: []*Wrapped{newWrapped(err, prefix)},
25+
for _, h := range helpers {
26+
if !h(w, err) {
27+
break
28+
}
1229
}
30+
return
1331
}
1432

1533
// HasType is a helper function that will recurse up from the root error and check that the provided type

errors_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,16 @@ func TestIsErr(t *testing.T) {
119119
t.Fatalf("want %t got %t", true, false)
120120
}
121121
}
122+
123+
func TestHelpers(t *testing.T) {
124+
fn := func(w *Wrapped, err error) (cont bool) {
125+
w.WithTypes("Test").WithTags(T("test", "tag")).WithTag("foo", "bar")
126+
return false
127+
}
128+
RegisterHelper(fn)
129+
130+
err := Wrap(io.EOF, "prefix")
131+
if !HasType(err, "Test") {
132+
t.Errorf("Expected to have type 'Test'")
133+
}
134+
}

helpers.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package errors
2+
3+
// Helper is a function which will automatically extract Type and Tag information based on the supplied err and
4+
// add it to the supplied *Wrapped error; this can be used independently or by registering using errors.REgisterHelper(...),
5+
// which will run the registered helper every time errors.Wrap(...) is called.
6+
type Helper func(*Wrapped, error) bool

helpers/awserrors/awserrors.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package awserrors
2+
3+
import (
4+
"github.com/aws/aws-sdk-go/aws/awserr"
5+
"github.com/go-playground/errors"
6+
)
7+
8+
// AWSErrors helps classify io related errors
9+
func AWSErrors(w *errors.Wrapped, err error) (cont bool) {
10+
switch e := err.(type) {
11+
case awserr.BatchError:
12+
w.WithTypes("Transient", "Batch").WithTags(errors.T("aws_error_code", e.Code()))
13+
return
14+
15+
case awserr.BatchedErrors:
16+
w.WithTypes("Transient", "Batch")
17+
return
18+
19+
case awserr.RequestFailure:
20+
w.WithTypes("Transient", "Request").WithTags(
21+
errors.T("status_code", e.StatusCode()),
22+
errors.T("request_id", e.RequestID()),
23+
)
24+
return
25+
26+
case awserr.Error:
27+
w.WithTypes("General", "Error").WithTags(errors.T("aws_error_code", e.Code()))
28+
return
29+
}
30+
return true
31+
}

helpers/ioerrors/ioerrors.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package ioerrors
2+
3+
import (
4+
"io"
5+
6+
"github.com/go-playground/errors"
7+
)
8+
9+
// IOErrors helps classify io related errors
10+
func IOErrors(w *errors.Wrapped, err error) (cont bool) {
11+
switch err {
12+
case io.EOF:
13+
w.WithTypes("io")
14+
return
15+
case io.ErrClosedPipe:
16+
w.WithTypes("Permanent", "io")
17+
return
18+
case io.ErrNoProgress:
19+
w.WithTypes("Permanent", "io")
20+
return
21+
case io.ErrShortBuffer:
22+
w.WithTypes("Permanent", "io")
23+
return
24+
case io.ErrShortWrite:
25+
w.WithTypes("Permanent", "io")
26+
return
27+
case io.ErrUnexpectedEOF:
28+
w.WithTypes("Transient", "io")
29+
return
30+
}
31+
return true
32+
}

helpers/neterrors/neterrors.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package neterrors
2+
3+
import (
4+
"net"
5+
6+
"github.com/go-playground/errors"
7+
)
8+
9+
// NETErrors helps classify io related errors
10+
func NETErrors(w *errors.Wrapped, err error) (cont bool) {
11+
switch e := err.(type) {
12+
case *net.AddrError:
13+
tp := "Permanent"
14+
if e.Temporary() {
15+
tp = "Transient"
16+
}
17+
w.WithTypes(tp, "net").WithTags(
18+
errors.T("addr", e.Addr),
19+
errors.T("is_timeout", e.Timeout()),
20+
errors.T("is_temporary", e.Temporary()),
21+
)
22+
return false
23+
24+
case *net.DNSError:
25+
tp := "Permanent"
26+
if e.Temporary() {
27+
tp = "Transient"
28+
}
29+
w.WithTypes(tp, "net").WithTags(
30+
errors.T("name", e.Name),
31+
errors.T("server", e.Server),
32+
errors.T("is_timeout", e.Timeout()),
33+
errors.T("is_temporary", e.Temporary()),
34+
)
35+
return false
36+
37+
case *net.ParseError:
38+
w.WithTypes("Permanent", "net").WithTags(
39+
errors.T("type", e.Type),
40+
errors.T("text", e.Text),
41+
)
42+
return false
43+
44+
case *net.OpError:
45+
tp := "Permanent"
46+
if e.Temporary() {
47+
tp = "Transient"
48+
}
49+
w.WithTypes(tp, "net").WithTags(
50+
errors.T("op", e.Op),
51+
errors.T("net", e.Net),
52+
errors.T("addr", e.Addr),
53+
errors.T("local_addr", e.Source),
54+
errors.T("is_timeout", e.Timeout()),
55+
errors.T("is_temporary", e.Temporary()),
56+
)
57+
return false
58+
case net.UnknownNetworkError:
59+
tp := "Permanent"
60+
if e.Temporary() {
61+
tp = "Transient"
62+
}
63+
w.WithTypes(tp, "net").WithTags(
64+
errors.T("is_timeout", e.Timeout()),
65+
errors.T("is_temporary", e.Temporary()),
66+
)
67+
}
68+
69+
switch err {
70+
case net.ErrWriteToConnected:
71+
w.WithTypes("Transient", "net")
72+
return false
73+
}
74+
return true
75+
}

wrapped.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ func newWrapped(err error, prefix string) *Wrapped {
2323
Prefix: prefix,
2424
Source: st(),
2525
}
26+
2627
}
2728

2829
// Wrapped contains a single error entry, unless it's the top level error, in

0 commit comments

Comments
 (0)