Skip to content

Commit bccf681

Browse files
Merge pull request #2 from IQ-tech/feat/add_more_functions
feat/add more functions
2 parents 13426d8 + 2f60790 commit bccf681

File tree

6 files changed

+297
-16
lines changed

6 files changed

+297
-16
lines changed

.github/workflows/main.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: Main
2+
on:
3+
push:
4+
branches:
5+
- master
6+
pull_request:
7+
branches:
8+
- master
9+
jobs:
10+
test:
11+
strategy:
12+
matrix:
13+
go-version: [1.16, 1.17]
14+
os: [ubuntu-latest, macos-latest, windows-latest]
15+
runs-on: ${{ matrix.os }}
16+
steps:
17+
- name: Install Go
18+
uses: actions/setup-go@v2
19+
with:
20+
go-version: ${{ matrix.go-version }}
21+
- name: Checkout code
22+
uses: actions/checkout@v2
23+
- name: Test
24+
run: go test ./...

README.md

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,28 @@ This package holds helpers for better error handling, adding wrapped and context
77
### `ApplicationError`
88

99
A generic application error, as a failure in parametrization or other unexpected error.
10-
*This error usually translates to a HTTP **503 Service Unavailable** error.*
10+
_This error usually translates to a HTTP **503 Service Unavailable** error._
1111

1212
### `ConflictError`
1313

1414
Indicates an action is conflicting with another action, as for example duplicated requests.
15-
*This error usually translates to a HTTP **429 Conflict** error.*
15+
_This error usually translates to a HTTP **429 Conflict** error._
1616

1717
### `ForbiddenError`
1818

1919
Indicates an action is not allowed, even if authenticated.
20-
*This error usually translates to a HTTP **403 Forbidden** error.*
20+
_This error usually translates to a HTTP **403 Forbidden** error._
2121

2222
### `NotAuthorizedError`
2323

2424
Indicates an action needs authorization or authentication to proceed.
25-
*This error usually translated to a HTTP **401 Unauthorized** error.*
25+
_This error usually translated to a HTTP **401 Unauthorized** error._
2626

2727
### `ValidationError`
2828

2929
Indicates that a parameter provided is not in the correct format or not present if required.
3030
This error allows to set a property that is related to the error and also add sub validation errors to build a validation error chain.
31-
*This error usually translates to a HTTP **422 Unprocessable Entity** error.*
31+
_This error usually translates to a HTTP **422 Unprocessable Entity** error._
3232

3333
> **Note:** All error constructors return a wrapped version of the error, removing the need to always pair an error constructor with a call to `errors.Wrap`.
3434
@@ -91,6 +91,26 @@ func Main() {
9191
}
9292
```
9393

94+
### `Wrapf(err error, format string, args ...interface{}) error`
95+
96+
Same as `Wrap` but accepts a format string
97+
98+
**Example:**
99+
100+
```go
101+
response := service.MakeHttpRequest()
102+
if response.StatusCode != http.StatusOK {
103+
return errors.Wrapf(`
104+
unexpected http response status.
105+
expected: %d
106+
got: %d
107+
`,
108+
http.StatusOK,
109+
response.StatusCode,
110+
)
111+
}
112+
```
113+
94114
## `GetOriginalError(err error) error`
95115

96116
There is a utility method to retrieve the original error from a chain of wrapped errors:
@@ -105,3 +125,47 @@ if originalErr == sql.ErrNoRows {
105125
// record not found
106126
}
107127
```
128+
129+
## `Is(a, b error) bool`
130+
131+
Checks if two errors are the same
132+
133+
**Example**
134+
135+
```go
136+
err := f()
137+
if errors.Is(err, domain.SomeDomainError) {
138+
139+
}
140+
```
141+
142+
Errors will be unwrapped before comparison.
143+
144+
Give preference to this function over
145+
146+
```go
147+
errors.GetOriginalError(a) == errors.GetOriginalError(b)
148+
```
149+
150+
## `Equals(a, b error) bool`
151+
152+
Checks if two errors have the same error message
153+
154+
**example**
155+
156+
```go
157+
errA := ...
158+
errB := ...
159+
160+
if errors.Equals(a, b) {
161+
162+
}
163+
```
164+
165+
Errors will be unwrapped before comparison.
166+
167+
Give preference to this function over
168+
169+
```go
170+
errors.GetOriginalError(a).Error() == errors.GetOriginalError(b).Error()
171+
```

