use thiserror::Error;
pub type ExchangeResult<T> = Result<T, ExchangeError>;
#[derive(Debug, Error)]
pub enum ExchangeError {
#[error("authentication failed: {0}")]
AuthenticationFailed(String),
#[error("rate limit exceeded, retry after {retry_after_ms}ms")]
RateLimitExceeded {
retry_after_ms: u64,
},
#[error("network error: {0}")]
NetworkError(String),
#[error("order rejected: {0}")]
OrderRejected(String),
#[error("insufficient funds: required {required}, available {available}")]
InsufficientFunds {
required: String,
available: String,
},
#[error("instrument not found: {0}")]
InstrumentNotFound(String),
#[error("market closed for {instrument}")]
MarketClosed {
instrument: String,
},
#[error("invalid order: {0}")]
InvalidOrder(String),
#[error("order not found: {0}")]
OrderNotFound(String),
#[error("unexpected response: {0}")]
UnexpectedResponse(String),
#[error("configuration error: {0}")]
ConfigurationError(String),
#[error("exchange error [{code}]: {message}")]
ExchangeSpecific {
code: String,
message: String,
},
#[error("operation timed out after {0}ms")]
Timeout(u64),
#[error("internal error: {0}")]
Internal(String),
}
impl ExchangeError {
#[must_use]
pub const fn is_retryable(&self) -> bool {
matches!(
self,
Self::RateLimitExceeded { .. } | Self::NetworkError(_) | Self::Timeout(_)
)
}
#[must_use]
pub const fn retry_delay_ms(&self) -> Option<u64> {
match self {
Self::RateLimitExceeded { retry_after_ms } => Some(*retry_after_ms),
Self::NetworkError(_) | Self::Timeout(_) => Some(1000), _ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_retryable_errors() {
let rate_limit = ExchangeError::RateLimitExceeded {
retry_after_ms: 5000,
};
assert!(rate_limit.is_retryable());
assert_eq!(rate_limit.retry_delay_ms(), Some(5000));
let network = ExchangeError::NetworkError("connection reset".into());
assert!(network.is_retryable());
assert_eq!(network.retry_delay_ms(), Some(1000));
let rejected = ExchangeError::OrderRejected("invalid price".into());
assert!(!rejected.is_retryable());
assert_eq!(rejected.retry_delay_ms(), None);
}
}