pub mod sync;
pub(crate) mod orders;
pub(crate) mod recovery;
use crate::websocket::error::Error as WsError;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub(crate) enum CoinApiBookError {
#[error("{0}")]
Retryable(String),
#[error("{0}")]
Permanent(String),
}
impl CoinApiBookError {
pub(crate) fn is_retryable(&self) -> bool {
matches!(self, Self::Retryable(_))
}
}
impl From<WsError> for CoinApiBookError {
fn from(error: WsError) -> Self {
match error {
WsError::Validation(_) => Self::Permanent(error.to_string()),
WsError::Transport(_) | WsError::Send(_) => Self::Retryable(error.to_string()),
}
}
}
#[cfg(test)]
mod tests {
use nautilus_network::{TransportError, error::SendError};
use rstest::rstest;
use super::*;
#[rstest]
#[case(
WsError::Validation("book pair is not subscribed".into()),
CoinApiBookError::Permanent(
"Invalid CoinAPI WebSocket request: book pair is not subscribed".into()
),
false
)]
#[case(
WsError::Transport(TransportError::ConnectionReset),
CoinApiBookError::Retryable("connection reset".into()),
true
)]
#[case(
WsError::Send(SendError::BrokenPipe("writer closed".into())),
CoinApiBookError::Retryable("send failed: broken pipe (writer closed)".into()),
true
)]
fn websocket_errors_preserve_context_and_retry_classification(
#[case] error: WsError,
#[case] expected: CoinApiBookError,
#[case] retryable: bool,
) {
let mapped = CoinApiBookError::from(error);
assert_eq!(mapped, expected);
assert_eq!(mapped.is_retryable(), retryable);
}
}