Skip to content

Commit e1de739

Browse files
committed
add basic Unwrap impl and tests
1 parent 27780fa commit e1de739

5 files changed

Lines changed: 117 additions & 29 deletions

File tree

error.go

Lines changed: 3 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 {
@@ -169,12 +169,12 @@ func (e *Error) Format(s fmt.State, verb rune) {
169169
message := e.fullMessage()
170170
switch verb {
171171
case 'v':
172-
io.WriteString(s, message)
172+
_, _ = io.WriteString(s, message)
173173
if s.Flag('+') {
174174
e.stackTrace.Format(s, verb)
175175
}
176176
case 's':
177-
io.WriteString(s, message)
177+
_, _ = io.WriteString(s, message)
178178
}
179179
}
180180

@@ -245,29 +245,3 @@ func (e *Error) messageText() string {
245245
}
246246
return message
247247
}
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.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// +build go1.13
2+
3+
package errorx
4+
5+
// todo godoc
6+
func (e *Error) As(target interface{}) bool {
7+
targetError, ok := target.(*error)
8+
if !ok {
9+
return false
10+
}
11+
12+
if !e.Is(*targetError) {
13+
return false
14+
}
15+
16+
// todo inject
17+
return true
18+
}
19+
20+
// todo godoc
21+
func (e *Error) Is(target error) bool {
22+
typedTarget := Cast(target)
23+
if typedTarget == nil {
24+
return false
25+
}
26+
27+
return e.IsOfType(typedTarget.Type()) // todo test with parents
28+
}
29+
30+
// todo godoc
31+
// If e.Unwrap() returns a non-nil error w, then we say that e wraps w.
32+
func (e *Error) Unwrap() error {
33+
if e.cause != nil && e.transparent {
34+
return e.cause
35+
} else {
36+
return nil
37+
}
38+
}

error_113_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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+
})
38+
39+
t.Run("DecorateForeign", func(t *testing.T) {
40+
err := Decorate(io.EOF, "")
41+
unwrapped := errors.Unwrap(err)
42+
require.NotNil(t, unwrapped)
43+
require.True(t, errors.Is(unwrapped, io.EOF))
44+
})
45+
}

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 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)