Skip to content

Commit ca59e56

Browse files
authored
Merge pull request #121 from cockroachdb/go-1.20-upgrade
Upgrade to go 1.20 and add multi-cause error support
2 parents 5197958 + 3a3abac commit ca59e56

37 files changed

+17764
-2282
lines changed

.github/workflows/ci.yaml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,8 @@ jobs:
1313
strategy:
1414
matrix:
1515
go:
16-
- "1.17"
17-
- "1.18"
18-
- "1.19"
1916
- "1.20"
17+
- "1.21"
2018
steps:
2119
- uses: actions/checkout@v2
2220

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,8 @@ Example use:
536536
| `WrapWithDepthf` | `WithMessagef` + `WithStackDepth` |
537537
| `AssertionFailedWithDepthf` | `NewWithDepthf` + `WithAssertionFailure` |
538538
| `NewAssertionErrorWithWrappedErrf` | `HandledWithMessagef` (barrier) + `WrapWithDepthf` + `WithAssertionFailure` |
539-
539+
| `Join` | `JoinWithDepth` (see below) |
540+
| `JoinWithDepth` | multi-cause wrapper + `WithStackDepth` |
540541
## API (not constructing error objects)
541542

542543
The following is a summary of the non-constructor API functions, grouped by category.
@@ -574,11 +575,15 @@ func RegisterLeafEncoder(typeName TypeKey, encoder LeafEncoder)
574575
func RegisterWrapperDecoder(typeName TypeKey, decoder WrapperDecoder)
575576
func RegisterWrapperEncoder(typeName TypeKey, encoder WrapperEncoder)
576577
func RegisterWrapperEncoderWithMessageOverride (typeName TypeKey, encoder WrapperEncoderWithMessageOverride)
578+
func RegisterMultiCauseEncoder(theType TypeKey, encoder MultiCauseEncoder)
579+
func RegisterMultiCauseDecoder(theType TypeKey, decoder MultiCauseDecoder)
577580
type LeafEncoder = func(ctx context.Context, err error) (msg string, safeDetails []string, payload proto.Message)
578581
type LeafDecoder = func(ctx context.Context, msg string, safeDetails []string, payload proto.Message) error
579582
type WrapperEncoder = func(ctx context.Context, err error) (msgPrefix string, safeDetails []string, payload proto.Message)
580583
type WrapperEncoderWithMessageOverride = func(ctx context.Context, err error) (msgPrefix string, safeDetails []string, payload proto.Message, overrideError bool)
581584
type WrapperDecoder = func(ctx context.Context, cause error, msgPrefix string, safeDetails []string, payload proto.Message) error
585+
type MultiCauseEncoder = func(ctx context.Context, err error) (msg string, safeDetails []string, payload proto.Message)
586+
type MultiCauseDecoder = func(ctx context.Context, causes []error, msgPrefix string, safeDetails []string, payload proto.Message) error
582587
583588
// Registering package renames for custom error types.
584589
func RegisterTypeMigration(previousPkgPath, previousTypeName string, newType error)

errbase/adapters_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,33 @@ func TestAdaptGoSingleWrapErr(t *testing.T) {
6464
tt := testutils.T{T: t}
6565
// The library preserves the cause. It's not possible to preserve the fmt
6666
// string.
67+
tt.CheckEqual(newErr.Error(), origErr.Error())
68+
tt.CheckContains(newErr.Error(), "hello")
69+
}
70+
71+
func TestAdaptBaseGoJoinErr(t *testing.T) {
72+
origErr := goErr.Join(goErr.New("hello"), goErr.New("world"))
73+
t.Logf("start err: %# v", pretty.Formatter(origErr))
74+
75+
newErr := network(t, origErr)
76+
77+
tt := testutils.T{T: t}
78+
// The library preserves the error message.
79+
tt.CheckEqual(newErr.Error(), origErr.Error())
80+
81+
}
82+
83+
func TestAdaptGoMultiWrapErr(t *testing.T) {
84+
origErr := fmt.Errorf("an error %w and also %w", goErr.New("hello"), goErr.New("world"))
85+
t.Logf("start err: %# v", pretty.Formatter(origErr))
86+
87+
newErr := network(t, origErr)
88+
89+
tt := testutils.T{T: t}
90+
// The library preserves the causes. It's not possible to preserve the fmt string.
91+
tt.CheckEqual(newErr.Error(), origErr.Error())
6792
tt.CheckContains(newErr.Error(), "hello")
93+
tt.CheckContains(newErr.Error(), "world")
6894
}
6995

