Coverage Report

Created: 2025-10-06 12:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/jgrusewski/Work/foxhunt/common/src/error.rs
Line
Count
Source
1
//! Common error types and utilities
2
//!
3
//! This module provides shared error types and utilities used across
4
//! all Foxhunt services.
5
6
use serde::{Deserialize, Serialize};
7
use std::fmt;
8
use std::time::Duration;
9
use thiserror::Error;
10
11
/// Common error type for all Foxhunt services
12
#[derive(Debug, Error)]
13
pub enum CommonError {
14
    /// Database operation failed - wraps database-specific errors
15
    #[error("Database error: {0}")]
16
    Database(#[from] crate::database::DatabaseError),
17
    /// Configuration is invalid or missing required parameters
18
    #[error("Configuration error: {0}")]
19
    Configuration(String),
20
    /// Network communication error occurred
21
    #[error("Network error: {0}")]
22
    Network(String),
23
    /// Service-specific error with categorization for metrics
24
    #[error("Service error: {category} - {message}")]
25
    Service {
26
        /// Error category for classification
27
        category: ErrorCategory,
28
        /// Descriptive error message
29
        message: String,
30
    },
31
    /// Input validation failed
32
    #[error("Validation error: {0}")]
33
    Validation(String),
34
    /// Operation exceeded maximum allowed execution time
35
    #[error("Timeout error: operation took {actual_ms}ms, max allowed {max_ms}ms")]
36
    Timeout {
37
        /// Actual execution time in milliseconds
38
        actual_ms: u64,
39
        /// Maximum allowed execution time in milliseconds
40
        max_ms: u64,
41
    },
42
}
43
44
/// Error categories for classification and metrics
45
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
46
pub enum ErrorCategory {
47
    /// Market data related errors
48
    MarketData,
49
    /// Trading and order management errors
50
    Trading,
51
    /// Network and communication errors
52
    Network,
53
    /// System and infrastructure errors
54
    System,
55
    /// Configuration errors
56
    Configuration,
57
    /// Validation errors
58
    Validation,
59
    /// Critical errors requiring immediate attention
60
    Critical,
61
    /// Connection errors (data providers)
62
    Connection,
63
    /// Authentication errors
64
    Authentication,
65
    /// Rate limiting errors
66
    RateLimit,
67
    /// Data parsing errors
68
    Parse,
69
    /// Subscription errors
70
    Subscription,
71
    /// Financial safety and calculation errors
72
    FinancialSafety,
73
    /// Risk management and circuit breakers
74
    RiskManagement,
75
    /// Database and persistence layer
76
    Database,
77
    /// Broker connectivity and execution
78
    Broker,
79
    /// Machine learning and AI errors
80
    MachineLearning,
81
    /// Security and authentication errors
82
    Security,
83
    /// Business logic errors
84
    BusinessLogic,
85
    /// Resource errors (not found, conflicts)
86
    Resource,
87
    /// Development and testing errors
88
    Development,
89
    /// Risk management errors
90
    Risk,
91
    /// Machine learning errors (alias for MachineLearning)
92
    ML,
93
    /// Unknown/other errors
94
    Other,
95
}
96
97
impl fmt::Display for ErrorCategory {
98
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99
0
        match self {
100
0
            Self::MarketData => write!(f, "MARKET_DATA"),
101
0
            Self::Trading => write!(f, "TRADING"),
102
0
            Self::Network => write!(f, "NETWORK"),
103
0
            Self::System => write!(f, "SYSTEM"),
104
0
            Self::Configuration => write!(f, "CONFIGURATION"),
105
0
            Self::Validation => write!(f, "VALIDATION"),
106
0
            Self::Critical => write!(f, "CRITICAL"),
107
0
            Self::Connection => write!(f, "CONNECTION"),
108
0
            Self::Authentication => write!(f, "AUTHENTICATION"),
109
0
            Self::RateLimit => write!(f, "RATE_LIMIT"),
110
0
            Self::Parse => write!(f, "PARSE"),
111
0
            Self::Subscription => write!(f, "SUBSCRIPTION"),
112
0
            Self::FinancialSafety => write!(f, "FINANCIAL_SAFETY"),
113
0
            Self::RiskManagement => write!(f, "RISK_MANAGEMENT"),
114
0
            Self::Database => write!(f, "DATABASE"),
115
0
            Self::Broker => write!(f, "BROKER"),
116
0
            Self::MachineLearning => write!(f, "MACHINE_LEARNING"),
117
0
            Self::Security => write!(f, "SECURITY"),
118
0
            Self::BusinessLogic => write!(f, "BUSINESS_LOGIC"),
119
0
            Self::Resource => write!(f, "RESOURCE"),
120
0
            Self::Development => write!(f, "DEVELOPMENT"),
121
0
            Self::Risk => write!(f, "RISK"),
122
0
            Self::ML => write!(f, "ML"),
123
0
            Self::Other => write!(f, "OTHER"),
124
        }
125
0
    }
126
}
127
128
/// Error severity levels for prioritization and alerting
129
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
130
pub enum ErrorSeverity {
131
    /// Debug level - for development and troubleshooting
132
    Debug,
133
    /// Info level - informational messages
134
    Info,
135
    /// Warning level - potentially problematic situations
136
    Warn,
137
    /// Error level - error conditions that should be addressed
138
    Error,
139
    /// Critical level - serious error conditions requiring immediate attention
140
    Critical,
141
}
142
143
impl fmt::Display for ErrorSeverity {
144
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145
0
        match self {
146
0
            Self::Debug => write!(f, "DEBUG"),
147
0
            Self::Info => write!(f, "INFO"),
148
0
            Self::Warn => write!(f, "WARN"),
149
0
            Self::Error => write!(f, "ERROR"),
150
0
            Self::Critical => write!(f, "CRITICAL"),
151
        }
152
0
    }
