loader revisited
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
package error
|
||||
|
||||
import "errors"
|
||||
|
||||
// Class describes a top-level operational error class that can be used by
|
||||
// higher layers to route failures to the appropriate UI or transport handler.
|
||||
type Class string
|
||||
|
||||
const (
|
||||
// ClassConnection marks connectivity and transport failures talking to remote services.
|
||||
ClassConnection Class = "connection"
|
||||
// ClassStorage marks local persistence and filesystem related failures.
|
||||
ClassStorage Class = "storage"
|
||||
// ClassService marks remote service contract and processing failures.
|
||||
ClassService Class = "service"
|
||||
)
|
||||
|
||||
// ClassifiedError wraps another error with a top-level error class.
|
||||
//
|
||||
// The wrapped error remains available through Unwrap so existing callers may
|
||||
// continue using errors.Is or errors.As against the original cause.
|
||||
type ClassifiedError struct {
|
||||
class Class
|
||||
err error
|
||||
}
|
||||
|
||||
// Error returns either the wrapped error text or, when no wrapped error is available,
|
||||
// the textual representation of the class itself.
|
||||
func (e *ClassifiedError) Error() string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
if e.err != nil {
|
||||
return e.err.Error()
|
||||
}
|
||||
return string(e.class)
|
||||
}
|
||||
|
||||
// Unwrap exposes the wrapped cause for standard Go error inspection.
|
||||
func (e *ClassifiedError) Unwrap() error {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
return e.err
|
||||
}
|
||||
|
||||
// Class returns the top-level class recorded on this error.
|
||||
func (e *ClassifiedError) Class() Class {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
return e.class
|
||||
}
|
||||
|
||||
// Is reports class equality so errors.Is(err, ErrConnection) style checks remain possible.
|
||||
func (e *ClassifiedError) Is(target error) bool {
|
||||
t, ok := target.(*ClassifiedError)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return e.class != "" && e.class == t.class
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrConnection is a class sentinel for connection related failures.
|
||||
ErrConnection = &ClassifiedError{class: ClassConnection}
|
||||
// ErrStorage is a class sentinel for storage related failures.
|
||||
ErrStorage = &ClassifiedError{class: ClassStorage}
|
||||
// ErrService is a class sentinel for service related failures.
|
||||
ErrService = &ClassifiedError{class: ClassService}
|
||||
)
|
||||
|
||||
// WrapConnection wraps err with the connection class unless it is already classified.
|
||||
func WrapConnection(err error) error {
|
||||
return wrapClass(ClassConnection, err)
|
||||
}
|
||||
|
||||
// WrapStorage wraps err with the storage class unless it is already classified.
|
||||
func WrapStorage(err error) error {
|
||||
return wrapClass(ClassStorage, err)
|
||||
}
|
||||
|
||||
// WrapService wraps err with the service class unless it is already classified.
|
||||
func WrapService(err error) error {
|
||||
return wrapClass(ClassService, err)
|
||||
}
|
||||
|
||||
// IsConnection reports whether err is classified as a connection failure.
|
||||
func IsConnection(err error) bool {
|
||||
return errors.Is(err, ErrConnection)
|
||||
}
|
||||
|
||||
// IsStorage reports whether err is classified as a storage failure.
|
||||
func IsStorage(err error) bool {
|
||||
return errors.Is(err, ErrStorage)
|
||||
}
|
||||
|
||||
// IsService reports whether err is classified as a service failure.
|
||||
func IsService(err error) bool {
|
||||
return errors.Is(err, ErrService)
|
||||
}
|
||||
|
||||
func wrapClass(class Class, err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if existing, ok := errors.AsType[*ClassifiedError](err); ok && existing.class == class {
|
||||
return err
|
||||
}
|
||||
return &ClassifiedError{class: class, err: err}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package error
|
||||
|
||||
import (
|
||||
stderrors "errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestClassifiedErrorWrapPreservesCause(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cause := stderrors.New("dial tcp: connection refused")
|
||||
err := WrapConnection(cause)
|
||||
|
||||
require.ErrorIs(t, err, cause)
|
||||
require.True(t, IsConnection(err))
|
||||
require.False(t, IsStorage(err))
|
||||
require.False(t, IsService(err))
|
||||
|
||||
classified, ok := stderrors.AsType[*ClassifiedError](err)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, ClassConnection, classified.Class())
|
||||
}
|
||||
|
||||
func TestClassifiedErrorDoesNotDoubleWrapSameClass(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cause := stderrors.New("write file")
|
||||
first := WrapStorage(cause)
|
||||
second := WrapStorage(first)
|
||||
|
||||
require.Same(t, first, second)
|
||||
}
|
||||
|
||||
func TestGenericErrorSupportsAsAndUnwrap(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cause := stderrors.New("root cause")
|
||||
err := newGenericError(ErrDummy, cause, "subject")
|
||||
|
||||
require.ErrorIs(t, err, cause)
|
||||
|
||||
generic, ok := stderrors.AsType[*GenericError](err)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, ErrDummy, generic.Code)
|
||||
require.Equal(t, "Dummy: subject: root cause", err.Error())
|
||||
}
|
||||
@@ -192,6 +192,11 @@ func (ge GenericError) Error() string {
|
||||
return msg
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying error wrapped by GenericError, if any.
|
||||
func (ge GenericError) Unwrap() error {
|
||||
return ge.err
|
||||
}
|
||||
|
||||
func newGenericError(code int, arg ...any) error {
|
||||
e := &GenericError{Code: code}
|
||||
if len(arg) > 0 {
|
||||
@@ -207,7 +212,7 @@ func newGenericError(code int, arg ...any) error {
|
||||
e.subject = asString(arg[i])
|
||||
}
|
||||
}
|
||||
return *e
|
||||
return e
|
||||
}
|
||||
|
||||
func asString(v any) string {
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
package error
|
||||
|
||||
func NewRepoError(arg ...any) error {
|
||||
return newGenericError(ErrStorageFailure, arg...)
|
||||
return WrapStorage(newGenericError(ErrStorageFailure, arg...))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user