Skip to content

Commit

Permalink
Complete interface definitions for errors
Browse files Browse the repository at this point in the history
Add test and fix missing definition

Signed-off-by: Derek McGowan <derek@mcg.dev>
  • Loading branch information
dmcgowan committed Sep 1, 2024
1 parent 70440b8 commit 41d12e1
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 10 deletions.
42 changes: 37 additions & 5 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
}
Expand All @@ -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{}
Expand Down Expand Up @@ -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{}
Expand Down
34 changes: 34 additions & 0 deletions errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package errdefs
import (
"context"
"errors"
"fmt"
"reflect"
"testing"
)
Expand Down Expand Up @@ -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 {
Expand Down
15 changes: 10 additions & 5 deletions resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit 41d12e1

Please sign in to comment.