Skip to content
This repository was archived by the owner on Dec 22, 2025. It is now read-only.

Commit 0ecd556

Browse files
authored
feat: filter out nil errors on errutil.Chain (#16)
1 parent c6d0465 commit 0ecd556

2 files changed

Lines changed: 105 additions & 22 deletions

File tree

errutil/error.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ func (e Error) Error() string {
2828
//
2929
// An empty list of errors will return a nil error.
3030
func Chain(errs ...error) error {
31-
// TODO(katcipis): should we do something when
32-
// in the middle of the errs slice we have nils ?
33-
// prone to filtering nils out, or they will break the chain anyway.
31+
errs = removeNils(errs)
32+
3433
if len(errs) == 0 {
3534
return nil
3635
}
36+
3737
return errorChain{
3838
head: errs[0],
3939
tail: Chain(errs[1:]...),
@@ -47,9 +47,6 @@ type errorChain struct {
4747

4848
// Error return a string representation of the chain of errors.
4949
func (e errorChain) Error() string {
50-
if e.head == nil {
51-
return ""
52-
}
5350
if e.tail == nil {
5451
return e.head.Error()
5552
}
@@ -67,3 +64,13 @@ func (e errorChain) Is(target error) bool {
6764
func (e errorChain) As(target interface{}) bool {
6865
return errors.As(e.head, target)
6966
}
67+
68+
func removeNils(errs []error) []error {
69+
res := make([]error, 0, len(errs))
70+
for _, err := range errs {
71+
if err != nil {
72+
res = append(res, err)
73+
}
74+
}
75+
return res
76+
}

errutil/error_test.go

Lines changed: 92 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,29 +36,100 @@ func TestErrorRepresentation(t *testing.T) {
3636
}
3737

3838
func TestErrorChain(t *testing.T) {
39-
testcases := [][]error{
40-
[]error{errors.New("single error")},
41-
[]error{
42-
errors.New("top error"),
43-
errors.New("wrapped error 1"),
39+
type testcase struct {
40+
name string
41+
errs []error
42+
want []error
43+
}
44+
45+
const (
46+
sentinelErr errutil.Error = "a sentinel error"
47+
sentinel2Err errutil.Error = "another sentinel error"
48+
sentinel3Err errutil.Error = "YASE"
49+
)
50+
51+
testcases := []testcase{
52+
{
53+
name: "single error",
54+
errs: []error{errors.New("single error")},
55+
},
56+
{
57+
name: "two errors",
58+
errs: []error{
59+
errors.New("top error"),
60+
errors.New("wrapped error 1"),
61+
},
4462
},
45-
[]error{
46-
errors.New("top error"),
47-
errors.New("wrapped error 1"),
48-
errors.New("wrapped error 2"),
63+
{
64+
name: "three errors",
65+
errs: []error{
66+
errors.New("top error"),
67+
errors.New("wrapped error 1"),
68+
errors.New("wrapped error 2"),
69+
},
70+
},
71+
{
72+
name: "errors is nil and err",
73+
errs: []error{
74+
nil,
75+
sentinelErr,
76+
},
77+
want: []error{
78+
sentinelErr,
79+
},
80+
},
81+
{
82+
name: "errors is err and nil",
83+
errs: []error{
84+
sentinelErr,
85+
nil,
86+
},
87+
want: []error{
88+
sentinelErr,
89+
},
90+
},
91+
{
92+
name: "errors is nil,err,nil",
93+
errs: []error{
94+
nil,
95+
sentinelErr,
96+
nil,
97+
},
98+
want: []error{
99+
sentinelErr,
100+
},
101+
},
102+
{
103+
name: "errors interleaved with nils",
104+
errs: []error{
105+
sentinelErr,
106+
nil,
107+
sentinel2Err,
108+
nil,
109+
sentinel3Err,
110+
nil,
111+
},
112+
want: []error{
113+
sentinelErr,
114+
sentinel2Err,
115+
sentinel3Err,
116+
},
49117
},
50118
}
51119

52-
for _, errs := range testcases {
53-
54-
name := fmt.Sprintf("%dErrors", len(errs))
55-
t.Run(name, func(t *testing.T) {
120+
for _, tc := range testcases {
121+
t.Run(tc.name, func(t *testing.T) {
56122

57-
err := errutil.Chain(errs...)
123+
err := errutil.Chain(tc.errs...)
58124
assert.Error(t, err)
59125

60126
got := err
61-
for i, want := range errs {
127+
128+
if tc.want == nil {
129+
tc.want = tc.errs
130+
}
131+
132+
for i, want := range tc.want {
62133
if got == nil {
63134
t.Fatal("expected error to exist, got nil")
64135
}
@@ -160,12 +231,17 @@ func TestErrorChainTypeSelection(t *testing.T) {
160231
}
161232
}
162233

163-
func TestErrorChainForEmptyErrList(t *testing.T) {
234+
func TestErrorChainForEmptyErrListIsNil(t *testing.T) {
164235
assert.NoError(t, errutil.Chain())
165236
errs := []error{}
166237
assert.NoError(t, errutil.Chain(errs...))
167238
}
168239

240+
func TestErrorChainWithOnlyNilErrorsIsNil(t *testing.T) {
241+
assert.NoError(t, errutil.Chain(nil))
242+
assert.NoError(t, errutil.Chain(nil, nil))
243+
}
244+
169245
func TestErrorChainRespectIsMethodOfChainedErrors(t *testing.T) {
170246
var neverIs errorThatNeverIs
171247

0 commit comments

Comments
 (0)