Skip to content

Commit c749e95

Browse files
authored
Merge feature/1.13_errors
2 parents 27780fa + a620e5c commit c749e95

File tree

4 files changed

+158
-29
lines changed

4 files changed

+158
-29
lines changed

error.go

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ func (e *Error) HasTrait(key Trait) bool {
103103
return false
104104
}
105105

106-
// IsOfType is a proper type check for an error.
106+
// IsOfType is a proper type check for an errorx-based errors.
107107
// It takes the transparency and error types hierarchy into account,
108108
// so that type check against any supertype of the original cause passes.
109109
func (e *Error) IsOfType(t *Type) bool {
@@ -156,6 +156,25 @@ func (e *Error) Cause() error {
156156
return e.cause
157157
}
158158

159+
// Is returns true if and only if target is errorx error that passes errorx type check against current error.
160+
// This behaviour is exactly the same as that of IsOfType().
161+
// See also: errors.Is()
162+
func (e *Error) Is(target error) bool {
163+
typedTarget := Cast(target)
164+
return typedTarget != nil && IsOfType(e, typedTarget.Type())
165+
}
166+
167+
// From errors package: if e.Unwrap() returns a non-nil error w, then we say that e wraps w.
168+
// Unwrap returns cause of current error in case it is wrapped transparently, nil otherwise.
169+
// See also: errors.Unwrap()
170+
func (e *Error) Unwrap() error {
171+
if e.cause != nil && e.transparent {
172+
return e.cause
173+
} else {
174+
return nil
175+
}
176+
}
177+
159178
// Format implements the Formatter interface.
160179
// Supported verbs:
161180
//
@@ -169,12 +188,12 @@ func (e *Error) Format(s fmt.State, verb rune) {
169188
message := e.fullMessage()
170189
switch verb {
171190
case 'v':
172-
io.WriteString(s, message)
191+
_, _ = io.WriteString(s, message)
173192
if s.Flag('+') {
174193
e.stackTrace.Format(s, verb)
175194
}
176195
case 's':
177-
io.WriteString(s, message)
196+
_, _ = io.WriteString(s, message)
178197
}
179198
}
180199

@@ -245,29 +264,3 @@ func (e *Error) messageText() string {
245264
}
246265
return message
247266
}
248-
249-
func joinStringsIfNonEmpty(delimiter string, parts ...string) string {
250-
switch len(parts) {
251-
case 0:
252-
return ""
253-
case 1:
254-
return parts[0]
255-
case 2:
256-
if len(parts[0]) == 0 {
257-
return parts[1]
258-
} else if len(parts[1]) == 0 {
259-
return parts[0]
260-
} else {
261-
return parts[0] + delimiter + parts[1]
262-
}
263-
default:
264-
filteredParts := make([]string, 0, len(parts))
265-
for _, part := range parts {
266-
if len(part) > 0 {
267-
filteredParts = append(filteredParts, part)
268-
}
269-
}
270-
271-
return strings.Join(filteredParts, delimiter)
272-
}
273-
}

