Skip to content

Commit

Permalink
Support multierr.Every (#78)
Browse files Browse the repository at this point in the history
This PR introduces a new exported function in the multierr package
called `Every`, which checks every error within a given error against a
given target using `errors.Is`, and only returns true if all checks
return true.

```go
err := multierr.Combine(ErrIgnorable, errors.New("great sadness"))
fmt.Println(errors.Is(err, ErrIgnorable))      // output = "true"
fmt.Println(multierr.Every(err, ErrIgnorable)) // output = "false"

err := multierr.Combine(ErrIgnorable, ErrIgnorable)
fmt.Println(errors.Is(err, ErrIgnorable))      // output = "true"
fmt.Println(multierr.Every(err, ErrIgnorable)) // output = "true"
```

This also works when the error passed in as the first argument is not a
`multiErr`, including when it is a go1.20+ error that implements
`Unwrap() []error`.

This solves #66.
  • Loading branch information
JacobOaks authored Mar 28, 2023
1 parent d42b7a1 commit d8067ab
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 0 deletions.
12 changes: 12 additions & 0 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ package multierr // import "go.uber.org/multierr"

import (
"bytes"
"errors"
"fmt"
"io"
"strings"
Expand Down Expand Up @@ -234,6 +235,17 @@ func (merr *multiError) Error() string {
return result
}

// Every compares every error in the given err against the given target error
// using [errors.Is], and returns true only if every comparison returned true.
func Every(err error, target error) bool {
for _, e := range extractErrors(err) {
if !errors.Is(e, target) {
return false
}
}
return true
}

func (merr *multiError) Format(f fmt.State, c rune) {
if c == 'v' && f.Flag('+') {
merr.writeMultiline(f)
Expand Down
23 changes: 23 additions & 0 deletions error_post_go120_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,26 @@ func TestErrorsOnErrorsJoin(t *testing.T) {
assert.Equal(t, err1, errs[0])
assert.Equal(t, err2, errs[1])
}

func TestEveryWithErrorsJoin(t *testing.T) {
myError1 := errors.New("woeful misfortune")
myError2 := errors.New("worrisome travesty")

t.Run("all match", func(t *testing.T) {
err := errors.Join(myError1, myError1, myError1)

assert.True(t, errors.Is(err, myError1))
assert.True(t, Every(err, myError1))
assert.False(t, errors.Is(err, myError2))
assert.False(t, Every(err, myError2))
})

t.Run("one matches", func(t *testing.T) {
err := errors.Join(myError1, myError2)

assert.True(t, errors.Is(err, myError1))
assert.False(t, Every(err, myError1))
assert.True(t, errors.Is(err, myError2))
assert.False(t, Every(err, myError2))
})
}
61 changes: 61 additions & 0 deletions error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,67 @@ func newMultiErr(errors ...error) error {
return &multiError{errors: errors}
}

func TestEvery(t *testing.T) {
myError1 := errors.New("woeful misfortune")
myError2 := errors.New("worrisome travesty")

for _, tt := range []struct {
desc string
giveErr error
giveTarget error
wantIs bool
wantEvery bool
}{
{
desc: "all match",
giveErr: newMultiErr(myError1, myError1, myError1),
giveTarget: myError1,
wantIs: true,
wantEvery: true,
},
{
desc: "one matches",
giveErr: newMultiErr(myError1, myError2),
giveTarget: myError1,
wantIs: true,
wantEvery: false,
},
{
desc: "not multiErrs and non equal",
giveErr: myError1,
giveTarget: myError2,
wantIs: false,
wantEvery: false,
},
{
desc: "not multiErrs but equal",
giveErr: myError1,
giveTarget: myError1,
wantIs: true,
wantEvery: true,
},
{
desc: "not multiErr w multiErr target",
giveErr: myError1,
giveTarget: newMultiErr(myError1, myError1),
wantIs: false,
wantEvery: false,
},
{
desc: "multiErr w multiErr target",
giveErr: newMultiErr(myError1, myError1),
giveTarget: newMultiErr(myError1, myError1),
wantIs: false,
wantEvery: false,
},
} {
t.Run(tt.desc, func(t *testing.T) {
assert.Equal(t, tt.wantIs, errors.Is(tt.giveErr, tt.giveTarget))
assert.Equal(t, tt.wantEvery, Every(tt.giveErr, tt.giveTarget))
})
}
}

func TestCombine(t *testing.T) {
tests := []struct {
// Input
Expand Down

0 comments on commit d8067ab

Please sign in to comment.