#![allow(dead_code)]
use std::io::ErrorKind;
use crate::errors::Error;
pub(crate) const MAX_RETRIES: u32 = 3;
pub(crate) fn is_connection_error(error: &Error) -> bool {
match error {
Error::Io(io_err) => matches!(
io_err.kind(),
ErrorKind::ConnectionReset | ErrorKind::ConnectionAborted | ErrorKind::UnexpectedEof | ErrorKind::BrokenPipe
),
Error::ConnectionReset | Error::ConnectionFailed => true,
_ => false,
}
}
pub(crate) fn is_timeout_error(error: &Error) -> bool {
match error {
Error::Io(io_err) => matches!(io_err.kind(), ErrorKind::WouldBlock | ErrorKind::TimedOut),
_ => false,
}
}
pub(crate) fn should_retry_request(error: &Error, retry_count: u32) -> bool {
retry_count < MAX_RETRIES && (is_connection_error(error) || is_transient_error(error))
}
pub(crate) fn is_transient_error(error: &Error) -> bool {
match error {
Error::UnexpectedResponse(_) => true,
Error::Io(io_err) => matches!(io_err.kind(), ErrorKind::Interrupted | ErrorKind::WouldBlock | ErrorKind::TimedOut),
_ => false,
}
}
pub(crate) fn is_fatal_error(error: &Error) -> bool {
matches!(
error,
Error::Shutdown | Error::InvalidArgument(_) | Error::NotImplemented | Error::ServerVersion(_, _, _) | Error::AlreadySubscribed
)
}
pub(crate) fn error_message(error: &Error) -> String {
match error {
Error::ConnectionFailed => "Connection to TWS/Gateway failed".to_string(),
Error::ConnectionReset => "Connection was reset by TWS/Gateway".to_string(),
Error::Shutdown => "Client is shutting down".to_string(),
Error::Cancelled => "Operation was cancelled".to_string(),
Error::EndOfStream => "No more data available".to_string(),
Error::ServerVersion(required, actual, feature) => {
format!("Server version {required} required for {feature}, but connected to version {actual}")
}
Error::Notice(n) => format!("TWS Error [{}]: {}", n.code, n.message),
_ => error.to_string(),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ErrorCategory {
Connection,
Parsing,
Validation,
ServerError,
Timeout,
Cancelled,
Fatal,
Transient,
}
pub(crate) fn categorize_error(error: &Error) -> ErrorCategory {
match error {
Error::ConnectionFailed | Error::ConnectionReset => ErrorCategory::Connection,
Error::Io(io_err) if is_connection_io_error(io_err) => ErrorCategory::Connection,
Error::Io(io_err) if is_timeout_io_error(io_err) => ErrorCategory::Timeout,
Error::Parse(_, _, _) | Error::ParseInt(_) | Error::FromUtf8(_) | Error::ParseTime(_) => ErrorCategory::Parsing,
Error::InvalidArgument(_) | Error::ServerVersion(_, _, _) => ErrorCategory::Validation,
Error::Notice(_) => ErrorCategory::ServerError,
Error::Cancelled => ErrorCategory::Cancelled,
Error::Shutdown | Error::NotImplemented | Error::AlreadySubscribed => ErrorCategory::Fatal,
Error::UnexpectedResponse(_) | Error::UnexpectedEndOfStream => ErrorCategory::Transient,
_ => ErrorCategory::Transient,
}
}
fn is_connection_io_error(io_err: &std::io::Error) -> bool {
matches!(
io_err.kind(),
ErrorKind::ConnectionReset | ErrorKind::ConnectionAborted | ErrorKind::UnexpectedEof | ErrorKind::BrokenPipe | ErrorKind::ConnectionRefused
)
}
fn is_timeout_io_error(io_err: &std::io::Error) -> bool {
matches!(io_err.kind(), ErrorKind::WouldBlock | ErrorKind::TimedOut)
}
#[cfg(test)]
mod tests;