error_113_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// +build go1.13
2+
3+
package errorx
4+
5+
import (
6+
"errors"
7+
"io"
8+
"testing"
9+
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestErrorUnwrap(t *testing.T) {
14+
t.Run("Trivial", func(t *testing.T) {
15+
err := testType.NewWithNoMessage()
16+
unwrapped := errors.Unwrap(err)
17+
require.Nil(t, unwrapped)
18+
})
19+
20+
t.Run("Wrap", func(t *testing.T) {
21+
err := testTypeBar1.Wrap(testType.NewWithNoMessage(), "")
22+
unwrapped := errors.Unwrap(err)
23+
require.Nil(t, unwrapped)
24+
})
25+
26+
t.Run("WrapForeign", func(t *testing.T) {
27+
err := testTypeBar1.Wrap(io.EOF, "")
28+
unwrapped := errors.Unwrap(err)
29+
require.Nil(t, unwrapped)
30+
})
31+
32+
t.Run("Decorate", func(t *testing.T) {
33+
err := Decorate(testType.NewWithNoMessage(), "")
34+
unwrapped := errors.Unwrap(err)
35+
require.NotNil(t, unwrapped)
36+
require.True(t, IsOfType(unwrapped, testType))
37+
require.True(t, Cast(unwrapped).Type() == testType)
38+
})
39+
40+
t.Run("DecorateForeign", func(t *testing.T) {
41+
err := Decorate(io.EOF, "")
42+
unwrapped := errors.Unwrap(err)
43+
require.NotNil(t, unwrapped)
44+
require.True(t, errors.Is(unwrapped, io.EOF))
45+
require.True(t, unwrapped == io.EOF)
46+
})
47+
48+
t.Run("Nested", func(t *testing.T) {
49+
err := Decorate(Decorate(testType.NewWithNoMessage(), ""), "")
50+
unwrapped := errors.Unwrap(err)
51+
require.NotNil(t, unwrapped)
52+
unwrapped = errors.Unwrap(unwrapped)
53+
require.NotNil(t, unwrapped)
54+
require.True(t, IsOfType(unwrapped, testType))
55+
})
56+
57+
t.Run("NestedWrapped", func(t *testing.T) {
58+
err := Decorate(testTypeBar1.Wrap(testType.NewWithNoMessage(), ""), "")
59+
unwrapped := errors.Unwrap(err)
60+
require.NotNil(t, unwrapped)
61+
require.True(t, IsOfType(unwrapped, testTypeBar1))
62+
unwrapped = errors.Unwrap(unwrapped)
63+
require.Nil(t, unwrapped)
64+
})
65+
66+
t.Run("NestedForeign", func(t *testing.T) {
67+
err := Decorate(Decorate(io.EOF, ""), "")
68+
unwrapped := errors.Unwrap(err)
69+
require.NotNil(t, unwrapped)
70+
unwrapped = errors.Unwrap(unwrapped)
71+
require.NotNil(t, unwrapped)
72+
require.True(t, errors.Is(unwrapped, io.EOF))
73+
})
74+
}
75+
76+
func TestErrorIs(t *testing.T) {
77+
t.Run("Trivial", func(t *testing.T) {
78+
err := testType.NewWithNoMessage()
79+
require.True(t, errors.Is(err, testType.NewWithNoMessage()))
80+
require.False(t, errors.Is(err, testTypeBar1.NewWithNoMessage()))
81+
})
82+
83+
t.Run("Wrap", func(t *testing.T) {
84+
err := testTypeBar1.Wrap(testType.NewWithNoMessage(), "")
85+
require.False(t, errors.Is(err, testType.NewWithNoMessage()))
86+
require.True(t, errors.Is(err, testTypeBar1.NewWithNoMessage()))
87+
})
88+
89+
t.Run("Supertype", func(t *testing.T) {
90+
err := testSubtype0.Wrap(testTypeBar1.NewWithNoMessage(), "")
91+
require.True(t, errors.Is(err, testType.NewWithNoMessage()))
92+
require.True(t, errors.Is(err, testSubtype0.NewWithNoMessage()))
93+
require.False(t, errors.Is(err, testTypeBar1.NewWithNoMessage()))
94+
})
95+
96+
t.Run("Decorate", func(t *testing.T) {
97+
err := Decorate(testType.NewWithNoMessage(), "")
98+
require.True(t, errors.Is(err, testType.NewWithNoMessage()))
99+
})
100+
101+
t.Run("DecorateForeign", func(t *testing.T) {
102+
err := Decorate(io.EOF, "")
103+
require.True(t, errors.Is(err, io.EOF))
104+
})
105+
}

helper.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package errorx
2+
3+
import "strings"
4+
5+
func joinStringsIfNonEmpty(delimiter string, parts ...string) string {
6+
switch len(parts) {
7+
case 0:
8+
return ""
9+
case 1:
10+
return parts[0]
11+
case 2:
12+
if len(parts[0]) == 0 {
13+
return parts[1]
14+
} else if len(parts[1]) == 0 {
15+
return parts[0]
16+
} else {
17+
return parts[0] + delimiter + parts[1]
18+
}
19+
default:
20+
filteredParts := make([]string, 0, len(parts))
21+
for _, part := range parts {
22+
if len(part) > 0 {
23+
filteredParts = append(filteredParts, part)
24+
}
25+
}
26+
27+
return strings.Join(filteredParts, delimiter)
28+
}
29+
}
30+

type.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ func (t *Type) NewWithNoMessage() *Error {
5656
// The original error will not pass its dynamic properties, and those are accessible only via direct walk over Cause() chain.
5757
// Without args, leaves the original message intact, so a message may be generated or provided externally.
5858
// With args, a formatting is performed, and it is therefore expected a format string to be constant.
59+
// NB: Wrap is NOT the reverse of errors.Unwrap() or Error.Unwrap() method; name may be changed in future releases to avoid confusion.
5960
func (t *Type) Wrap(err error, message string, args ...interface{}) *Error {
6061
return NewErrorBuilder(t).
6162
WithConditionallyFormattedMessage(message, args...).

0 commit comments

Comments
 (0)