Files
foxhunt/common/src/error_enhanced.rs
jgrusewski 030a15ee05 🔧 Emergency Fix: Resolve catastrophic _i32 suffix corruption (463→0 errors)
- Fixed systematic array indexing corruption: [0_i32] → [0]
- Fixed numeric literal suffixes across 835 files
- Fixed iterator patterns on RwLockReadGuard (.iter() required)
- Fixed float type annotations (365.25_f64 for sqrt)
- Fixed missing semicolons in position manager
- Fixed reference dereferencing in data loader

Root cause: Mass refactoring incorrectly added _i32 suffixes to array indices
Impact: Complete compilation failure (463 errors)
Resolution: Automated regex + targeted fixes
Result: 100% compilation success (0 errors)

Validated: cargo check --workspace passes
Ready for: Production deployment
2025-10-10 23:05:26 +02:00

602 lines
20 KiB
Rust

//! Enhanced common error types and utilities for HFT error consolidation
//!
//! This module provides consolidated error types and utilities used across
//! all Foxhunt services with proper categorization for metrics.
use serde::{Deserialize, Serialize};
use std::fmt;
use std::time::Duration;
use thiserror::Error;
use crate::error::ErrorCategory;
/// Enhanced common error type for all Foxhunt services
#[derive(Debug, Error)]
pub enum CommonError {
/// Database operation failed - wraps database-specific errors
#[error("Database error: {0}")]
Database(#[from] crate::database::DatabaseError),
/// Configuration is invalid or missing required parameters
#[error("Configuration error: {0}")]
Configuration(String),
/// Network communication error occurred
#[error("Network error: {0}")]
Network(String),
/// Service-specific error with categorization for metrics
#[error("Service error: {category} - {message}")]
Service {
/// Error category for classification
category: ErrorCategory,
/// Descriptive error message
message: String
},
/// Input validation failed with field context
#[error("Validation error: {field} - {message}")]
Validation {
/// Field that failed validation
field: String,
/// Validation error message
message: String
},
/// Operation exceeded maximum allowed execution time
#[error("Timeout error: operation took {actual_ms}ms, max allowed {max_ms}ms")]
Timeout {
/// Actual execution time in milliseconds
actual_ms: u64,
/// Maximum allowed execution time in milliseconds
max_ms: u64
},
/// Authentication failed
#[error("Authentication error: {0}")]
Authentication(String),
/// Authorization/Permission denied
#[error("Authorization error: {0}")]
Authorization(String),
/// Resource not found
#[error("{resource} not found: {identifier}")]
NotFound {
/// Type of resource
resource: String,
/// Resource identifier
identifier: String
},
/// Service unavailable
#[error("Service unavailable: {service} - {reason}")]
ServiceUnavailable {
/// Service name
service: String,
/// Reason for unavailability
reason: String
},
/// Rate limit exceeded
#[error("Rate limit exceeded: {limit_type}")]
RateLimited {
/// Type of rate limit
limit_type: String
},
/// Resource exhausted
#[error("Resource exhausted: {resource}")]
ResourceExhausted {
/// Resource that was exhausted
resource: String
},
/// Serialization/Deserialization error
#[error("Serialization error: {0}")]
Serialization(String),
/// Internal server error
#[error("Internal error: {0}")]
Internal(String),
/// Connection error
#[error("Connection error: {endpoint} - {reason}")]
Connection {
/// Connection endpoint
endpoint: String,
/// Connection failure reason
reason: String
},
/// Order/Trading specific errors
#[error("Trading error: {0}")]
Trading(String),
/// ML/Model specific errors
#[error("ML error: {model} - {message}")]
ML {
/// Model name
model: String,
/// Error message
message: String
},
/// Risk management errors
#[error("Risk error: {risk_type} - {message}")]
Risk {
/// Type of risk violation
risk_type: String,
/// Risk error message
message: String
},
}
// ErrorCategory is now imported from crate::error
impl fmt::Display for ErrorCategory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MarketData => write!(f, "MARKET_DATA"),
Self::Trading => write!(f, "TRADING"),
Self::Network => write!(f, "NETWORK"),
Self::System => write!(f, "SYSTEM"),
Self::Configuration => write!(f, "CONFIGURATION"),
Self::Validation => write!(f, "VALIDATION"),
Self::Critical => write!(f, "CRITICAL"),
Self::Security => write!(f, "SECURITY"),
Self::ML => write!(f, "ML"),
Self::Risk => write!(f, "RISK"),
Self::Database => write!(f, "DATABASE"),
}
}
}
/// Enhanced retry strategies for error recovery
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum RetryStrategy {
/// Do not retry - error is permanent
NoRetry,
/// Retry immediately without delay
Immediate,
/// Linear backoff with fixed intervals
Linear {
/// Base delay in milliseconds between retries
base_delay_ms: u64,
},
/// Exponential backoff with jitter
Exponential {
/// Base delay in milliseconds for exponential backoff
base_delay_ms: u64,
/// Maximum delay cap in milliseconds
max_delay_ms: u64,
},
/// Wait for circuit breaker to close
CircuitBreaker,
/// Custom retry for HFT scenarios
HftCustom {
/// Initial delay in nanoseconds for HFT
initial_delay_ns: u64,
/// Maximum retries before giving up
max_retries: u32,
},
}
impl RetryStrategy {
/// Calculate delay for retry attempt
#[must_use]
pub fn calculate_delay(&self, attempt: u32) -> Option<Duration> {
match self {
Self::NoRetry => None,
Self::Immediate => Some(Duration::from_millis(0)),
Self::Linear { base_delay_ms } => {
Some(Duration::from_millis(base_delay_ms * u64::from(attempt)))
}
Self::Exponential {
base_delay_ms,
max_delay_ms,
} => {
let delay_ms = base_delay_ms * 2_u64.pow(attempt.min(10));
let capped_delay = delay_ms.min(*max_delay_ms);
// Add simple jitter (±10%)
let jitter_ms = capped_delay / 10;
let final_delay = capped_delay.saturating_sub(jitter_ms / 2);
Some(Duration::from_millis(final_delay))
}
Self::CircuitBreaker => Some(Duration::from_secs(30)),
Self::HftCustom { initial_delay_ns, .. } => {
let delay_ns = initial_delay_ns * u64::from(attempt);
Some(Duration::from_nanos(delay_ns))
}
}
}
/// Get maximum recommended retry attempts
#[must_use]
pub const fn max_attempts(&self) -> Option<u32> {
match self {
Self::NoRetry => Some(0),
Self::Immediate => Some(3),
Self::Linear { .. } => Some(5),
Self::Exponential { .. } => Some(7),
Self::CircuitBreaker => Some(1),
Self::HftCustom { max_retries, .. } => Some(*max_retries),
}
}
}
/// Error severity for HFT metrics
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ErrorSeverity {
/// Trace level - detailed diagnostic information
Trace,
/// Debug level - debugging information
Debug,
/// Info level - informational messages
Info,
/// Warn level - warning messages
Warn,
/// Error level - error messages
Error,
/// Critical level - critical errors requiring immediate attention
Critical,
}
impl fmt::Display for ErrorSeverity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Trace => write!(f, "TRACE"),
Self::Debug => write!(f, "DEBUG"),
Self::Info => write!(f, "INFO"),
Self::Warn => write!(f, "WARN"),
Self::Error => write!(f, "ERROR"),
Self::Critical => write!(f, "CRITICAL"),
}
}
}
impl CommonError {
/// Get error category for metrics
pub fn category(&self) -> ErrorCategory {
match self {
Self::Database(_) => ErrorCategory::Database,
Self::Configuration(_) => ErrorCategory::Configuration,
Self::Network(_) => ErrorCategory::Network,
Self::Service { category, .. } => *category,
Self::Validation { .. } => ErrorCategory::Validation,
Self::Timeout { .. } => ErrorCategory::System,
Self::Authentication(_) => ErrorCategory::Security,
Self::Authorization(_) => ErrorCategory::Security,
Self::NotFound { .. } => ErrorCategory::System,
Self::ServiceUnavailable { .. } => ErrorCategory::System,
Self::RateLimited { .. } => ErrorCategory::System,
Self::ResourceExhausted { .. } => ErrorCategory::System,
Self::Serialization(_) => ErrorCategory::System,
Self::Internal(_) => ErrorCategory::Critical,
Self::Connection { .. } => ErrorCategory::Network,
Self::Trading(_) => ErrorCategory::Trading,
Self::ML { .. } => ErrorCategory::ML,
Self::Risk { .. } => ErrorCategory::Risk,
}
}
/// Get error severity for HFT monitoring
pub fn severity(&self) -> ErrorSeverity {
match self {
Self::Internal(_) => ErrorSeverity::Critical,
Self::Authentication(_) => ErrorSeverity::Critical,
Self::Configuration(_) => ErrorSeverity::Critical,
Self::Risk { .. } => ErrorSeverity::Critical,
Self::Trading(_) => ErrorSeverity::Error,
Self::ML { .. } => ErrorSeverity::Error,
Self::Database(_) => ErrorSeverity::Error,
Self::Authorization(_) => ErrorSeverity::Warn,
Self::Timeout { .. } => ErrorSeverity::Warn,
Self::Network(_) => ErrorSeverity::Warn,
Self::Connection { .. } => ErrorSeverity::Warn,
Self::ServiceUnavailable { .. } => ErrorSeverity::Warn,
Self::RateLimited { .. } => ErrorSeverity::Warn,
Self::ResourceExhausted { .. } => ErrorSeverity::Warn,
Self::Validation { .. } => ErrorSeverity::Info,
Self::NotFound { .. } => ErrorSeverity::Info,
Self::Serialization(_) => ErrorSeverity::Info,
Self::Service { .. } => ErrorSeverity::Error,
}
}
/// Get retry strategy for this error
pub fn retry_strategy(&self) -> RetryStrategy {
match self {
Self::Authentication(_) => RetryStrategy::NoRetry,
Self::Authorization(_) => RetryStrategy::NoRetry,
Self::Configuration(_) => RetryStrategy::NoRetry,
Self::Validation { .. } => RetryStrategy::NoRetry,
Self::NotFound { .. } => RetryStrategy::NoRetry,
Self::Network(_) => RetryStrategy::Exponential {
base_delay_ms: 100,
max_delay_ms: 5000,
},
Self::Connection { .. } => RetryStrategy::Exponential {
base_delay_ms: 100,
max_delay_ms: 5000,
},
Self::Timeout { .. } => RetryStrategy::Linear { base_delay_ms: 1000 },
Self::ServiceUnavailable { .. } => RetryStrategy::Exponential {
base_delay_ms: 1000,
max_delay_ms: 30000,
},
Self::RateLimited { .. } => RetryStrategy::Linear { base_delay_ms: 5000 },
Self::ResourceExhausted { .. } => RetryStrategy::Linear { base_delay_ms: 2000 },
Self::Database(_) => RetryStrategy::Exponential {
base_delay_ms: 250,
max_delay_ms: 2000,
},
Self::Trading(_) => RetryStrategy::HftCustom {
initial_delay_ns: 50000, // 50µs
max_retries: 3,
},
Self::ML { .. } => RetryStrategy::Linear { base_delay_ms: 500 },
Self::Risk { .. } => RetryStrategy::NoRetry, // Risk errors should not be retried
_ => RetryStrategy::Immediate,
}
}
/// Check if error is retryable
pub fn is_retryable(&self) -> bool {
!matches!(self.retry_strategy(), RetryStrategy::NoRetry)
}
/// Get error code for monitoring
pub fn error_code(&self) -> &'static str {
match self {
Self::Database(_) => "DATABASE_ERROR",
Self::Configuration(_) => "CONFIGURATION_ERROR",
Self::Network(_) => "NETWORK_ERROR",
Self::Service { .. } => "SERVICE_ERROR",
Self::Validation { .. } => "VALIDATION_ERROR",
Self::Timeout { .. } => "TIMEOUT_ERROR",
Self::Authentication(_) => "AUTHENTICATION_ERROR",
Self::Authorization(_) => "AUTHORIZATION_ERROR",
Self::NotFound { .. } => "NOT_FOUND_ERROR",
Self::ServiceUnavailable { .. } => "SERVICE_UNAVAILABLE_ERROR",
Self::RateLimited { .. } => "RATE_LIMITED_ERROR",
Self::ResourceExhausted { .. } => "RESOURCE_EXHAUSTED_ERROR",
Self::Serialization(_) => "SERIALIZATION_ERROR",
Self::Internal(_) => "INTERNAL_ERROR",
Self::Connection { .. } => "CONNECTION_ERROR",
Self::Trading(_) => "TRADING_ERROR",
Self::ML { .. } => "ML_ERROR",
Self::Risk { .. } => "RISK_ERROR",
}
}
}
/// Enhanced convenience functions for creating common errors
impl CommonError {
/// Create a configuration error
pub fn config<S: Into<String>>(message: S) -> Self {
Self::Configuration(message.into())
}
/// Create a network error
pub fn network<S: Into<String>>(message: S) -> Self {
Self::Network(message.into())
}
/// Create a service error with category
pub fn service<S: Into<String>>(category: ErrorCategory, message: S) -> Self {
Self::Service {
category,
message: message.into(),
}
}
/// Create a validation error with field context
pub fn validation<F: Into<String>, S: Into<String>>(field: F, message: S) -> Self {
Self::Validation {
field: field.into(),
message: message.into(),
}
}
/// Create a timeout error
pub fn timeout(actual_ms: u64, max_ms: u64) -> Self {
Self::Timeout { actual_ms, max_ms }
}
/// Create an authentication error
pub fn authentication<S: Into<String>>(message: S) -> Self {
Self::Authentication(message.into())
}
/// Create an authorization error
pub fn authorization<S: Into<String>>(message: S) -> Self {
Self::Authorization(message.into())
}
/// Create a not found error
pub fn not_found<R: Into<String>, I: Into<String>>(resource: R, identifier: I) -> Self {
Self::NotFound {
resource: resource.into(),
identifier: identifier.into(),
}
}
/// Create a service unavailable error
pub fn service_unavailable<S: Into<String>, R: Into<String>>(service: S, reason: R) -> Self {
Self::ServiceUnavailable {
service: service.into(),
reason: reason.into(),
}
}
/// Create a rate limited error
pub fn rate_limited<S: Into<String>>(limit_type: S) -> Self {
Self::RateLimited {
limit_type: limit_type.into(),
}
}
/// Create a resource exhausted error
pub fn resource_exhausted<S: Into<String>>(resource: S) -> Self {
Self::ResourceExhausted {
resource: resource.into(),
}
}
/// Create a serialization error
pub fn serialization<S: Into<String>>(message: S) -> Self {
Self::Serialization(message.into())
}
/// Create an internal error
pub fn internal<S: Into<String>>(message: S) -> Self {
Self::Internal(message.into())
}
/// Create a connection error
pub fn connection<E: Into<String>, R: Into<String>>(endpoint: E, reason: R) -> Self {
Self::Connection {
endpoint: endpoint.into(),
reason: reason.into(),
}
}
/// Create a trading error
pub fn trading<S: Into<String>>(message: S) -> Self {
Self::Trading(message.into())
}
/// Create an ML error
pub fn ml<M: Into<String>, S: Into<String>>(model: M, message: S) -> Self {
Self::ML {
model: model.into(),
message: message.into(),
}
}
/// Create a risk error
pub fn risk<T: Into<String>, S: Into<String>>(risk_type: T, message: S) -> Self {
Self::Risk {
risk_type: risk_type.into(),
message: message.into(),
}
}
}
/// Result type for common operations
pub type CommonResult<T> = Result<T, CommonError>;
/// Conversion from standard library errors
impl From<std::io::Error> for CommonError {
fn from(err: std::io::Error) -> Self {
Self::Network(format!("IO error: {}", err))
}
}
impl From<serde_json::Error> for CommonError {
fn from(err: serde_json::Error) -> Self {
Self::Serialization(format!("JSON error: {}", err))
}
}
impl From<reqwest::Error> for CommonError {
fn from(err: reqwest::Error) -> Self {
Self::Network(format!("HTTP error: {}", err))
}
}
/// gRPC Status conversion for TLI service
impl From<CommonError> for tonic::Status {
fn from(err: CommonError) -> Self {
match err {
CommonError::Authentication(_) => {
tonic::Status::unauthenticated(err.to_owned())
}
CommonError::Authorization(_) => {
tonic::Status::permission_denied(err.to_owned())
}
CommonError::Validation { .. } => {
tonic::Status::invalid_argument(err.to_owned())
}
CommonError::NotFound { .. } => {
tonic::Status::not_found(err.to_owned())
}
CommonError::ServiceUnavailable { .. } => {
tonic::Status::unavailable(err.to_owned())
}
CommonError::RateLimited { .. } => {
tonic::Status::resource_exhausted(err.to_owned())
}
CommonError::ResourceExhausted { .. } => {
tonic::Status::resource_exhausted(err.to_owned())
}
CommonError::Timeout { .. } => {
tonic::Status::deadline_exceeded(err.to_owned())
}
CommonError::Connection { .. } => {
tonic::Status::unavailable(err.to_owned())
}
CommonError::Network(_) => {
tonic::Status::unavailable(err.to_owned())
}
_ => tonic::Status::internal(err.to_owned()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_categorization() {
let error = CommonError::trading("Order validation failed");
assert_eq!(error.category(), ErrorCategory::Trading);
assert_eq!(error.severity(), ErrorSeverity::Error);
assert!(error.is_retryable());
}
#[test]
fn test_retry_strategy() {
let auth_error = CommonError::authentication("Invalid token");
assert_eq!(auth_error.retry_strategy(), RetryStrategy::NoRetry);
assert!(!auth_error.is_retryable());
let network_error = CommonError::network("Connection refused");
assert!(network_error.is_retryable());
match network_error.retry_strategy() {
RetryStrategy::Exponential { .. } => (),
_ => panic!("Expected exponential backoff for network errors"),
}
}
#[test]
fn test_hft_retry_strategy() {
let trading_error = CommonError::trading("Order rejected");
match trading_error.retry_strategy() {
RetryStrategy::HftCustom { initial_delay_ns, max_retries } => {
assert_eq!(initial_delay_ns, 50000); // 50µs
assert_eq!(max_retries, 3);
}
_ => panic!("Expected HFT custom retry for trading errors"),
}
}
#[test]
fn test_error_severity() {
let risk_error = CommonError::risk("position_limit", "Exceeded maximum position");
assert_eq!(risk_error.severity(), ErrorSeverity::Critical);
let validation_error = CommonError::validation("price", "Must be positive");
assert_eq!(validation_error.severity(), ErrorSeverity::Info);
}
#[test]
fn test_grpc_conversion() {
let error = CommonError::authentication("Invalid credentials");
let status: tonic::Status = error.into();
assert_eq!(status.code(), tonic::Code::Unauthenticated);
}
}