Skip to content

Commit ef792d8

Browse files
committed
feat: implement enhanced error handling and recovery system - Add custom error types with context and categories - Implement exponential backoff and circuit breaker patterns - Create comprehensive input validation framework - Add checkpoint system for progress preservation - Addresses issue #13
1 parent ae803c4 commit ef792d8

File tree

7 files changed

+1367
-0
lines changed

7 files changed

+1367
-0
lines changed

pkg/errors/types/codes.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package types
2+
3+
// Error Categories and Codes as defined in issue #13
4+
5+
// Network errors
6+
const (
7+
ErrNetworkTimeout = "NET_001"
8+
ErrNetworkUnavailable = "NET_002"
9+
ErrRateLimited = "NET_003"
10+
ErrConnectionRefused = "NET_004"
11+
ErrDNSResolution = "NET_005"
12+
)
13+
14+
// Authentication errors
15+
const (
16+
ErrInvalidCredentials = "AUTH_001"
17+
ErrTokenExpired = "AUTH_002"
18+
ErrUnauthorized = "AUTH_003"
19+
ErrForbidden = "AUTH_004"
20+
ErrAPIKeyMissing = "AUTH_005"
21+
)
22+
23+
// Validation errors
24+
const (
25+
ErrInvalidConfig = "VAL_001"
26+
ErrInvalidInput = "VAL_002"
27+
ErrMissingRequired = "VAL_003"
28+
ErrInvalidFormat = "VAL_004"
29+
ErrOutOfRange = "VAL_005"
30+
)
31+
32+
// Operation errors
33+
const (
34+
ErrExportFailed = "OP_001"
35+
ErrImportFailed = "OP_002"
36+
ErrFileSystem = "OP_003"
37+
ErrProcessingFailed = "OP_004"
38+
ErrOperationCanceled = "OP_005"
39+
ErrOperationFailed = "OP_006"
40+
)
41+
42+
// Data errors
43+
const (
44+
ErrDataCorrupted = "DATA_001"
45+
ErrDataMissing = "DATA_002"
46+
ErrDataFormat = "DATA_003"
47+
ErrDataIntegrity = "DATA_004"
48+
)
49+
50+
// Configuration errors
51+
const (
52+
ErrConfigMissing = "CFG_001"
53+
ErrConfigCorrupted = "CFG_002"
54+
ErrConfigPermissions = "CFG_003"
55+
ErrConfigSyntax = "CFG_004"
56+
)
57+
58+
// System errors
59+
const (
60+
ErrSystemResource = "SYS_001"
61+
ErrSystemPermission = "SYS_002"
62+
ErrSystemDisk = "SYS_003"
63+
ErrSystemMemory = "SYS_004"
64+
)
65+
66+
// ErrorCategory represents error categories for classification
67+
type ErrorCategory string
68+
69+
const (
70+
CategoryNetwork ErrorCategory = "network"
71+
CategoryAuthentication ErrorCategory = "authentication"
72+
CategoryValidation ErrorCategory = "validation"
73+
CategoryOperation ErrorCategory = "operation"
74+
CategoryData ErrorCategory = "data"
75+
CategoryConfiguration ErrorCategory = "configuration"
76+
CategorySystem ErrorCategory = "system"
77+
)
78+
79+
// GetErrorCategory returns the category for a given error code
80+
func GetErrorCategory(code string) ErrorCategory {
81+
switch {
82+
case code[:3] == "NET":
83+
return CategoryNetwork
84+
case code[:4] == "AUTH":
85+
return CategoryAuthentication
86+
case code[:3] == "VAL":
87+
return CategoryValidation
88+
case code[:2] == "OP":
89+
return CategoryOperation
90+
case code[:4] == "DATA":
91+
return CategoryData
92+
case code[:3] == "CFG":
93+
return CategoryConfiguration
94+
case code[:3] == "SYS":
95+
return CategorySystem
96+
default:
97+
return CategoryOperation // default category
98+
}
99+
}
100+
101+
// IsRetryableError determines if an error should be retried
102+
func IsRetryableError(code string) bool {
103+
retryableCodes := map[string]bool{
104+
ErrNetworkTimeout: true,
105+
ErrNetworkUnavailable: true,
106+
ErrRateLimited: true,
107+
ErrConnectionRefused: true,
108+
ErrTokenExpired: true,
109+
ErrSystemResource: true,
110+
ErrSystemDisk: false, // Usually not retryable
111+
ErrSystemMemory: false, // Usually not retryable
112+
}
113+
114+
return retryableCodes[code]
115+
}
116+
117+
// IsTemporaryError determines if an error is temporary
118+
func IsTemporaryError(code string) bool {
119+
temporaryCodes := map[string]bool{
120+
ErrNetworkTimeout: true,
121+
ErrNetworkUnavailable: true,
122+
ErrRateLimited: true,
123+
ErrSystemResource: true,
124+
ErrTokenExpired: true,
125+
}
126+
127+
return temporaryCodes[code]
128+
}

