Skip to content

Commit 5a91b6f

Browse files
author
Dean Karn
authored
Merge pull request #2 from go-playground/structural-updates
update internals
2 parents b4ae218 + ba974b9 commit 5a91b6f

File tree

10 files changed

+191
-195
lines changed

10 files changed

+191
-195
lines changed

README.md

Lines changed: 3 additions & 7 deletions
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.3.0-green.svg)
3+
![Project status](https://img.shields.io/badge/version-2.0.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)
@@ -51,12 +51,8 @@ func main() {
5151
fmt.Println(cause)
5252

5353
// can even still inspect the internal error
54-
fmt.Println(errors.IsErr(err, io.EOF)) // will extract the cause for you
55-
fmt.Println(errors.IsErr(cause, io.EOF))
56-
57-
// or manually with access to base fields
58-
wrapped := cause.(*errors.Wrapped)
59-
fmt.Println(wrapped.Err == io.EOF)
54+
fmt.Println(errors.Cause(err) == io.EOF) // will extract the cause for you
55+
fmt.Println(errors.Cause(cause) == io.EOF)
6056
}
6157

6258
func level1(value string) error {

_examples/basic/main.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,8 @@ func main() {
1919
fmt.Println(cause)
2020

2121
// 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)
22+
fmt.Println(errors.Cause(err) == io.EOF) // will extract the cause for you
23+
fmt.Println(errors.Cause(cause) == io.EOF)
2824
}
2925

3026
func level1(value string) error {

chain.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package errors
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
)
7+
8+
// T is a shortcut to make a Tag
9+
func T(key string, value interface{}) Tag {
10+
return Tag{Key: key, Value: value}
11+
}
12+
13+
// Tag contains a single key value conbination
14+
// to be attached to your error
15+
type Tag struct {
16+
Key string
17+
Value interface{}
18+
}
19+
20+
func newLink(err error, prefix string) *Link {
21+
return &Link{
22+
Err: err,
23+
Prefix: prefix,
24+
Source: st(),
25+
}
26+
27+
}
28+
29+
// Chain contains the chained errors, the links, of the chains if you will
30+
type Chain []*Link
31+
32+
// Error returns the formatted error string
33+
func (c Chain) Error() string {
34+
lines := make([]string, 0, len(c))
35+
// source=<source> <prefix>: <error> tag=value tag2=value2 types=type1,type2
36+
for i := len(c) - 1; i >= 0; i-- {
37+
line := c[i].formatError()
38+
lines = append(lines, line)
39+
}
40+
return strings.Join(lines, "\n")
41+
}
42+
43+
// Link contains a single error entry, unless it's the top level error, in
44+
// which case it only contains an array of errors
45+
type Link struct {
46+
47+
// Err is the wrapped error, either the original or already wrapped
48+
Err error
49+
50+
// Prefix contains the error prefix text
51+
Prefix string
52+
53+
// Type stores one or more categorized types of error set by the caller using WithTypes and is optional
54+
Types []string
55+
56+
// Tags contains an array of tags associated with this error, if any
57+
Tags []Tag
58+
59+
// Source contains the name, file and lines obtained from the stack trace
60+
Source string
61+
}
62+
63+
// formatError prints a single Links error
64+
func (l *Link) formatError() string {
65+
line := fmt.Sprintf("source=%s ", l.Source)
66+
67+
if l.Prefix != "" {
68+
line += l.Prefix + ": "
69+
}
70+
71+
line += l.Err.Error()
72+
73+
for _, tag := range l.Tags {
74+
line += fmt.Sprintf(" %s=%v", tag.Key, tag.Value)
75+
}
76+
77+
if len(l.Types) > 0 {
78+
line += " types=" + strings.Join(l.Types, ",")
79+
}
80+
return line
81+
}
82+
83+
// helper method to get the current *Link from the top level
84+
func (c Chain) current() *Link {
85+
return c[len(c)-1]
86+
}
87+
88+
// WithTags allows the addition of multiple tags
89+
func (c Chain) WithTags(tags ...Tag) Chain {
90+
l := c.current()
91+
if len(l.Tags) == 0 {
92+
l.Tags = make([]Tag, 0, len(tags))
93+
}
94+
l.Tags = append(l.Tags, tags...)
95+
return c
96+
}
97+
98+
// WithTag allows the addition of a single tag
99+
func (c Chain) WithTag(key string, value interface{}) Chain {
100+
return c.WithTags(Tag{Key: key, Value: value})
101+
}
102+
103+
// WithTypes sets one or more categorized types on the Link error
104+
func (c Chain) WithTypes(typ ...string) Chain {
105+
l := c.current()
106+
l.Types = append(l.Types, typ...)
107+
return c
108+
}

errors.go

Lines changed: 43 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package errors
22

3-
import "errors"
3+
import (
4+
"errors"
5+
)
46

57
var (
68
helpers []Helper
@@ -13,74 +15,70 @@ func RegisterHelper(helper Helper) {
1315
helpers = append(helpers, helper)
1416
}
1517

18+
// New creates an error with the provided text and automatically wraps it with line information.
19+
func New(s string) Chain {
20+
return wrap(errors.New(s), "")
21+
}
22+
1623
// Wrap encapsulates the error, stores a contextual prefix and automatically obtains
1724
// a stack trace.
18-
func Wrap(err error, prefix string) (w *Wrapped) {
25+
func Wrap(err error, prefix string) Chain {
1926
return wrap(err, prefix)
2027
}
2128

22-
func wrap(err error, prefix string) (w *Wrapped) {
29+
func wrap(err error, prefix string) (c Chain) {
2330
var ok bool
24-
if w, ok = err.(*Wrapped); ok {
25-
w.Errors = append(w.Errors, newWrapped(err, prefix))
31+
if c, ok = err.(Chain); ok {
32+
c = append(c, newLink(err, prefix))
2633
} else {
27-
w = &Wrapped{
28-
Errors: []*Wrapped{newWrapped(err, prefix)},
29-
}
34+
c = Chain{newLink(err, prefix)}
3035
}
3136
for _, h := range helpers {
32-
if !h(w, err) {
37+
if !h(c, err) {
3338
break
3439
}
3540
}
3641
return
3742
}
3843

44+
// Cause extracts and returns the root wrapped error (the naked error with no additional information
45+
func Cause(err error) error {
46+
switch t := err.(type) {
47+
case Chain:
48+
return t[0].Err
49+
default:
50+
return err
51+
}
52+
// TODO: lookup via Cause interface recursively on error
53+
}
54+
3955
// HasType is a helper function that will recurse up from the root error and check that the provided type
4056
// is present using an equality check
4157
func HasType(err error, typ string) bool {
42-
w, ok := err.(*Wrapped)
43-
if !ok {
44-
return false
45-
}
46-
for i := len(w.Errors) - 1; i >= 0; i-- {
47-
for j := 0; j < len(w.Errors[i].Types); j++ {
48-
if w.Errors[i].Types[j] == typ {
49-
return true
58+
switch t := err.(type) {
59+
case Chain:
60+
for i := len(t) - 1; i >= 0; i-- {
61+
for j := 0; j < len(t[i].Types); j++ {
62+
if t[i].Types[j] == typ {
63+
return true
64+
}
5065
}
5166
}
5267
}
5368
return false
5469
}
5570

56-
// Cause extracts and returns the root error
57-
func Cause(err error) error {
58-
if w, ok := err.(*Wrapped); ok {
59-
// if root level error
60-
if len(w.Errors) > 0 {
61-
return w.Errors[0]
62-
}
63-
// already extracted error
64-
return w
65-
}
66-
return err
67-
}
68-
69-
// IsErr will fetch the root error, and check the original error against the provided type
70-
// eg. errors.IsErr(io.EOF)
71-
func IsErr(err, errType error) bool {
72-
if w, ok := err.(*Wrapped); ok {
73-
// if root level error
74-
if len(w.Errors) > 0 {
75-
return w.Errors[0].Err == errType
71+
// LookupTag recursively searches for the provided tag and returns it's value or nil
72+
func LookupTag(err error, key string) interface{} {
73+
switch t := err.(type) {
74+
case Chain:
75+
for i := len(t) - 1; i >= 0; i-- {
76+
for j := 0; j < len(t[i].Tags); j++ {
77+
if t[i].Tags[j].Key == key {
78+
return t[i].Tags[j].Value
79+
}
80+
}
7681
}
77-
// already extracted error
78-
return w.Err == errType
7982
}
80-
return err == errType
81-
}
82-
83-
// New creates an error with the provided text and automatically wraps it with line information.
84-
func New(s string) *Wrapped {
85-
return wrap(errors.New(s), "")
83+
return nil
8684
}

errors_test.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,30 +98,30 @@ func TestCause(t *testing.T) {
9898
err := Wrap(defaultErr, "prefix")
9999
err = Wrap(err, "prefix2")
100100
cause := Cause(err)
101-
expect := "prefix: this is an error"
102-
if !strings.HasSuffix(cause.Error(), expect) {
101+
expect := "this is an error"
102+
if cause.Error() != expect {
103103
t.Fatalf("want %s got %s", expect, err.Error())
104104
}
105105
}
106106

107-
func TestIsErr(t *testing.T) {
107+
func TestCause2(t *testing.T) {
108108
err := Wrap(io.EOF, "prefix")
109109
err = Wrap(err, "prefix2")
110110

111-
if !IsErr(err, io.EOF) {
111+
if Cause(err) != io.EOF {
112112
t.Fatalf("want %t got %t", true, false)
113113
}
114114
cause := Cause(err)
115-
if !IsErr(cause, io.EOF) {
115+
if Cause(cause) != io.EOF {
116116
t.Fatalf("want %t got %t", true, false)
117117
}
118-
if !IsErr(io.EOF, io.EOF) {
118+
if Cause(io.EOF) != io.EOF {
119119
t.Fatalf("want %t got %t", true, false)
120120
}
121121
}
122122

123123
func TestHelpers(t *testing.T) {
124-
fn := func(w *Wrapped, err error) (cont bool) {
124+
fn := func(w Chain, err error) (cont bool) {
125125
w.WithTypes("Test").WithTags(T("test", "tag")).WithTag("foo", "bar")
126126
return false
127127
}
@@ -132,3 +132,10 @@ func TestHelpers(t *testing.T) {
132132
t.Errorf("Expected to have type 'Test'")
133133
}
134134
}
135+
136+
func TestLookupTag(t *testing.T) {
137+
err := Wrap(io.EOF, "prefix").WithTag("Key", "Value")
138+
if LookupTag(err, "Key").(string) != "Value" {
139+
t.Fatalf("want %s got %v", "Value", LookupTag(err, "Key"))
140+
}
141+
}

helpers.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package errors
22

33
// 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(...),
4+
// add it to the supplied *Link error; this can be used independently or by registering using errors.REgisterHelper(...),
55
// which will run the registered helper every time errors.Wrap(...) is called.
6-
type Helper func(*Wrapped, error) bool
6+
type Helper func(Chain, error) bool

helpers/awserrors/awserrors.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,25 @@ import (
66
)
77

88
// AWSErrors helps classify io related errors
9-
func AWSErrors(w *errors.Wrapped, err error) (cont bool) {
9+
func AWSErrors(c errors.Chain, err error) (cont bool) {
1010
switch e := err.(type) {
1111
case awserr.BatchError:
12-
w.WithTypes("Transient", "Batch").WithTags(errors.T("aws_error_code", e.Code()))
12+
c.WithTypes("Transient", "Batch").WithTags(errors.T("aws_error_code", e.Code()))
1313
return
1414

1515
case awserr.BatchedErrors:
16-
w.WithTypes("Transient", "Batch")
16+
c.WithTypes("Transient", "Batch")
1717
return
1818

1919
case awserr.RequestFailure:
20-
w.WithTypes("Transient", "Request").WithTags(
20+
c.WithTypes("Transient", "Request").WithTags(
2121
errors.T("status_code", e.StatusCode()),
2222
errors.T("request_id", e.RequestID()),
2323
)
2424
return
2525

2626
case awserr.Error:
27-
w.WithTypes("General", "Error").WithTags(errors.T("aws_error_code", e.Code()))
27+
c.WithTypes("General", "Error").WithTags(errors.T("aws_error_code", e.Code()))
2828
return
2929
}
3030
return true

helpers/ioerrors/ioerrors.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,25 @@ import (
77
)
88

99
// IOErrors helps classify io related errors
10-
func IOErrors(w *errors.Wrapped, err error) (cont bool) {
10+
func IOErrors(c errors.Chain, err error) (cont bool) {
1111
switch err {
1212
case io.EOF:
13-
w.WithTypes("io")
13+
c.WithTypes("io")
1414
return
1515
case io.ErrClosedPipe:
16-
w.WithTypes("Permanent", "io")
16+
c.WithTypes("Permanent", "io")
1717
return
1818
case io.ErrNoProgress:
19-
w.WithTypes("Permanent", "io")
19+
c.WithTypes("Permanent", "io")
2020
return
2121
case io.ErrShortBuffer:
22-
w.WithTypes("Permanent", "io")
22+
c.WithTypes("Permanent", "io")
2323
return
2424
case io.ErrShortWrite:
25-
w.WithTypes("Permanent", "io")
25+
c.WithTypes("Permanent", "io")
2626
return
2727
case io.ErrUnexpectedEOF:
28-
w.WithTypes("Transient", "io")
28+
c.WithTypes("Transient", "io")
2929
return
3030
}
3131
return true

0 commit comments

Comments
 (0)