//! 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 { 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 { 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>(message: S) -> Self { Self::Configuration(message.into()) } /// Create a network error pub fn network>(message: S) -> Self { Self::Network(message.into()) } /// Create a service error with category pub fn service>(category: ErrorCategory, message: S) -> Self { Self::Service { category, message: message.into(), } } /// Create a validation error with field context pub fn validation, S: Into>(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>(message: S) -> Self { Self::Authentication(message.into()) } /// Create an authorization error pub fn authorization>(message: S) -> Self { Self::Authorization(message.into()) } /// Create a not found error pub fn not_found, I: Into>(resource: R, identifier: I) -> Self { Self::NotFound { resource: resource.into(), identifier: identifier.into(), } } /// Create a service unavailable error pub fn service_unavailable, R: Into>(service: S, reason: R) -> Self { Self::ServiceUnavailable { service: service.into(), reason: reason.into(), } } /// Create a rate limited error pub fn rate_limited>(limit_type: S) -> Self { Self::RateLimited { limit_type: limit_type.into(), } } /// Create a resource exhausted error pub fn resource_exhausted>(resource: S) -> Self { Self::ResourceExhausted { resource: resource.into(), } } /// Create a serialization error pub fn serialization>(message: S) -> Self { Self::Serialization(message.into()) } /// Create an internal error pub fn internal>(message: S) -> Self { Self::Internal(message.into()) } /// Create a connection error pub fn connection, R: Into>(endpoint: E, reason: R) -> Self { Self::Connection { endpoint: endpoint.into(), reason: reason.into(), } } /// Create a trading error pub fn trading>(message: S) -> Self { Self::Trading(message.into()) } /// Create an ML error pub fn ml, S: Into>(model: M, message: S) -> Self { Self::ML { model: model.into(), message: message.into(), } } /// Create a risk error pub fn risk, S: Into>(risk_type: T, message: S) -> Self { Self::Risk { risk_type: risk_type.into(), message: message.into(), } } } /// Result type for common operations pub type CommonResult = Result; /// Conversion from standard library errors impl From for CommonError { fn from(err: std::io::Error) -> Self { Self::Network(format!("IO error: {}", err)) } } impl From for CommonError { fn from(err: serde_json::Error) -> Self { Self::Serialization(format!("JSON error: {}", err)) } } impl From for CommonError { fn from(err: reqwest::Error) -> Self { Self::Network(format!("HTTP error: {}", err)) } } /// gRPC Status conversion for TLI service impl From 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); } }