Skip to content

Commit 67d1190

Browse files
committed
errors: clarify that Join doesn't extend already-joined errors
Updates the documentation for Join to explain that it does not append to an existing joinError (i.e. the result of calling Join), and if one of the errors is a joinError, it will be wrapped again. See #60209 It also adds a test to provide an example of said behavior, as well as formalizing it in the API's contract.
1 parent e0ceba8 commit 67d1190

File tree

2 files changed

+50
-0
lines changed

2 files changed

+50
-0
lines changed

src/errors/join.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ package errors
1212
// between each string.
1313
//
1414
// A non-nil error returned by Join implements the Unwrap() []error method.
15+
//
16+
// Calling Join on an error that was previously returned by Join wraps
17+
// the error again. Consequently, calling Unwrap() []error on the result
18+
// of Join(Join(err1, err2), err3) returns a slice with 2 items, of which
19+
// the first one implements Unwrap() []error too.
1520
func Join(errs ...error) error {
1621
n := 0
1722
for _, err := range errs {

src/errors/join_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,48 @@ func TestJoinErrorMethod(t *testing.T) {
7070
}
7171
}
7272
}
73+
74+
func TestJoinWithJoinedError(t *testing.T) {
75+
err1 := errors.New("err1")
76+
err2 := errors.New("err2")
77+
78+
var err error
79+
err = errors.Join(err, err1)
80+
if err == nil {
81+
t.Fatal("errors.Join(err, err1) = nil, want non-nil")
82+
}
83+
84+
gotErrs := err.(interface{ Unwrap() []error }).Unwrap()
85+
if len(gotErrs) != 1 {
86+
t.Fatalf("errors.Join(err, err1) returns errors with len=%v, want len==1", len(gotErrs))
87+
}
88+
89+
err = errors.Join(err, err2)
90+
if err == nil {
91+
t.Fatal("errors.Join(err, err2) = nil, want non-nil")
92+
}
93+
94+
gotErrs = err.(interface{ Unwrap() []error }).Unwrap()
95+
if len(gotErrs) != 2 {
96+
t.Fatalf("errors.Join(err, err2) returns errors with len=%v, want len==2", len(gotErrs))
97+
}
98+
99+
// Wraps the error again, so the resulting joined error will have len==1
100+
err = errors.Join(err, nil)
101+
if err == nil {
102+
t.Fatal("errors.Join(err, nil) = nil, want non-nil")
103+
}
104+
105+
gotErrs = err.(interface{ Unwrap() []error }).Unwrap()
106+
if len(gotErrs) != 1 {
107+
t.Fatalf("errors.Join(err, nil) returns errors with len=%v, want len==1", len(gotErrs))
108+
}
109+
110+
if err.Error() != "err1\nerr2" {
111+
t.Errorf("Join(err, nil).Error() = %q; want %q", err.Error(), "err1\nerr2")
112+
}
113+
114+
if _, ok := gotErrs[0].(interface{ Unwrap() []error }); !ok {
115+
t.Error("first error returned by errors.Join(err, nil) is not a joined error")
116+
}
117+
}

0 commit comments

Comments
 (0)