Skip to content

Commit a5133c6

Browse files
committed
feat: add Join and AsType standard library shortcuts
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
1 parent 05fbace commit a5133c6

File tree

4 files changed

+95
-0
lines changed

4 files changed

+95
-0
lines changed

astype.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//go:build !go1.26
2+
3+
package errors
4+
5+
// AsType finds the first error in err's chain that matches type E,
6+
// and if so, returns that error value and true.
7+
func AsType[E error](err error) (E, bool) {
8+
var target E
9+
return target, As(err, &target)
10+
}

astype_go126.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//go:build go1.26
2+
3+
package errors
4+
5+
import (
6+
stderrors "errors"
7+
)
8+
9+
// AsType finds the first error in err's chain that matches type E,
10+
// and if so, returns that error value and true.
11+
func AsType[E error](err error) (E, bool) {
12+
return stderrors.AsType[E](err)
13+
}

unwrap.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,11 @@ func As(err error, target interface{}) bool { return stderrors.As(err, target) }
3434
func Unwrap(err error) error {
3535
return stderrors.Unwrap(err)
3636
}
37+
38+
// Join returns an error that wraps the given errors.
39+
// Any nil error values are discarded.
40+
// Join returns nil if every value in errs is nil.
41+
// The error formats each wrapped error, separated by newlines.
42+
func Join(errs ...error) error {
43+
return stderrors.Join(errs...)
44+
}

unwrap_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package errors
33
import (
44
stderrors "errors"
55
"fmt"
6+
"io"
67
"reflect"
78
"testing"
89
)
@@ -174,3 +175,66 @@ func TestUnwrap(t *testing.T) {
174175
})
175176
}
176177
}
178+
179+
func TestJoin(t *testing.T) {
180+
err1 := New("err1")
181+
err2 := New("err2")
182+
183+
tests := []struct {
184+
name string
185+
errs []error
186+
want string
187+
}{
188+
{
189+
name: "two errors",
190+
errs: []error{err1, err2},
191+
want: "err1\nerr2",
192+
},
193+
{
194+
name: "nil filtered",
195+
errs: []error{err1, nil, err2},
196+
want: "err1\nerr2",
197+
},
198+
}
199+
for _, tt := range tests {
200+
t.Run(tt.name, func(t *testing.T) {
201+
err := Join(tt.errs...)
202+
if err == nil {
203+
t.Fatal("Join() = nil, want non-nil")
204+
}
205+
if got := err.Error(); got != tt.want {
206+
t.Errorf("Join().Error() = %q, want %q", got, tt.want)
207+
}
208+
})
209+
}
210+
}
211+
212+
func TestJoinNil(t *testing.T) {
213+
if err := Join(); err != nil {
214+
t.Errorf("Join() = %v, want nil", err)
215+
}
216+
if err := Join(nil, nil); err != nil {
217+
t.Errorf("Join(nil, nil) = %v, want nil", err)
218+
}
219+
}
220+
221+
func TestAsType(t *testing.T) {
222+
err := customErr{msg: "test"}
223+
wrapped := Wrap(err, "wrapped")
224+
225+
got, ok := AsType[customErr](wrapped)
226+
if !ok {
227+
t.Fatal("AsType[customErr]() = false, want true")
228+
}
229+
if got != err {
230+
t.Errorf("AsType[customErr]() = %v, want %v", got, err)
231+
}
232+
}
233+
234+
func TestAsTypeNotFound(t *testing.T) {
235+
err := io.EOF
236+
_, ok := AsType[customErr](err)
237+
if ok {
238+
t.Error("AsType[customErr](io.EOF) = true, want false")
239+
}
240+
}

0 commit comments

Comments
 (0)