errors.go

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,25 @@
22
package errors
33

44
import (
5+
stderrors "errors"
6+
"fmt"
57
"runtime"
68
"strings"
79
)
810

11+
func New(message string) error {
12+
return stderrors.New(message)
13+
}
14+
915
// Wrap wraps an error with a context message and adds execution path
1016
func Wrap(err error, messages ...string) error {
1117
return wrap(err, 4, messages...)
1218
}
1319

20+
func Wrapf(err error, format string, args ...interface{}) error {
21+
return wrap(err, 4, fmt.Sprintf(format, args...))
22+
}
23+
1424
// Wrap wraps an error with a context message and adds execution path
1525
func wrap(err error, skipStack int, messages ...string) error {
1626
if err != nil {
@@ -37,14 +47,39 @@ func GetOriginalError(err error) error {
3747

3848
// getCallerFunction returns the name of a method in the method chain
3949
// indicated by the stackOrder index from last to first
40-
func getCallerFunction(stackOrder int) string {
50+
func getCallerFunction(skip int) string {
4151
// get caller function path
42-
pc := make([]uintptr, 1)
43-
runtime.Callers(stackOrder, pc)
52+
pc := make([]uintptr, 15)
53+
n := runtime.Callers(skip, pc)
54+
frames := runtime.CallersFrames(pc[:n])
55+
frame, _ := frames.Next()
56+
path := strings.Split(frame.Function, "/")
4457

45-
funcRef := runtime.FuncForPC(pc[0])
58+
return path[len(path)-1]
59+
}
60+
61+
// Returns true when two errors at the same.
62+
//
63+
// err := errors.New("oops")
64+
// errors.Is(err, err) => true
65+
//
66+
// err2 = errors.New("oops")
67+
// errors.Is(err, err2) => false
68+
func Is(a, b error) bool {
69+
return GetOriginalError(a) == GetOriginalError((b))
70+
}
4671

47-
funcPath := strings.Split(funcRef.Name(), "/")
72+
// Returns true when two errors have the same error message.
73+
//
74+
// err := errors.New("oops")
75+
// errors.Equals(err, err) => true
76+
//
77+
// err2 := errors.New("oops...")
78+
// errors.Equals(err, err2) => false
79+
func Equals(a, b error) bool {
80+
if a == nil || b == nil {
81+
return Is(a, b)
82+
}
4883

49-
return funcPath[len(funcPath)-1]
84+
return GetOriginalError(a).Error() == GetOriginalError(b).Error()
5085
}

errors_test.go

Lines changed: 147 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ package errors
44
import (
55
"errors"
66
"testing"
7+
8+
"github.com/stretchr/testify/assert"
79
)
810

911
func wrap1() error {
@@ -15,12 +17,152 @@ func wrap2() error {
1517
}
1618

1719
func TestWrap(t *testing.T) {
18-
err := wrap2()
20+
assert.Equal(t, "go-errors.TestWrap ➡︎ some error", Wrap(errors.New("some error")).Error())
21+
assert.Equal(t, "go-errors.wrap1 ➡︎ some error", wrap1().Error())
22+
assert.Equal(t, "go-errors.wrap2 ➡︎ go-errors.wrap1 ➡︎ some error", wrap2().Error())
23+
}
24+
25+
func TestIs(t *testing.T) {
26+
t.Parallel()
1927

20-
errMsg := err.Error()
21-
expectedErrMsg := "go-errors.wrap2 ➡︎ go-errors.wrap1 ➡︎ some error"
28+
errOne := errors.New("err 1")
29+
errTwo := errors.New("err 2")
2230

23-
if errMsg != expectedErrMsg {
24-
t.Errorf("Wrap() returned wrong error message: %s", errMsg)
31+
tests := []struct {
32+
lhs error
33+
rhs error
34+
expected bool
35+
}{
36+
{
37+
lhs: nil,
38+
rhs: NewValidationError("a", "b"),
39+
expected: false,
40+
},
41+
{
42+
lhs: NewValidationError("a", "b"),
43+
rhs: nil,
44+
expected: false,
45+
},
46+
{
47+
lhs: nil,
48+
rhs: Wrap(NewValidationError("a", "b")),
49+
expected: false,
50+
},
51+
{
52+
lhs: Wrap(NewValidationError("a", "b")),
53+
rhs: nil,
54+
expected: false,
55+
},
56+
{
57+
lhs: NewValidationError("a", "b"),
58+
rhs: NewValidationError("b", "c"),
59+
expected: false,
60+
},
61+
{
62+
lhs: NewValidationError("a", "b"),
63+
rhs: NewValidationError("a", "b"),
64+
expected: false,
65+
},
66+
{
67+
lhs: errOne,
68+
rhs: errTwo,
69+
expected: false,
70+
},
71+
{
72+
lhs: errOne,
73+
rhs: errOne,
74+
expected: true,
75+
},
76+
{
77+
lhs: Wrap(errOne),
78+
rhs: errOne,
79+
expected: true,
80+
},
81+
{
82+
lhs: Wrap(errOne),
83+
rhs: Wrap(errOne),
84+
expected: true,
85+
},
86+
{
87+
lhs: Wrap(Wrap(Wrap(errTwo))),
88+
rhs: Wrap(errTwo),
89+
expected: true,
90+
},
91+
{
92+
lhs: Wrap(Wrap(Wrap(errOne))),
93+
rhs: Wrap(errTwo),
94+
expected: false,
95+
},
2596
}
97+
98+
for _, tt := range tests {
99+
actual := Is(tt.lhs, tt.rhs)
100+
101+
assert.Equal(t, tt.expected, actual)
102+
}
103+
}
104+
105+
func TestEquals(t *testing.T) {
106+
t.Parallel()
107+
108+
tests := []struct {
109+
lhs error
110+
rhs error
111+
expected bool
112+
}{
113+
{
114+
lhs: nil,
115+
rhs: nil,
116+
expected: true,
117+
},
118+
{
119+
lhs: nil,
120+
rhs: nil,
121+
expected: true,
122+
},
123+
{
124+
lhs: NewValidationError("card_id", "Cartão deve ser informado!"),
125+
rhs: nil,
126+
expected: false,
127+
},
128+
{
129+
lhs: nil,
130+
rhs: NewValidationError("card_id", "Cartão deve ser informado!"),
131+
expected: false,
132+
},
133+
{
134+
lhs: NewValidationError("card_id", "Cartão deve ser informado!"),
135+
rhs: NewValidationError("card_id", "Cartão deve ser informado!"),
136+
expected: true,
137+
},
138+
{
139+
lhs: NewValidationError("card_id", "Cartão deve ser informado!"),
140+
rhs: NewValidationError("card_id", "Cartão deve ser informado!"),
141+
expected: true,
142+
},
143+
}
144+
145+
for _, tt := range tests {
146+
actual := Equals(tt.lhs, tt.rhs)
147+
148+
assert.Equal(t, tt.expected, actual)
149+
}
150+
}
151+
152+
func TestWrapf(t *testing.T) {
153+
t.Parallel()
154+
155+
t.Run("when error is nil", func(t *testing.T) {
156+
t.Run("returns nil", func(t *testing.T) {
157+
actual := Wrapf(nil, "%s", "test")
158+
assert.Equal(t, nil, actual)
159+
})
160+
})
161+
162+
t.Run("when error is not nil", func(t *testing.T) {
163+
t.Run("adds formatted string to wrapped error messages", func(t *testing.T) {
164+
actual := Wrapf(New("oops"), "%s %d", "test", 1)
165+
assert.Equal(t, "go-errors.TestWrapf.func2.1: test 1; ➡︎ oops", actual.Error())
166+
})
167+
})
26168
}

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module github.com/IQ-tech/go-errors
2+
3+
go 1.16
4+
5+
require github.com/stretchr/testify v1.7.0

go.sum

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
2+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
6+
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
7+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
8+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
9+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
10+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
11+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)