- 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
602 lines
20 KiB
Rust
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);
|
|
}
|
|
} |