summaryrefslogtreecommitdiff
path: root/internal/errors
diff options
context:
space:
mode:
Diffstat (limited to 'internal/errors')
-rw-r--r--internal/errors/errors.go137
-rw-r--r--internal/errors/errors_test.go109
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