Skip to main content

trader_bot/exchange/
error.rs

1//! Exchange-related error types.
2
3use thiserror::Error;
4
5/// Result type for exchange operations.
6pub type ExchangeResult<T> = Result<T, ExchangeError>;
7
8/// Errors that can occur when interacting with an exchange.
9#[derive(Debug, Error)]
10pub enum ExchangeError {
11    /// Authentication failed.
12    #[error("authentication failed: {0}")]
13    AuthenticationFailed(String),
14
15    /// Rate limit exceeded.
16    #[error("rate limit exceeded, retry after {retry_after_ms}ms")]
17    RateLimitExceeded {
18        /// Milliseconds to wait before retrying.
19        retry_after_ms: u64,
20    },
21
22    /// Network error.
23    #[error("network error: {0}")]
24    NetworkError(String),
25
26    /// Order rejected by the exchange.
27    #[error("order rejected: {0}")]
28    OrderRejected(String),
29
30    /// Insufficient funds.
31    #[error("insufficient funds: required {required}, available {available}")]
32    InsufficientFunds {
33        /// Required amount.
34        required: String,
35        /// Available amount.
36        available: String,
37    },
38
39    /// Instrument not found.
40    #[error("instrument not found: {0}")]
41    InstrumentNotFound(String),
42
43    /// Market is closed.
44    #[error("market closed for {instrument}")]
45    MarketClosed {
46        /// The instrument that's not tradeable.
47        instrument: String,
48    },
49
50    /// Invalid order parameters.
51    #[error("invalid order: {0}")]
52    InvalidOrder(String),
53
54    /// Order not found.
55    #[error("order not found: {0}")]
56    OrderNotFound(String),
57
58    /// Exchange returned an unexpected response.
59    #[error("unexpected response: {0}")]
60    UnexpectedResponse(String),
61
62    /// Configuration error.
63    #[error("configuration error: {0}")]
64    ConfigurationError(String),
65
66    /// Exchange-specific error.
67    #[error("exchange error [{code}]: {message}")]
68    ExchangeSpecific {
69        /// Error code from the exchange.
70        code: String,
71        /// Error message from the exchange.
72        message: String,
73    },
74
75    /// Operation timed out.
76    #[error("operation timed out after {0}ms")]
77    Timeout(u64),
78
79    /// Generic internal error.
80    #[error("internal error: {0}")]
81    Internal(String),
82}
83
84impl ExchangeError {
85    /// Returns true if the error is retryable.
86    #[must_use]
87    pub const fn is_retryable(&self) -> bool {
88        matches!(
89            self,
90            Self::RateLimitExceeded { .. } | Self::NetworkError(_) | Self::Timeout(_)
91        )
92    }
93
94    /// Returns the retry delay in milliseconds if applicable.
95    #[must_use]
96    pub const fn retry_delay_ms(&self) -> Option<u64> {
97        match self {
98            Self::RateLimitExceeded { retry_after_ms } => Some(*retry_after_ms),
99            Self::NetworkError(_) | Self::Timeout(_) => Some(1000), // Default 1s retry
100            _ => None,
101        }
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn test_retryable_errors() {
111        let rate_limit = ExchangeError::RateLimitExceeded {
112            retry_after_ms: 5000,
113        };
114        assert!(rate_limit.is_retryable());
115        assert_eq!(rate_limit.retry_delay_ms(), Some(5000));
116
117        let network = ExchangeError::NetworkError("connection reset".into());
118        assert!(network.is_retryable());
119        assert_eq!(network.retry_delay_ms(), Some(1000));
120
121        let rejected = ExchangeError::OrderRejected("invalid price".into());
122        assert!(!rejected.is_retryable());
123        assert_eq!(rejected.retry_delay_ms(), None);
124    }
125}