Skip to content

Commit 5f56b93

Browse files
authored
feat!: add As, drop HasType and Split (#18)
1 parent 0807571 commit 5f56b93

File tree

4 files changed

+44
-79
lines changed

4 files changed

+44
-79
lines changed

README.md

+26-22
Original file line numberDiff line numberDiff line change
@@ -22,30 +22,31 @@ go get go-simpler.org/errorsx
2222
A multi-target version of `errors.Is`.
2323

2424
```go
25-
if errorsx.IsAny(err, os.ErrNotExist, os.ErrPermission) {
25+
// Before:
26+
if errors.Is(err, os.ErrNotExist) || errors.Is(err, os.ErrPermission) {
2627
fmt.Println(err)
2728
}
28-
```
29-
30-
### HasType
3129

32-
Reports whether the error has type `T`.
33-
It is equivalent to `errors.As` without the need to declare the target variable.
34-
35-
```go
36-
if errorsx.HasType[*os.PathError](err) {
30+
// After:
31+
if errorsx.IsAny(err, os.ErrNotExist, os.ErrPermission) {
3732
fmt.Println(err)
3833
}
3934
```
4035

41-
### Split
36+
### As
4237

43-
Returns errors joined by `errors.Join` or by `fmt.Errorf` with multiple `%w` verbs.
44-
If the given error was created differently, `Split` returns nil.
38+
A generic version of `errors.As`.
4539

4640
```go
47-
if errs := errorsx.Split(err); errs != nil {
48-
fmt.Println(errs)
41+
// Before:
42+
var pathErr *os.PathError
43+
if errors.As(err, &pathErr) {
44+
fmt.Println(pathErr.Path)
45+
}
46+
47+
// After:
48+
if pathErr, ok := errorsx.As[*os.PathError](err); ok {
49+
fmt.Println(pathErr.Path)
4950
}
5051
```
5152

@@ -54,13 +55,16 @@ if errs := errorsx.Split(err); errs != nil {
5455
Attempts to close the given `io.Closer` and assigns the returned error (if any) to `err`.
5556

5657
```go
57-
func() (err error) {
58-
f, err := os.Open("file.txt")
59-
if err != nil {
60-
return err
61-
}
62-
defer errorsx.Close(f, &err)
63-
64-
return nil
58+
f, err := os.Open("file.txt")
59+
if err != nil {
60+
return err
61+
}
62+
63+
// Before:
64+
defer func() {
65+
err = errors.Join(err, f.Close())
6566
}()
67+
68+
// After:
69+
defer errorsx.Close(f, &err)
6670
```

errorsx.go

+5-17
Original file line numberDiff line numberDiff line change
@@ -19,27 +19,15 @@ func IsAny(err, target error, targets ...error) bool {
1919
return false
2020
}
2121

22-
// HasType reports whether the error has type T.
23-
// It is equivalent to [errors.As] without the need to declare the target variable.
24-
func HasType[T any](err error) bool {
22+
// As is a generic version of [errors.As].
23+
func As[T any](err error) (T, bool) {
2524
var t T
26-
return errors.As(err, &t)
27-
}
28-
29-
// Split returns errors joined by [errors.Join] or by [fmt.Errorf] with multiple %w verbs.
30-
// If the given error was created differently, Split returns nil.
31-
func Split(err error) []error {
32-
u, ok := err.(interface{ Unwrap() []error })
33-
if !ok {
34-
return nil
35-
}
36-
return u.Unwrap()
25+
ok := errors.As(err, &t)
26+
return t, ok
3727
}
3828

3929
// Close attempts to close the given [io.Closer] and assigns the returned error (if any) to err.
4030
// If err is already not nil, it will be joined with the [io.Closer]'s error.
4131
func Close(c io.Closer, err *error) { //nolint:gocritic // ptrToRefParam: err must be a pointer here.
42-
if cerr := c.Close(); cerr != nil {
43-
*err = errors.Join(*err, cerr)
44-
}
32+
*err = errors.Join(*err, c.Close())
4533
}

errorsx_test.go

+8-28
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package errorsx_test
33
import (
44
"errors"
55
"fmt"
6-
"slices"
76
"testing"
87

98
"go-simpler.org/errorsx"
@@ -30,16 +29,17 @@ func TestIsAny(t *testing.T) {
3029
}
3130
}
3231

33-
func TestHasType(t *testing.T) {
32+
func TestAs(t *testing.T) {
33+
isok := func(_ any, ok bool) bool { return ok }
34+
3435
tests := map[string]struct {
3536
fn func(error) bool
3637
err error
3738
want bool
3839
}{
39-
"no match": {fn: errorsx.HasType[barError], err: errFoo, want: false},
40-
"match (exact)": {fn: errorsx.HasType[fooError], err: errFoo, want: true},
41-
"match (wrapped)": {fn: errorsx.HasType[fooError], err: wrap(errFoo), want: true},
42-
"match (interface)": {fn: errorsx.HasType[interface{ Error() string }], err: errFoo, want: true},
40+
"no match": {fn: func(err error) bool { return isok(errorsx.As[barError](err)) }, err: errFoo, want: false},
41+
"match (exact)": {fn: func(err error) bool { return isok(errorsx.As[fooError](err)) }, err: errFoo, want: true},
42+
"match (wrapped)": {fn: func(err error) bool { return isok(errorsx.As[fooError](err)) }, err: wrap(errFoo), want: true},
4343
}
4444

4545
for name, test := range tests {
@@ -51,26 +51,6 @@ func TestHasType(t *testing.T) {
5151
}
5252
}
5353

54-
func TestSplit(t *testing.T) {
55-
tests := map[string]struct {
56-
err error
57-
wantErrs []error
58-
}{
59-
"nil error": {err: nil, wantErrs: nil},
60-
"single error": {err: errFoo, wantErrs: nil},
61-
"joined errors (errors.Join)": {err: errors.Join(errFoo, errBar), wantErrs: []error{errFoo, errBar}},
62-
"joined errors (fmt.Errorf)": {err: fmt.Errorf("%w; %w", errFoo, errBar), wantErrs: []error{errFoo, errBar}},
63-
}
64-
65-
for name, test := range tests {
66-
t.Run(name, func(t *testing.T) {
67-
if gotErrs := errorsx.Split(test.err); !slices.Equal(gotErrs, test.wantErrs) {
68-
t.Errorf("got %v; want %v", gotErrs, test.wantErrs)
69-
}
70-
})
71-
}
72-
}
73-
7454
func TestClose(t *testing.T) {
7555
tests := map[string]struct {
7656
mainErr error
@@ -112,8 +92,8 @@ type barError struct{}
11292

11393
func (barError) Error() string { return "bar" }
11494

115-
func wrap(err error) error { return fmt.Errorf("%w", err) }
116-
11795
type errCloser struct{ err error }
11896

11997
func (c *errCloser) Close() error { return c.err }
98+
99+
func wrap(err error) error { return fmt.Errorf("%w", err) }

example_test.go

+5-12
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,20 @@ func ExampleIsAny() {
1515
}
1616
}
1717

18-
func ExampleHasType() {
19-
if errorsx.HasType[*os.PathError](err) {
20-
fmt.Println(err)
18+
func ExampleAs() {
19+
if pathErr, ok := errorsx.As[*os.PathError](err); ok {
20+
fmt.Println(pathErr.Path)
2121
}
2222
}
2323

24-
func ExampleSplit() {
25-
if errs := errorsx.Split(err); errs != nil {
26-
fmt.Println(errs)
27-
}
28-
}
29-
30-
//nolint:errcheck // this is just an example.
3124
func ExampleClose() {
32-
func() (err error) {
25+
_ = func() (err error) {
3326
f, err := os.Open("file.txt")
3427
if err != nil {
3528
return err
3629
}
3730
defer errorsx.Close(f, &err)
3831

3932
return nil
40-
}()
33+
}
4134
}

0 commit comments

Comments
 (0)