pkg/errors/types/errors.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package types
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
)
8+
9+
// AppError represents the main error structure for the application
10+
type AppError struct {
11+
Code string `json:"code"`
12+
Message string `json:"message"`
13+
Details string `json:"details,omitempty"`
14+
Cause error `json:"-"`
15+
Context ErrorContext `json:"context,omitempty"`
16+
Time time.Time `json:"timestamp"`
17+
}
18+
19+
// ErrorContext provides additional context for errors
20+
type ErrorContext struct {
21+
Operation string `json:"operation"`
22+
RequestID string `json:"request_id,omitempty"`
23+
UserID string `json:"user_id,omitempty"`
24+
Metadata map[string]string `json:"metadata,omitempty"`
25+
StackTrace string `json:"stack_trace,omitempty"`
26+
}
27+
28+
// Error implements the error interface
29+
func (e *AppError) Error() string {
30+
if e.Cause != nil {
31+
return fmt.Sprintf("%s: %s (caused by: %v)", e.Code, e.Message, e.Cause)
32+
}
33+
return fmt.Sprintf("%s: %s", e.Code, e.Message)
34+
}
35+
36+
// Unwrap allows error unwrapping for errors.Is and errors.As
37+
func (e *AppError) Unwrap() error {
38+
return e.Cause
39+
}
40+
41+
// WithContext adds context to an error
42+
func (e *AppError) WithContext(ctx context.Context) *AppError {
43+
if requestID := ctx.Value("request_id"); requestID != nil {
44+
if id, ok := requestID.(string); ok {
45+
e.Context.RequestID = id
46+
}
47+
}
48+
if userID := ctx.Value("user_id"); userID != nil {
49+
if id, ok := userID.(string); ok {
50+
e.Context.UserID = id
51+
}
52+
}
53+
return e
54+
}
55+
56+
// WithMetadata adds metadata to the error context
57+
func (e *AppError) WithMetadata(key, value string) *AppError {
58+
if e.Context.Metadata == nil {
59+
e.Context.Metadata = make(map[string]string)
60+
}
61+
e.Context.Metadata[key] = value
62+
return e
63+
}
64+
65+
// NewAppError creates a new application error
66+
func NewAppError(code, message string, cause error) *AppError {
67+
return &AppError{
68+
Code: code,
69+
Message: message,
70+
Cause: cause,
71+
Time: time.Now(),
72+
Context: ErrorContext{},
73+
}
74+
}
75+
76+
// NewAppErrorWithOperation creates a new application error with operation context
77+
func NewAppErrorWithOperation(code, message, operation string, cause error) *AppError {
78+
return &AppError{
79+
Code: code,
80+
Message: message,
81+
Cause: cause,
82+
Time: time.Now(),
83+
Context: ErrorContext{
84+
Operation: operation,
85+
},
86+
}
87+
}

0 commit comments

Comments
 (0)