7096
func TestAdaptPkgWithMessage(t *testing.T) {

errbase/decode.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ func decodeLeaf(ctx context.Context, enc *errorspb.EncodedErrorLeaf) error {
5757
return genErr
5858
}
5959
// Decoding failed, we'll drop through to opaqueLeaf{} below.
60+
} else if decoder, ok := multiCauseDecoders[typeKey]; ok {
61+
causes := make([]error, len(enc.MultierrorCauses))
62+
for i, e := range enc.MultierrorCauses {
63+
causes[i] = DecodeError(ctx, *e)
64+
}
65+
genErr := decoder(ctx, causes, enc.Message, enc.Details.ReportablePayload, payload)
66+
if genErr != nil {
67+
return genErr
68+
}
6069
} else {
6170
// Shortcut for non-registered proto-encodable error types:
6271
// if it already implements `error`, it's good to go.
@@ -66,6 +75,19 @@ func decodeLeaf(ctx context.Context, enc *errorspb.EncodedErrorLeaf) error {
6675
}
6776
}
6877

78+
if len(enc.MultierrorCauses) > 0 {
79+
causes := make([]error, len(enc.MultierrorCauses))
80+
for i, e := range enc.MultierrorCauses {
81+
causes[i] = DecodeError(ctx, *e)
82+
}
83+
leaf := &opaqueLeafCauses{
84+
causes: causes,
85+
}
86+
leaf.msg = enc.Message
87+
leaf.details = enc.Details
88+
return leaf
89+
}
90+
6991
// No decoder and no error type: we'll keep what we received and
7092
// make it ready to re-encode exactly (if the error leaves over the
7193
// network again).
@@ -161,3 +183,24 @@ type WrapperDecoder = func(ctx context.Context, cause error, msgPrefix string, s
161183

162184
// registry for RegisterWrapperType.
163185
var decoders = map[TypeKey]WrapperDecoder{}
186+
187+
// MultiCauseDecoder is to be provided (via RegisterMultiCauseDecoder
188+
// above) by additional multi-cause wrapper types not yet known by the
189+
// library. A nil return indicates that decoding was not successful.
190+
type MultiCauseDecoder = func(ctx context.Context, causes []error, msgPrefix string, safeDetails []string, payload proto.Message) error
191+
192+
// registry for RegisterMultiCauseDecoder.
193+
var multiCauseDecoders = map[TypeKey]MultiCauseDecoder{}
194+
195+
// RegisterMultiCauseDecoder can be used to register new multi-cause
196+
// wrapper types to the library. Registered wrappers will be decoded
197+
// using their own Go type when an error is decoded. Multi-cause
198+
// wrappers that have not been registered will be decoded using the
199+
// opaqueWrapper type.
200+
func RegisterMultiCauseDecoder(theType TypeKey, decoder MultiCauseDecoder) {
201+
if decoder == nil {
202+
delete(multiCauseDecoders, theType)
203+
} else {
204+
multiCauseDecoders[theType] = decoder
205+
}
206+
}

errbase/encode.go

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,24 @@ func EncodeError(ctx context.Context, err error) EncodedError {
3333
if cause := UnwrapOnce(err); cause != nil {
3434
return encodeWrapper(ctx, err, cause)
3535
}
36-
// Not a causer.
37-
return encodeLeaf(ctx, err)
36+
return encodeLeaf(ctx, err, UnwrapMulti(err))
3837
}
3938

40-
// encodeLeaf encodes a leaf error.
41-
func encodeLeaf(ctx context.Context, err error) EncodedError {
39+
// encodeLeaf encodes a leaf error. This function accepts a `causes`
40+
// argument because we encode multi-cause errors using the Leaf
41+
// protobuf. This was done to enable backwards compatibility when
42+
// introducing this functionality since the Wrapper type already has a
43+
// required single `cause` field.
44+
func encodeLeaf(ctx context.Context, err error, causes []error) EncodedError {
4245
var msg string
4346
var details errorspb.EncodedErrorDetails
4447

4548
if e, ok := err.(*opaqueLeaf); ok {
4649
msg = e.msg
4750
details = e.details
51+
} else if e, ok := err.(*opaqueLeafCauses); ok {
52+
msg = e.msg
53+
details = e.details
4854
} else {
4955
details.OriginalTypeName, details.ErrorTypeMark.FamilyName, details.ErrorTypeMark.Extension = getTypeDetails(err, false /*onlyFamily*/)
5056

@@ -74,11 +80,21 @@ func encodeLeaf(ctx context.Context, err error) EncodedError {
7480
details.FullDetails = encodeAsAny(ctx, err, payload)
7581
}
7682

83+
var cs []*EncodedError
84+
if len(causes) > 0 {
85+
cs = make([]*EncodedError, len(causes))
86+
for i, ee := range causes {
87+
ee := EncodeError(ctx, ee)
88+
cs[i] = &ee
89+
}
90+
}
91+
7792
return EncodedError{
7893
Error: &errorspb.EncodedError_Leaf{
7994
Leaf: &errorspb.EncodedErrorLeaf{
80-
Message: msg,
81-
Details: details,
95+
Message: msg,
96+
Details: details,
97+
MultierrorCauses: cs,
8298
},
8399
},
84100
}
@@ -207,6 +223,8 @@ func getTypeDetails(
207223
switch t := err.(type) {
208224
case *opaqueLeaf:
209225
return t.details.OriginalTypeName, t.details.ErrorTypeMark.FamilyName, t.details.ErrorTypeMark.Extension
226+
case *opaqueLeafCauses:
227+
return t.details.OriginalTypeName, t.details.ErrorTypeMark.FamilyName, t.details.ErrorTypeMark.Extension
210228
case *opaqueWrapper:
211229
return t.details.OriginalTypeName, t.details.ErrorTypeMark.FamilyName, t.details.ErrorTypeMark.Extension
212230
}
@@ -310,6 +328,28 @@ type LeafEncoder = func(ctx context.Context, err error) (msg string, safeDetails
310328
// registry for RegisterLeafEncoder.
311329
var leafEncoders = map[TypeKey]LeafEncoder{}
312330

331+
// RegisterMultiCauseEncoder can be used to register new multi-cause
332+
// error types to the library. Registered types will be encoded using
333+
// their own Go type when an error is encoded. Multi-cause wrappers
334+
// that have not been registered will be encoded using the
335+
// opaqueWrapper type.
336+
func RegisterMultiCauseEncoder(theType TypeKey, encoder MultiCauseEncoder) {
337+
// This implementation is a simple wrapper around `LeafEncoder`
338+
// because we implemented multi-cause error wrapper encoding into a
339+
// `Leaf` instead of a `Wrapper` for smoother backwards
340+
// compatibility support. Exposing this detail to consumers of the
341+
// API is confusing and hence avoided. The causes of the error are
342+
// encoded separately regardless of this encoder's implementation.
343+
RegisterLeafEncoder(theType, encoder)
344+
}
345+
346+
// MultiCauseEncoder is to be provided (via RegisterMultiCauseEncoder
347+
// above) by additional multi-cause wrapper types not yet known to this
348+
// library. The encoder will automatically extract and encode the
349+
// causes of this error by calling `Unwrap()` and expecting a slice of
350+
// errors.
351+
type MultiCauseEncoder = func(ctx context.Context, err error) (msg string, safeDetails []string, payload proto.Message)
352+
313353
// RegisterWrapperEncoder can be used to register new wrapper types to
314354
// the library. Registered wrappers will be encoded using their own
315355
// Go type when an error is encoded. Wrappers that have not been

0 commit comments

Comments
 (0)