diff --git a/README.md b/README.md index 1315c56..203be0f 100644 --- a/README.md +++ b/README.md @@ -22,30 +22,31 @@ go get go-simpler.org/errorsx A multi-target version of `errors.Is`. ```go -if errorsx.IsAny(err, os.ErrNotExist, os.ErrPermission) { +// Before: +if errors.Is(err, os.ErrNotExist) || errors.Is(err, os.ErrPermission) { fmt.Println(err) } -``` - -### HasType -Reports whether the error has type `T`. -It is equivalent to `errors.As` without the need to declare the target variable. - -```go -if errorsx.HasType[*os.PathError](err) { +// After: +if errorsx.IsAny(err, os.ErrNotExist, os.ErrPermission) { fmt.Println(err) } ``` -### Split +### As -Returns errors joined by `errors.Join` or by `fmt.Errorf` with multiple `%w` verbs. -If the given error was created differently, `Split` returns nil. +A generic version of `errors.As`. ```go -if errs := errorsx.Split(err); errs != nil { - fmt.Println(errs) +// Before: +var pathErr *os.PathError +if errors.As(err, &pathErr) { + fmt.Println(pathErr.Path) +} + +// After: +if pathErr, ok := errorsx.As[*os.PathError](err); ok { + fmt.Println(pathErr.Path) } ``` @@ -54,13 +55,16 @@ if errs := errorsx.Split(err); errs != nil { Attempts to close the given `io.Closer` and assigns the returned error (if any) to `err`. ```go -func() (err error) { - f, err := os.Open("file.txt") - if err != nil { - return err - } - defer errorsx.Close(f, &err) - - return nil +f, err := os.Open("file.txt") +if err != nil { + return err +} + +// Before: +defer func() { + err = errors.Join(err, f.Close()) }() + +// After: +defer errorsx.Close(f, &err) ``` diff --git a/errorsx.go b/errorsx.go index 276d83b..ba82e02 100644 --- a/errorsx.go +++ b/errorsx.go @@ -19,27 +19,15 @@ func IsAny(err, target error, targets ...error) bool { return false } -// HasType reports whether the error has type T. -// It is equivalent to [errors.As] without the need to declare the target variable. -func HasType[T any](err error) bool { +// As is a generic version of [errors.As]. +func As[T any](err error) (T, bool) { var t T - return errors.As(err, &t) -} - -// Split returns errors joined by [errors.Join] or by [fmt.Errorf] with multiple %w verbs. -// If the given error was created differently, Split returns nil. -func Split(err error) []error { - u, ok := err.(interface{ Unwrap() []error }) - if !ok { - return nil - } - return u.Unwrap() + ok := errors.As(err, &t) + return t, ok } // Close attempts to close the given [io.Closer] and assigns the returned error (if any) to err. // If err is already not nil, it will be joined with the [io.Closer]'s error. func Close(c io.Closer, err *error) { //nolint:gocritic // ptrToRefParam: err must be a pointer here. - if cerr := c.Close(); cerr != nil { - *err = errors.Join(*err, cerr) - } + *err = errors.Join(*err, c.Close()) } diff --git a/errorsx_test.go b/errorsx_test.go index add3361..4305e77 100644 --- a/errorsx_test.go +++ b/errorsx_test.go @@ -3,7 +3,6 @@ package errorsx_test import ( "errors" "fmt" - "slices" "testing" "go-simpler.org/errorsx" @@ -30,16 +29,17 @@ func TestIsAny(t *testing.T) { } } -func TestHasType(t *testing.T) { +func TestAs(t *testing.T) { + isok := func(_ any, ok bool) bool { return ok } + tests := map[string]struct { fn func(error) bool err error want bool }{ - "no match": {fn: errorsx.HasType[barError], err: errFoo, want: false}, - "match (exact)": {fn: errorsx.HasType[fooError], err: errFoo, want: true}, - "match (wrapped)": {fn: errorsx.HasType[fooError], err: wrap(errFoo), want: true}, - "match (interface)": {fn: errorsx.HasType[interface{ Error() string }], err: errFoo, want: true}, + "no match": {fn: func(err error) bool { return isok(errorsx.As[barError](err)) }, err: errFoo, want: false}, + "match (exact)": {fn: func(err error) bool { return isok(errorsx.As[fooError](err)) }, err: errFoo, want: true}, + "match (wrapped)": {fn: func(err error) bool { return isok(errorsx.As[fooError](err)) }, err: wrap(errFoo), want: true}, } for name, test := range tests { @@ -51,26 +51,6 @@ func TestHasType(t *testing.T) { } } -func TestSplit(t *testing.T) { - tests := map[string]struct { - err error - wantErrs []error - }{ - "nil error": {err: nil, wantErrs: nil}, - "single error": {err: errFoo, wantErrs: nil}, - "joined errors (errors.Join)": {err: errors.Join(errFoo, errBar), wantErrs: []error{errFoo, errBar}}, - "joined errors (fmt.Errorf)": {err: fmt.Errorf("%w; %w", errFoo, errBar), wantErrs: []error{errFoo, errBar}}, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - if gotErrs := errorsx.Split(test.err); !slices.Equal(gotErrs, test.wantErrs) { - t.Errorf("got %v; want %v", gotErrs, test.wantErrs) - } - }) - } -} - func TestClose(t *testing.T) { tests := map[string]struct { mainErr error @@ -112,8 +92,8 @@ type barError struct{} func (barError) Error() string { return "bar" } -func wrap(err error) error { return fmt.Errorf("%w", err) } - type errCloser struct{ err error } func (c *errCloser) Close() error { return c.err } + +func wrap(err error) error { return fmt.Errorf("%w", err) } diff --git a/example_test.go b/example_test.go index c2b7889..035f24b 100644 --- a/example_test.go +++ b/example_test.go @@ -15,21 +15,14 @@ func ExampleIsAny() { } } -func ExampleHasType() { - if errorsx.HasType[*os.PathError](err) { - fmt.Println(err) +func ExampleAs() { + if pathErr, ok := errorsx.As[*os.PathError](err); ok { + fmt.Println(pathErr.Path) } } -func ExampleSplit() { - if errs := errorsx.Split(err); errs != nil { - fmt.Println(errs) - } -} - -//nolint:errcheck // this is just an example. func ExampleClose() { - func() (err error) { + _ = func() (err error) { f, err := os.Open("file.txt") if err != nil { return err @@ -37,5 +30,5 @@ func ExampleClose() { defer errorsx.Close(f, &err) return nil - }() + } }