lib/errors: add support for unwrapping go1.20 multi errors

This commit is contained in:
Nick Craig-Wood 2023-01-17 10:35:02 +00:00
parent ca9182d6ae
commit 844e8fb8bd
2 changed files with 215 additions and 47 deletions

View File

@ -17,8 +17,10 @@ type WalkFunc func(error) bool
// The next error in the chain is determined by the following rules:
//
// the return value of this method is used.
// - If the current error has a `Unwrap() error` method (golang.org/x/xerrors),
// - If the current error has a `Unwrap() error` method
// the return value of this method is used.
// - If the current error has a `Unwrap() []error` method
// the return values of this method is used.
// - Common errors in the Go runtime that contain an Err field will use this value.
func Walk(err error, f WalkFunc) {
for prev := err; err != nil; prev = err {
@ -27,6 +29,11 @@ func Walk(err error, f WalkFunc) {
}
switch e := err.(type) {
case multiWrapper:
for _, err = range e.Unwrap() {
Walk(err, f)
}
return
case causer:
err = e.Cause()
case wrapper:
@ -62,3 +69,6 @@ type causer interface {
type wrapper interface {
Unwrap() error
}
type multiWrapper interface {
Unwrap() []error
}

View File

@ -1,4 +1,4 @@
package errors_test
package errors
import (
"errors"
@ -6,86 +6,244 @@ import (
"testing"
"github.com/stretchr/testify/assert"
liberrors "github.com/rclone/rclone/lib/errors"
)
func TestWalk(t *testing.T) {
origin := errors.New("origin")
var (
e1 = errors.New("e1")
e2 = errors.New("e2")
e3 = errors.New("e3")
)
for _, test := range []struct {
err error
calls int
last error
err error
want []error
}{
{causerError{nil}, 1, causerError{nil}},
{wrapperError{nil}, 1, wrapperError{nil}},
{reflectError{nil}, 1, reflectError{nil}},
{causerError{origin}, 2, origin},
{wrapperError{origin}, 2, origin},
{reflectError{origin}, 2, origin},
{causerError{reflectError{origin}}, 3, origin},
{wrapperError{causerError{origin}}, 3, origin},
{reflectError{wrapperError{origin}}, 3, origin},
{causerError{reflectError{causerError{origin}}}, 4, origin},
{wrapperError{causerError{wrapperError{origin}}}, 4, origin},
{reflectError{wrapperError{reflectError{origin}}}, 4, origin},
{stopError{nil}, 1, stopError{nil}},
{stopError{causerError{nil}}, 1, stopError{causerError{nil}}},
{stopError{wrapperError{nil}}, 1, stopError{wrapperError{nil}}},
{stopError{reflectError{nil}}, 1, stopError{reflectError{nil}}},
{causerError{stopError{origin}}, 2, stopError{origin}},
{wrapperError{stopError{origin}}, 2, stopError{origin}},
{reflectError{stopError{origin}}, 2, stopError{origin}},
{causerError{reflectError{stopError{nil}}}, 3, stopError{nil}},
{wrapperError{causerError{stopError{nil}}}, 3, stopError{nil}},
{reflectError{wrapperError{stopError{nil}}}, 3, stopError{nil}},
{
causerError{nil}, []error{
causerError{nil},
},
}, {
wrapperError{nil}, []error{
wrapperError{nil},
},
}, {
reflectError{nil}, []error{
reflectError{nil},
},
}, {
causerError{e1}, []error{
causerError{e1}, e1,
},
}, {
wrapperError{e1}, []error{
wrapperError{e1}, e1,
},
}, {
reflectError{e1}, []error{
reflectError{e1}, e1,
},
}, {
causerError{reflectError{e1}}, []error{
causerError{reflectError{e1}},
reflectError{e1},
e1,
},
}, {
wrapperError{causerError{e1}}, []error{
wrapperError{causerError{e1}},
causerError{e1},
e1,
},
}, {
reflectError{wrapperError{e1}}, []error{
reflectError{wrapperError{e1}},
wrapperError{e1},
e1,
},
}, {
causerError{reflectError{causerError{e1}}}, []error{
causerError{reflectError{causerError{e1}}},
reflectError{causerError{e1}},
causerError{e1},
e1,
},
}, {
wrapperError{causerError{wrapperError{e1}}}, []error{
wrapperError{causerError{wrapperError{e1}}},
causerError{wrapperError{e1}},
wrapperError{e1},
e1,
},
}, {
reflectError{wrapperError{reflectError{e1}}}, []error{
reflectError{wrapperError{reflectError{e1}}},
wrapperError{reflectError{e1}},
reflectError{e1},
e1,
},
}, {
stopError{nil}, []error{
stopError{nil},
},
}, {
stopError{causerError{nil}}, []error{
stopError{causerError{nil}},
},
}, {
stopError{wrapperError{nil}}, []error{
stopError{wrapperError{nil}},
},
}, {
stopError{reflectError{nil}}, []error{
stopError{reflectError{nil}},
},
}, {
causerError{stopError{e1}}, []error{
causerError{stopError{e1}},
stopError{e1},
},
}, {
wrapperError{stopError{e1}}, []error{
wrapperError{stopError{e1}},
stopError{e1},
},
}, {
reflectError{stopError{e1}}, []error{
reflectError{stopError{e1}},
stopError{e1},
},
}, {
causerError{reflectError{stopError{nil}}}, []error{
causerError{reflectError{stopError{nil}}},
reflectError{stopError{nil}},
stopError{nil},
},
}, {
wrapperError{causerError{stopError{nil}}}, []error{
wrapperError{causerError{stopError{nil}}},
causerError{stopError{nil}},
stopError{nil},
},
}, {
reflectError{wrapperError{stopError{nil}}}, []error{
reflectError{wrapperError{stopError{nil}}},
wrapperError{stopError{nil}},
stopError{nil},
},
}, {
multiWrapperError{[]error{e1}}, []error{
multiWrapperError{[]error{e1}},
e1,
},
}, {
multiWrapperError{[]error{}}, []error{
multiWrapperError{[]error{}},
},
}, {
multiWrapperError{[]error{e1, e2, e3}}, []error{
multiWrapperError{[]error{e1, e2, e3}},
e1,
e2,
e3,
},
}, {
multiWrapperError{[]error{reflectError{e1}, wrapperError{e2}, stopError{e3}}}, []error{
multiWrapperError{[]error{reflectError{e1}, wrapperError{e2}, stopError{e3}}},
reflectError{e1},
e1,
wrapperError{e2},
e2,
stopError{e3},
},
},
} {
var last error
calls := 0
liberrors.Walk(test.err, func(err error) bool {
calls++
last = err
var got []error
Walk(test.err, func(err error) bool {
got = append(got, err)
_, stop := err.(stopError)
return stop
})
assert.Equal(t, test.calls, calls)
assert.Equal(t, test.last, last)
assert.Equal(t, test.want, got, test.err)
}
}
type causerError struct {
err error
}
type wrapperError struct {
err error
}
type reflectError struct {
Err error
}
type stopError struct {
err error
}
func (e causerError) Error() string {
return fmt.Sprintf("causerError(%s)", e.err)
}
func (e causerError) Cause() error {
return e.err
}
var (
_ error = causerError{nil}
_ causer = causerError{nil}
)
type wrapperError struct {
err error
}
func (e wrapperError) Unwrap() error {
return e.err
}
func (e wrapperError) Error() string {
return fmt.Sprintf("wrapperError(%s)", e.err)
}
var (
_ error = wrapperError{nil}
_ wrapper = wrapperError{nil}
)
type multiWrapperError struct {
errs []error
}
func (e multiWrapperError) Unwrap() []error {
return e.errs
}
func (e multiWrapperError) Error() string {
return fmt.Sprintf("multiWrapperError(%s)", e.errs)
}
var (
_ error = multiWrapperError{nil}
_ multiWrapper = multiWrapperError{nil}
)
type reflectError struct {
Err error
}
func (e reflectError) Error() string {
return fmt.Sprintf("reflectError(%s)", e.Err)
}
var (
_ error = reflectError{nil}
)
type stopError struct {
err error
}
func (e stopError) Error() string {
return fmt.Sprintf("stopError(%s)", e.err)
}
func (e stopError) Cause() error {
return e.err
}
var (
_ error = stopError{nil}
_ causer = stopError{nil}
)