From 844e8fb8bdfbe4ba707b7fd3a4ac7a2366dc95a1 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Tue, 17 Jan 2023 10:35:02 +0000 Subject: [PATCH] lib/errors: add support for unwrapping go1.20 multi errors --- lib/errors/errors.go | 12 +- lib/errors/errors_test.go | 250 +++++++++++++++++++++++++++++++------- 2 files changed, 215 insertions(+), 47 deletions(-) diff --git a/lib/errors/errors.go b/lib/errors/errors.go index ed848cbd6..a978909ab 100644 --- a/lib/errors/errors.go +++ b/lib/errors/errors.go @@ -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 +} diff --git a/lib/errors/errors_test.go b/lib/errors/errors_test.go index c208a5a2e..c04b6d669 100644 --- a/lib/errors/errors_test.go +++ b/lib/errors/errors_test.go @@ -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} +)