diff --git a/errors.go b/errors.go index 4827d8c..f654d19 100644 --- a/errors.go +++ b/errors.go @@ -139,20 +139,28 @@ type errAlreadyExists struct{} func (errAlreadyExists) Error() string { return "already exists" } +func (errAlreadyExists) AlreadyExists() {} + func (e errAlreadyExists) WithMessage(msg string) error { return customMessage{e, msg} } +type alreadyExists interface { + AlreadyExists() +} + // IsAlreadyExists returns true if the error is due to an already existing // metadata item func IsAlreadyExists(err error) bool { - return errors.Is(err, ErrAlreadyExists) + return errors.Is(err, ErrAlreadyExists) || isInterface[alreadyExists](err) } type errPermissionDenied struct{} func (errPermissionDenied) Error() string { return "permission denied" } +func (errPermissionDenied) Forbidden() {} + func (e errPermissionDenied) WithMessage(msg string) error { return customMessage{e, msg} } @@ -172,28 +180,40 @@ type errResourceExhausted struct{} func (errResourceExhausted) Error() string { return "resource exhausted" } +func (errResourceExhausted) ResourceExhausted() {} + func (e errResourceExhausted) WithMessage(msg string) error { return customMessage{e, msg} } +type resourceExhausted interface { + ResourceExhausted() +} + // IsResourceExhausted returns true if the error is due to // a lack of resources or too many attempts. func IsResourceExhausted(err error) bool { - return errors.Is(err, errResourceExhausted{}) + return errors.Is(err, errResourceExhausted{}) || isInterface[resourceExhausted](err) } type errFailedPrecondition struct{} func (e errFailedPrecondition) Error() string { return "failed precondition" } +func (errFailedPrecondition) FailedPrecondition() {} + func (e errFailedPrecondition) WithMessage(msg string) error { return customMessage{e, msg} } +type failedPrecondition interface { + FailedPrecondition() +} + // IsFailedPrecondition returns true if an operation could not proceed due to // the lack of a particular condition func IsFailedPrecondition(err error) bool { - return errors.Is(err, errFailedPrecondition{}) + return errors.Is(err, errFailedPrecondition{}) || isInterface[failedPrecondition](err) } type errConflict struct{} @@ -242,27 +262,39 @@ type errAborted struct{} func (errAborted) Error() string { return "aborted" } +func (errAborted) Aborted() {} + func (e errAborted) WithMessage(msg string) error { return customMessage{e, msg} } +type aborted interface { + Aborted() +} + // IsAborted returns true if an operation was aborted. func IsAborted(err error) bool { - return errors.Is(err, errAborted{}) + return errors.Is(err, errAborted{}) || isInterface[aborted](err) } type errOutOfRange struct{} func (errOutOfRange) Error() string { return "out of range" } +func (errOutOfRange) OutOfRange() {} + func (e errOutOfRange) WithMessage(msg string) error { return customMessage{e, msg} } +type outOfRange interface { + OutOfRange() +} + // IsOutOfRange returns true if an operation could not proceed due // to data being out of the expected range. func IsOutOfRange(err error) bool { - return errors.Is(err, errOutOfRange{}) + return errors.Is(err, errOutOfRange{}) || isInterface[outOfRange](err) } type errNotImplemented struct{} diff --git a/errors_test.go b/errors_test.go index 40e564f..d4cb0d9 100644 --- a/errors_test.go +++ b/errors_test.go @@ -19,6 +19,7 @@ package errdefs import ( "context" "errors" + "fmt" "reflect" "testing" ) @@ -158,6 +159,39 @@ func TestWithMessage(t *testing.T) { } } +func TestInterfaceMatch(t *testing.T) { + testCases := []struct { + err error + check func(error) bool + }{ + {ErrUnknown, isInterface[unknown]}, + {ErrInvalidArgument, isInterface[invalidParameter]}, + {ErrNotFound, isInterface[notFound]}, + {ErrAlreadyExists, isInterface[alreadyExists]}, + {ErrPermissionDenied, isInterface[forbidden]}, + {ErrResourceExhausted, isInterface[resourceExhausted]}, + {ErrFailedPrecondition, isInterface[failedPrecondition]}, + {ErrConflict, isInterface[conflict]}, + {ErrNotModified, isInterface[notModified]}, + {ErrAborted, isInterface[aborted]}, + {ErrOutOfRange, isInterface[outOfRange]}, + {ErrNotImplemented, isInterface[notImplemented]}, + {ErrInternal, isInterface[system]}, + {ErrUnavailable, isInterface[unavailable]}, + {ErrDataLoss, isInterface[dataLoss]}, + {ErrUnauthenticated, isInterface[unauthorized]}, + } + + for _, tc := range testCases { + tc := tc + t.Run(fmt.Sprintf("%T", tc.err), func(t *testing.T) { + if !tc.check(tc.err) { + t.Fatal("Error does not match interface") + } + }) + } +} + type customInvalidArgument struct{} func (*customInvalidArgument) Error() string { diff --git a/resolve.go b/resolve.go index 852d591..c02d4a7 100644 --- a/resolve.go +++ b/resolve.go @@ -74,17 +74,22 @@ func firstError(err error) error { return ErrInvalidArgument case notFound: return ErrNotFound - // Skip ErrAlreadyExists, no interface defined + case alreadyExists: + return ErrAlreadyExists case forbidden: return ErrPermissionDenied - // Skip ErrResourceExhasuted, no interface defined - // Skip ErrFailedPrecondition, no interface defined + case resourceExhausted: + return ErrResourceExhausted + case failedPrecondition: + return ErrFailedPrecondition case conflict: return ErrConflict case notModified: return ErrNotModified - // Skip ErrAborted, no interface defined - // Skip ErrOutOfRange, no interface defined + case aborted: + return ErrAborted + case errOutOfRange: + return ErrOutOfRange case notImplemented: return ErrNotImplemented case system: