diff options
Diffstat (limited to 'internal/errors')
| -rw-r--r-- | internal/errors/errors.go | 137 | ||||
| -rw-r--r-- | internal/errors/errors_test.go | 109 |
2 files changed, 246 insertions, 0 deletions
diff --git a/internal/errors/errors.go b/internal/errors/errors.go new file mode 100644 index 0000000..bb53efd --- /dev/null +++ b/internal/errors/errors.go @@ -0,0 +1,137 @@ +package errors + +import ( + "errors" + "fmt" +) + +// Sentinel errors for common error conditions +var ( + // Connection errors + ErrConnectionFailed = errors.New("connection failed") + ErrConnectionTimeout = errors.New("connection timeout") + ErrConnectionRefused = errors.New("connection refused") + ErrTooManyConnections = errors.New("too many connections") + + // Authentication/Permission errors + ErrPermissionDenied = errors.New("permission denied") + ErrAuthenticationFailed = errors.New("authentication failed") + ErrUnauthorized = errors.New("unauthorized") + ErrInvalidCredentials = errors.New("invalid credentials") + + // Configuration errors + ErrInvalidConfig = errors.New("invalid configuration") + ErrMissingConfig = errors.New("missing configuration") + ErrConfigValidation = errors.New("configuration validation failed") + + // File/IO errors + ErrFileNotFound = errors.New("file not found") + ErrFileAccessDenied = errors.New("file access denied") + ErrInvalidPath = errors.New("invalid path") + ErrReadFailed = errors.New("read failed") + ErrWriteFailed = errors.New("write failed") + + // Protocol errors + ErrInvalidProtocol = errors.New("invalid protocol") + ErrProtocolMismatch = errors.New("protocol version mismatch") + ErrInvalidCommand = errors.New("invalid command") + ErrInvalidQuery = errors.New("invalid query") + + // Resource errors + ErrResourceExhausted = errors.New("resource exhausted") + ErrBufferFull = errors.New("buffer full") + ErrTimeout = errors.New("operation timeout") + + // General errors + ErrInvalidArgument = errors.New("invalid argument") + ErrNotImplemented = errors.New("not implemented") + ErrInternal = errors.New("internal error") +) + +// Error wrapping functions + +// Wrap wraps an error with additional context +func Wrap(err error, msg string) error { + if err == nil { + return nil + } + return fmt.Errorf("%s: %w", msg, err) +} + +// Wrapf wraps an error with formatted context +func Wrapf(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + return fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), err) +} + +// New creates a new error with formatted message +func New(format string, args ...interface{}) error { + return fmt.Errorf(format, args...) +} + +// Is checks if an error is of a specific type +func Is(err, target error) bool { + return errors.Is(err, target) +} + +// As attempts to extract a specific error type +func As(err error, target interface{}) bool { + return errors.As(err, target) +} + +// Unwrap returns the wrapped error +func Unwrap(err error) error { + return errors.Unwrap(err) +} + +// Multi-error support for operations that can have multiple failures + +// MultiError represents multiple errors +type MultiError struct { + errors []error +} + +// NewMultiError creates a new MultiError +func NewMultiError() *MultiError { + return &MultiError{ + errors: make([]error, 0), + } +} + +// Add adds an error to the MultiError +func (m *MultiError) Add(err error) { + if err != nil { + m.errors = append(m.errors, err) + } +} + +// HasErrors returns true if there are any errors +func (m *MultiError) HasErrors() bool { + return len(m.errors) > 0 +} + +// Error implements the error interface +func (m *MultiError) Error() string { + if len(m.errors) == 0 { + return "" + } + if len(m.errors) == 1 { + return m.errors[0].Error() + } + return fmt.Sprintf("multiple errors occurred: %v", m.errors) +} + +// Errors returns all collected errors +func (m *MultiError) Errors() []error { + return m.errors +} + +// ErrorOrNil returns nil if no errors, otherwise returns the MultiError +func (m *MultiError) ErrorOrNil() error { + if m.HasErrors() { + return m + } + return nil +}
\ No newline at end of file diff --git a/internal/errors/errors_test.go b/internal/errors/errors_test.go new file mode 100644 index 0000000..9193e38 --- /dev/null +++ b/internal/errors/errors_test.go @@ -0,0 +1,109 @@ +package errors + +import ( + "errors" + "strings" + "testing" +) + +func TestWrap(t *testing.T) { + tests := []struct { + name string + err error + msg string + expected string + }{ + { + name: "wrap with message", + err: ErrFileNotFound, + msg: "opening config file", + expected: "opening config file: file not found", + }, + { + name: "wrap nil error", + err: nil, + msg: "should return nil", + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := Wrap(tt.err, tt.msg) + if tt.err == nil && result != nil { + t.Errorf("expected nil, got %v", result) + } + if tt.err != nil && result.Error() != tt.expected { + t.Errorf("expected %q, got %q", tt.expected, result.Error()) + } + }) + } +} + +func TestWrapf(t *testing.T) { + err := Wrapf(ErrConnectionFailed, "connecting to %s:%d", "localhost", 2222) + expected := "connecting to localhost:2222: connection failed" + if err.Error() != expected { + t.Errorf("expected %q, got %q", expected, err.Error()) + } +} + +func TestIs(t *testing.T) { + wrapped := Wrap(ErrPermissionDenied, "accessing /etc/passwd") + + if !Is(wrapped, ErrPermissionDenied) { + t.Error("expected Is to return true for wrapped error") + } + + if Is(wrapped, ErrFileNotFound) { + t.Error("expected Is to return false for different error") + } +} + +func TestMultiError(t *testing.T) { + multi := NewMultiError() + + // Test empty multi-error + if multi.HasErrors() { + t.Error("new MultiError should not have errors") + } + if multi.ErrorOrNil() != nil { + t.Error("ErrorOrNil should return nil for empty MultiError") + } + + // Add errors + multi.Add(ErrConnectionFailed) + multi.Add(nil) // Should be ignored + multi.Add(ErrTimeout) + + if !multi.HasErrors() { + t.Error("MultiError should have errors after adding") + } + + if len(multi.Errors()) != 2 { + t.Errorf("expected 2 errors, got %d", len(multi.Errors())) + } + + // Test error message + errMsg := multi.Error() + if !strings.Contains(errMsg, "multiple errors occurred") { + t.Errorf("unexpected error message: %s", errMsg) + } + + // Test single error + single := NewMultiError() + single.Add(ErrInvalidArgument) + if single.Error() != "invalid argument" { + t.Errorf("single error message incorrect: %s", single.Error()) + } +} + +func TestErrorUnwrapping(t *testing.T) { + base := errors.New("base error") + wrapped := Wrap(base, "context") + + unwrapped := Unwrap(wrapped) + if unwrapped != base { + t.Error("Unwrap did not return base error") + } +}
\ No newline at end of file |
