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 next error in the chain is determined by the following rules:
// //
// the return value of this method is used. // 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. // 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. // - Common errors in the Go runtime that contain an Err field will use this value.
func Walk(err error, f WalkFunc) { func Walk(err error, f WalkFunc) {
for prev := err; err != nil; prev = err { for prev := err; err != nil; prev = err {
@ -27,6 +29,11 @@ func Walk(err error, f WalkFunc) {
} }
switch e := err.(type) { switch e := err.(type) {
case multiWrapper:
for _, err = range e.Unwrap() {
Walk(err, f)
}
return
case causer: case causer:
err = e.Cause() err = e.Cause()
case wrapper: case wrapper:
@ -62,3 +69,6 @@ type causer interface {
type wrapper interface { type wrapper interface {
Unwrap() error Unwrap() error
} }
type multiWrapper interface {
Unwrap() []error
}

View File

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