Skip to content

Commit 2fb1006

Browse files
authored
Merge pull request #118 from dhartunian/go-1.20-join
Add Go 1.20 errors.Join support
2 parents f0a2a69 + 89c9f9e commit 2fb1006

16 files changed

+2449
-0
lines changed

errutil/utilities.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package errutil
1616

1717
import (
18+
"github.com/cockroachdb/errors/join"
1819
"github.com/cockroachdb/errors/secondary"
1920
"github.com/cockroachdb/errors/withstack"
2021
"github.com/cockroachdb/redact"
@@ -158,3 +159,10 @@ func WrapWithDepthf(depth int, err error, format string, args ...interface{}) er
158159
err = withstack.WithStackDepth(err, depth+1)
159160
return err
160161
}
162+
163+
// JoinWithDepth constructs a Join error with the provided list of
164+
// errors as arguments, and wraps it in a `WithStackDepth` to capture a
165+
// stacktrace alongside.
166+
func JoinWithDepth(depth int, errs ...error) error {
167+
return withstack.WithStackDepth(join.Join(errs...), depth+1)
168+
}

errutil_api.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,3 +192,19 @@ func HandleAsAssertionFailureDepth(depth int, origErr error) error {
192192
// - it also supports recursing through causes with Cause().
193193
// - if it detects an API use error, its panic object is a valid error.
194194
func As(err error, target interface{}) bool { return errutil.As(err, target) }
195+
196+
// Join returns an error that wraps the given errors.
197+
// Any nil error values are discarded.
198+
// Join returns nil if errs contains no non-nil values.
199+
// The error formats as the concatenation of the strings obtained
200+
// by calling the Error method of each element of errs, with a newline
201+
// between each string. A stack trace is also retained.
202+
func Join(errs ...error) error {
203+
return errutil.JoinWithDepth(1, errs...)
204+
}
205+
206+
// JoinWithDepth is like Join but the depth at which the call stack is
207+
// captured can be specified.
208+
func JoinWithDepth(depth int, errs ...error) error {
209+
return errutil.JoinWithDepth(depth+1, errs...)
210+
}

errutil_api_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package errors_test
22

33
import (
44
"fmt"
5+
"strings"
56
"testing"
67

78
"github.com/cockroachdb/errors"
@@ -17,3 +18,14 @@ func TestUnwrap(t *testing.T) {
1718
// (per API documentation)
1819
tt.Check(errors.Unwrap(e) == nil)
1920
}
21+
22+
// More detailed testing of Join is in datadriven_test.go. Here we make
23+
// sure that the public API includes the stacktrace wrapper.
24+
func TestJoin(t *testing.T) {
25+
e := errors.Join(errors.New("abc123"), errors.New("def456"))
26+
printed := fmt.Sprintf("%+v", e)
27+
expected := `Error types: (1) *withstack.withStack (2) *join.joinError (3) *withstack.withStack (4) *errutil.leafError (5) *withstack.withStack (6) *errutil.leafError`
28+
if !strings.Contains(printed, expected) {
29+
t.Errorf("Expected: %s to contain: %s", printed, expected)
30+
}
31+
}

fmttests/datadriven_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
"github.com/cockroachdb/errors/errutil"
3838
"github.com/cockroachdb/errors/hintdetail"
3939
"github.com/cockroachdb/errors/issuelink"
40+
"github.com/cockroachdb/errors/join"
4041
"github.com/cockroachdb/errors/report"
4142
"github.com/cockroachdb/errors/safedetails"
4243
"github.com/cockroachdb/errors/secondary"
@@ -276,6 +277,9 @@ var wrapCommands = map[string]commandFn{
276277
ctx = logtags.AddTag(ctx, "safe", redact.Safe(456))
277278
return contexttags.WithContextTags(err, ctx)
278279
},
280+
"join": func(err error, args []arg) error {
281+
return join.Join(err, errutil.New(strfy(args)))
282+
},
279283
}
280284

281285
var noPrefixWrappers = map[string]bool{
@@ -300,6 +304,7 @@ var noPrefixWrappers = map[string]bool{
300304
"stack": true,
301305
"tags": true,
302306
"telemetry": true,
307+
"join": true,
303308
}
304309

305310
var wrapOnlyExceptions = map[string]string{}
@@ -329,6 +334,7 @@ func init() {
329334
// means they don't match.
330335
"nofmt",
331336
"errorf",
337+
"join",
332338
} {
333339
wrapOnlyExceptions[v] = `
334340
accept %\+v via Formattable.*IRREGULAR: not same as %\+v

fmttests/testdata/format/wrap-fmt

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2251,6 +2251,197 @@ Type: "*fmttests.errFmt"
22512251
Title: "×"
22522252
(NO STACKTRACE)
22532253

2254+
run
2255+
fmt innerone innertwo
2256+
join outerthree outerfour
2257+
2258+
accept %\+v via Formattable.*IRREGULAR: not same as %\+v
2259+
accept %\#v via Formattable.*IRREGULAR: not same as %\#v
2260+
2261+
require (?s)
2262+
----
2263+
&join.joinError{
2264+
errs: {
2265+
&fmttests.errFmt{msg:"innerone\ninnertwo"},
2266+
&withstack.withStack{
2267+
cause: &errutil.leafError{msg:"outerthree\nouterfour"},
2268+
stack: &stack{...},
2269+
},
2270+
},
2271+
}
2272+
=====
2273+
===== non-redactable formats
2274+
=====
2275+
== %#v
2276+
&join.joinError{
2277+
errs: {
2278+
&fmttests.errFmt{msg:"innerone\ninnertwo"},
2279+
&withstack.withStack{
2280+
cause: &errutil.leafError{msg:"outerthree\nouterfour"},
2281+
stack: &stack{...},
2282+
},
2283+
},
2284+
}
2285+
== Error()
2286+
innerone
2287+
innertwo
2288+
outerthree
2289+
outerfour
2290+
== %v = Error(), good
2291+
== %s = Error(), good
2292+
== %q = quoted Error(), good
2293+
== %x = hex Error(), good
2294+
== %X = HEX Error(), good
2295+
== %+v
2296+
innerone
2297+
(1) innerone
2298+
| innertwo
2299+
| outerthree
2300+
| outerfour
2301+
Wraps: (2) attached stack trace
2302+
-- stack trace:
2303+
| github.com/cockroachdb/errors/fmttests.glob...funcNN...
2304+
| <tab><path>:<lineno>
2305+
| github.com/cockroachdb/errors/fmttests.TestDatadriven.func2.1
2306+
| <tab><path>:<lineno>
2307+
| github.com/cockroachdb/datadriven.runDirective.func1
2308+
| <tab><path>:<lineno>
2309+
| github.com/cockroachdb/datadriven.runDirective
2310+
| <tab><path>:<lineno>
2311+
| github.com/cockroachdb/datadriven.runDirectiveOrSubTest
2312+
| <tab><path>:<lineno>
2313+
| github.com/cockroachdb/datadriven.runTestInternal
2314+
| <tab><path>:<lineno>
2315+
| github.com/cockroachdb/datadriven.RunTest
2316+
| <tab><path>:<lineno>
2317+
| github.com/cockroachdb/errors/fmttests.TestDatadriven.func2
2318+
| <tab><path>:<lineno>
2319+
| github.com/cockroachdb/datadriven.Walk
2320+
| <tab><path>:<lineno>
2321+
| github.com/cockroachdb/datadriven.Walk.func1
2322+
| <tab><path>:<lineno>
2323+
| testing.tRunner
2324+
| <tab><path>:<lineno>
2325+
| runtime.goexit
2326+
| <tab><path>:<lineno>
2327+
└─ Wraps: (3) outerthree
2328+
| outerfour
2329+
Wraps: (4) innerone
2330+
| innertwo
2331+
| -- this is innerone
2332+
| innertwo's
2333+
| multi-line leaf payload
2334+
Error types: (1) *join.joinError (2) *withstack.withStack (3) *errutil.leafError (4) *fmttests.errFmt
2335+
== %#v via Formattable() = %#v, good
2336+
== %v via Formattable() = Error(), good
2337+
== %s via Formattable() = %v via Formattable(), good
2338+
== %q via Formattable() = quoted %v via Formattable(), good
2339+
== %+v via Formattable() == %+v, good
2340+
=====
2341+
===== redactable formats
2342+
=====
2343+
== printed via redact Print(), ok - congruent with %v
2344+
‹innerone›
2345+
‹innertwo›
2346+
outerthree
2347+
outerfour
2348+
== printed via redact Printf() %v = Print(), good
2349+
== printed via redact Printf() %s = Print(), good
2350+
== printed via redact Printf() %q, refused - good
2351+
== printed via redact Printf() %x, refused - good
2352+
== printed via redact Printf() %X, refused - good
2353+
== printed via redact Printf() %+v, ok - congruent with %+v
2354+
‹innerone›
2355+
(1) ‹innerone›
2356+
| ‹innertwo›
2357+
| outerthree
2358+
| outerfour
2359+
Wraps: (2) attached stack trace
2360+
-- stack trace:
2361+
| github.com/cockroachdb/errors/fmttests.glob...funcNN...
2362+
| <tab><path>:<lineno>
2363+
| github.com/cockroachdb/errors/fmttests.TestDatadriven.func2.1
2364+
| <tab><path>:<lineno>
2365+
| github.com/cockroachdb/datadriven.runDirective.func1
2366+
| <tab><path>:<lineno>
2367+
| github.com/cockroachdb/datadriven.runDirective
2368+
| <tab><path>:<lineno>
2369+
| github.com/cockroachdb/datadriven.runDirectiveOrSubTest
2370+
| <tab><path>:<lineno>
2371+
| github.com/cockroachdb/datadriven.runTestInternal
2372+
| <tab><path>:<lineno>
2373+
| github.com/cockroachdb/datadriven.RunTest
2374+
| <tab><path>:<lineno>
2375+
| github.com/cockroachdb/errors/fmttests.TestDatadriven.func2
2376+
| <tab><path>:<lineno>
2377+
| github.com/cockroachdb/datadriven.Walk
2378+
| <tab><path>:<lineno>
2379+
| github.com/cockroachdb/datadriven.Walk.func1
2380+
| <tab><path>:<lineno>
2381+
| testing.tRunner
2382+
| <tab><path>:<lineno>
2383+
| runtime.goexit
2384+
| <tab><path>:<lineno>
2385+
└─ Wraps: (3) outerthree
2386+
| outerfour
2387+
Wraps: (4) ‹innerone›
2388+
‹ | innertwo›
2389+
‹ | -- this is innerone›
2390+
‹ | innertwo's›
2391+
‹ | multi-line leaf payload›
2392+
Error types: (1) *join.joinError (2) *withstack.withStack (3) *errutil.leafError (4) *fmttests.errFmt
2393+
=====
2394+
===== Sentry reporting
2395+
=====
2396+
== Message payload
2397+
×
2398+
(1) ×
2399+
| ×
2400+
| outerthree
2401+
| outerfour
2402+
Wraps: (2) attached stack trace
2403+
-- stack trace:
2404+
| github.com/cockroachdb/errors/fmttests.glob...funcNN...
2405+
| <tab><path>:<lineno>
2406+
| github.com/cockroachdb/errors/fmttests.TestDatadriven.func2.1
2407+
| <tab><path>:<lineno>
2408+
| github.com/cockroachdb/datadriven.runDirective.func1
2409+
| <tab><path>:<lineno>
2410+
| github.com/cockroachdb/datadriven.runDirective
2411+
| <tab><path>:<lineno>
2412+
| github.com/cockroachdb/datadriven.runDirectiveOrSubTest
2413+
| <tab><path>:<lineno>
2414+
| github.com/cockroachdb/datadriven.runTestInternal
2415+
| <tab><path>:<lineno>
2416+
| github.com/cockroachdb/datadriven.RunTest
2417+
| <tab><path>:<lineno>
2418+
| github.com/cockroachdb/errors/fmttests.TestDatadriven.func2
2419+
| <tab><path>:<lineno>
2420+
| github.com/cockroachdb/datadriven.Walk
2421+
| <tab><path>:<lineno>
2422+
| github.com/cockroachdb/datadriven.Walk.func1
2423+
| <tab><path>:<lineno>
2424+
| testing.tRunner
2425+
| <tab><path>:<lineno>
2426+
| runtime.goexit
2427+
| <tab><path>:<lineno>
2428+
└─ Wraps: (3) outerthree
2429+
| outerfour
2430+
Wraps: (4) ×
2431+
×
2432+
×
2433+
×
2434+
×
2435+
Error types: (1) *join.joinError (2) *withstack.withStack (3) *errutil.leafError (4) *fmttests.errFmt
2436+
-- report composition:
2437+
*join.joinError
2438+
== Extra "error types"
2439+
github.com/cockroachdb/errors/join/*join.joinError (*::)
2440+
== Exception 1 (Module: "error domain: <none>")
2441+
Type: "*join.joinError"
2442+
Title: "×"
2443+
(NO STACKTRACE)
2444+
22542445
run
22552446
fmt innerone innertwo
22562447
migrated outerthree outerfour

0 commit comments

Comments
 (0)