Skip to content

Commit

Permalink
feat: filter out nil errors on errutil.Chain (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
katcipis authored Nov 29, 2021
1 parent c6d0465 commit 0ecd556
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 22 deletions.
19 changes: 13 additions & 6 deletions errutil/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ func (e Error) Error() string {
//
// An empty list of errors will return a nil error.
func Chain(errs ...error) error {
// TODO(katcipis): should we do something when
// in the middle of the errs slice we have nils ?
// prone to filtering nils out, or they will break the chain anyway.
errs = removeNils(errs)

if len(errs) == 0 {
return nil
}

return errorChain{
head: errs[0],
tail: Chain(errs[1:]...),
Expand All @@ -47,9 +47,6 @@ type errorChain struct {

// Error return a string representation of the chain of errors.
func (e errorChain) Error() string {
if e.head == nil {
return ""
}
if e.tail == nil {
return e.head.Error()
}
Expand All @@ -67,3 +64,13 @@ func (e errorChain) Is(target error) bool {
func (e errorChain) As(target interface{}) bool {
return errors.As(e.head, target)
}

func removeNils(errs []error) []error {
res := make([]error, 0, len(errs))
for _, err := range errs {
if err != nil {
res = append(res, err)
}
}
return res
}
108 changes: 92 additions & 16 deletions errutil/error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,29 +36,100 @@ func TestErrorRepresentation(t *testing.T) {
}

func TestErrorChain(t *testing.T) {
testcases := [][]error{
[]error{errors.New("single error")},
[]error{
errors.New("top error"),
errors.New("wrapped error 1"),
type testcase struct {
name string
errs []error
want []error
}

const (
sentinelErr errutil.Error = "a sentinel error"
sentinel2Err errutil.Error = "another sentinel error"
sentinel3Err errutil.Error = "YASE"
)

testcases := []testcase{
{
name: "single error",
errs: []error{errors.New("single error")},
},
{
name: "two errors",
errs: []error{
errors.New("top error"),
errors.New("wrapped error 1"),
},
},
[]error{
errors.New("top error"),
errors.New("wrapped error 1"),
errors.New("wrapped error 2"),
{
name: "three errors",
errs: []error{
errors.New("top error"),
errors.New("wrapped error 1"),
errors.New("wrapped error 2"),
},
},
{
name: "errors is nil and err",
errs: []error{
nil,
sentinelErr,
},
want: []error{
sentinelErr,
},
},
{
name: "errors is err and nil",
errs: []error{
sentinelErr,
nil,
},
want: []error{
sentinelErr,
},
},
{
name: "errors is nil,err,nil",
errs: []error{
nil,
sentinelErr,
nil,
},
want: []error{
sentinelErr,
},
},
{
name: "errors interleaved with nils",
errs: []error{
sentinelErr,
nil,
sentinel2Err,
nil,
sentinel3Err,
nil,
},
want: []error{
sentinelErr,
sentinel2Err,
sentinel3Err,
},
},
}

for _, errs := range testcases {

name := fmt.Sprintf("%dErrors", len(errs))
t.Run(name, func(t *testing.T) {
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {

err := errutil.Chain(errs...)
err := errutil.Chain(tc.errs...)
assert.Error(t, err)

got := err
for i, want := range errs {

if tc.want == nil {
tc.want = tc.errs
}

for i, want := range tc.want {
if got == nil {
t.Fatal("expected error to exist, got nil")
}
Expand Down Expand Up @@ -160,12 +231,17 @@ func TestErrorChainTypeSelection(t *testing.T) {
}
}

func TestErrorChainForEmptyErrList(t *testing.T) {
func TestErrorChainForEmptyErrListIsNil(t *testing.T) {
assert.NoError(t, errutil.Chain())
errs := []error{}
assert.NoError(t, errutil.Chain(errs...))
}

func TestErrorChainWithOnlyNilErrorsIsNil(t *testing.T) {
assert.NoError(t, errutil.Chain(nil))
assert.NoError(t, errutil.Chain(nil, nil))
}

func TestErrorChainRespectIsMethodOfChainedErrors(t *testing.T) {
var neverIs errorThatNeverIs

Expand Down

0 comments on commit 0ecd556

Please sign in to comment.