153
}
154
155
/// Retry strategies for error recovery
156
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
157
pub enum RetryStrategy {
158
    /// Do not retry - error is permanent
159
    NoRetry,
160
    /// Retry immediately without delay
161
    Immediate,
162
    /// Linear backoff with fixed intervals
163
    Linear {
164
        /// Base delay in milliseconds between retries
165
        base_delay_ms: u64,
166
    },
167
    /// Exponential backoff with jitter
168
    Exponential {
169
        /// Base delay in milliseconds for exponential backoff
170
        base_delay_ms: u64,
171
        /// Maximum delay cap in milliseconds
172
        max_delay_ms: u64,
173
    },
174
    /// Wait for circuit breaker to close
175
    CircuitBreaker,
176
}
177
178
impl RetryStrategy {
179
    /// Calculate delay for retry attempt
180
    #[must_use]
181
19
    pub fn calculate_delay(&self, attempt: u32) -> Option<Duration> {
182
19
        match self {
183
3
            Self::NoRetry => None,
184
2
            Self::Immediate => Some(Duration::from_millis(0)),
185
4
            Self::Linear { base_delay_ms } => {
186
4
                Some(Duration::from_millis(base_delay_ms * u64::from(attempt)))
187
            },
188
            Self::Exponential {
189
8
                base_delay_ms,
190
8
                max_delay_ms,
191
            } => {
192
8
                let delay_ms = base_delay_ms * 2_u64.pow(attempt.min(10));
193
8
                let capped_delay = delay_ms.min(*max_delay_ms);
194
195
                // Add simple jitter (±10%)
196
8
                let jitter_ms = capped_delay / 10;
197
8
                let final_delay = capped_delay.saturating_sub(jitter_ms / 2);
198
199
8
                Some(Duration::from_millis(final_delay))
200
            },
201
2
            Self::CircuitBreaker => Some(Duration::from_secs(30)),
202
        }
203
19
    }
204
205
    /// Get maximum recommended retry attempts
206
    #[must_use]
207
0
    pub const fn max_attempts(&self) -> Option<u32> {
208
0
        match self {
209
0
            Self::NoRetry => Some(0),
210
0
            Self::Immediate => Some(3),
211
0
            Self::Linear { .. } => Some(5),
212
0
            Self::Exponential { .. } => Some(7),
213
0
            Self::CircuitBreaker => Some(1),
214
        }
215
0
    }
216
}
217
218
/// Convenience functions for creating common errors
219
impl CommonError {
220
    /// Create a configuration error
221
0
    pub fn config<S: Into<String>>(message: S) -> Self {
222
0
        Self::Configuration(message.into())
223
0
    }
224
225
    /// Create a network error
226
0
    pub fn network<S: Into<String>>(message: S) -> Self {
227
0
        Self::Network(message.into())
228
0
    }
229
230
    /// Create a service error with category
231
29
    pub fn service<S: Into<String>>(category: ErrorCategory, message: S) -> Self {
232
29
        Self::Service {
233
29
            category,
234
29
            message: message.into(),
235
29
        }
236
29
    }
237
238
    /// Create a validation error
239
0
    pub fn validation<S: Into<String>>(message: S) -> Self {
240
0
        Self::Validation(message.into())
241
0
    }
242
243
    /// Create a timeout error
244
0
    pub fn timeout(actual_ms: u64, max_ms: u64) -> Self {
245
0
        Self::Timeout { actual_ms, max_ms }
246
0
    }
247
248
    /// Create a machine learning specific service error
249
0
    pub fn ml<S: Into<String>, M: Into<String>>(model_name: S, message: M) -> Self {
250
0
        Self::Service {
251
0
            category: ErrorCategory::MachineLearning,
252
0
            message: format!("{}: {}", model_name.into(), message.into()),
253
0
        }
254
0
    }
255
256
    /// Create a serialization error
257
0
    pub fn serialization<S: Into<String>>(message: S) -> Self {
258
0
        Self::Service {
259
0
            category: ErrorCategory::Parse,
260
0
            message: format!("Serialization error: {}", message.into()),
261
0
        }
262
0
    }
263
264
    /// Create an internal error
265
0
    pub fn internal<S: Into<String>>(message: S) -> Self {
266
0
        Self::Service {
267
0
            category: ErrorCategory::System,
268
0
            message: format!("Internal error: {}", message.into()),
269
0
        }
270
0
    }
271
272
    /// Create a resource exhausted error
273
0
    pub fn resource_exhausted<S: Into<String>>(resource: S) -> Self {
274
0
        Self::Service {
275
0
            category: ErrorCategory::Resource,
276
0
            message: format!("Resource exhausted: {}", resource.into()),
277
0
        }
278
0
    }
279
280
    /// Get the error category for classification and metrics
281
0
    pub fn category(&self) -> ErrorCategory {
282
0
        match self {
283
0
            Self::Database(_) => ErrorCategory::Database,
284
0
            Self::Configuration(_) => ErrorCategory::Configuration,
285
0
            Self::Network(_) => ErrorCategory::Network,
286
0
            Self::Service { category, .. } => *category,
287
0
            Self::Validation(_) => ErrorCategory::Validation,
288
0
            Self::Timeout { .. } => ErrorCategory::System,
289
        }
290
0
    }
291
292
    /// Get error severity level
293
28
    pub fn severity(&self) -> ErrorSeverity {
294
28
        match self {
295
1
            Self::Database(_) => ErrorSeverity::Critical,
296
1
            Self::Configuration(_) => ErrorSeverity::Critical,
297
1
            Self::Network(_) => ErrorSeverity::Error,
298
23
            Self::Service { category, .. } => match category {
299
                ErrorCategory::Critical
300
                | ErrorCategory::FinancialSafety
301
3
                | ErrorCategory::Authentication => ErrorSeverity::Critical,
302
                ErrorCategory::Trading
303
                | ErrorCategory::RiskManagement
304
3
                | ErrorCategory::Database => ErrorSeverity::Error,
305
17
                _ => ErrorSeverity::Warn,
306
            },
307
1
            Self::Validation(_) => ErrorSeverity::Warn,
308
1
            Self::Timeout { .. } => ErrorSeverity::Error,
309
        }
310
28
    }
311
312
    /// Check if the error is retryable
313
11
    pub fn is_retryable(&self) -> bool {
314
11
        match self {
315
1
            Self::Database(_) => true,       // Database operations can be retried
316
1
            Self::Configuration(_) => false, // Configuration errors are permanent
317
1
            Self::Network(_) => true,        // Network errors are often transient
318
6
            Self::Service { category, .. } => !
matches!5
(
319
6
                category,
320
                ErrorCategory::Authentication
321
                    | ErrorCategory::Configuration
322
                    | ErrorCategory::Validation
323
            ),
324
1
            Self::Validation(_) => false, // Validation errors are permanent
325
1
            Self::Timeout { .. } => true, // Timeouts can be retried
326
        }
327
11
    }
328
329
    /// Get retry strategy for this error
330
11
    pub fn retry_strategy(&self) -> RetryStrategy {
331
11
        if !self.is_retryable() {
332
3
            return RetryStrategy::NoRetry;
333
8
        }
334
335
8
        match self {
336
1
            Self::Database(_) => RetryStrategy::Exponential {
337
1
                base_delay_ms: 1000,
338
1
                max_delay_ms: 10000,
339
1
            },
340
1
            Self::Network(_) => RetryStrategy::Linear { base_delay_ms: 500 },
341
5
            Self::Service { category, .. } => match category {
342
                ErrorCategory::Network | ErrorCategory::Connection => {
343
2
                    RetryStrategy::Linear { base_delay_ms: 500 }
344
                },
345
1
                ErrorCategory::RateLimit => RetryStrategy::Exponential {
346
1
                    base_delay_ms: 5000,
347
1
                    max_delay_ms: 60000,
348
1
                },
349
2
                _ => RetryStrategy::Immediate,
350
            },
351
1
            Self::Timeout { .. } => RetryStrategy::Linear {
352
1
                base_delay_ms: 1000,
353
1
            },
354
0
            _ => RetryStrategy::NoRetry,
355
        }
356
11
    }
357
}
358
359
/// Result type for common operations
360
pub type CommonResult<T> = Result<T, CommonError>;