use std::io;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum MonocoqueError {
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error("Protocol error: {0}")]
Protocol(String),
#[error("Handshake timeout after {0:?}")]
HandshakeTimeout(std::time::Duration),
#[error("Invalid greeting: {0}")]
InvalidGreeting(String),
#[error("Invalid frame: {0}")]
InvalidFrame(String),
#[error("Socket closed")]
SocketClosed,
#[error("Channel send error")]
ChannelSend,
#[error("Channel receive error")]
ChannelRecv,
#[error("Peer disconnected: {0}")]
PeerDisconnected(String),
#[error("Invalid routing ID")]
InvalidRoutingId,
#[error("Message too large: {size} bytes (max: {max})")]
MessageTooLarge { size: usize, max: usize },
#[error("Subscription error: {0}")]
Subscription(String),
}
pub type Result<T> = std::result::Result<T, MonocoqueError>;
pub trait ResultExt<T> {
fn context(self, context: impl Into<String>) -> Result<T>;
fn with_context<F>(self, f: F) -> Result<T>
where
F: FnOnce() -> String;
}
impl<T> ResultExt<T> for Result<T> {
fn context(self, context: impl Into<String>) -> Self {
self.map_err(|e| {
let ctx = context.into();
match e {
MonocoqueError::Io(io_err) => {
MonocoqueError::Io(io::Error::new(io_err.kind(), format!("{ctx}: {io_err}")))
}
MonocoqueError::Protocol(msg) => MonocoqueError::Protocol(format!("{ctx}: {msg}")),
other => other,
}
})
}
fn with_context<F>(self, f: F) -> Self
where
F: FnOnce() -> String,
{
self.map_err(|e| {
let ctx = f();
match e {
MonocoqueError::Io(io_err) => {
MonocoqueError::Io(io::Error::new(io_err.kind(), format!("{ctx}: {io_err}")))
}
MonocoqueError::Protocol(msg) => MonocoqueError::Protocol(format!("{ctx}: {msg}")),
other => other,
}
})
}
}
impl MonocoqueError {
pub fn protocol(msg: impl Into<String>) -> Self {
Self::Protocol(msg.into())
}
pub fn invalid_greeting(msg: impl Into<String>) -> Self {
Self::InvalidGreeting(msg.into())
}
pub fn invalid_frame(msg: impl Into<String>) -> Self {
Self::InvalidFrame(msg.into())
}
pub fn peer_disconnected(peer_id: impl Into<String>) -> Self {
Self::PeerDisconnected(peer_id.into())
}
#[must_use]
pub fn is_recoverable(&self) -> bool {
match self {
Self::Io(e) => matches!(
e.kind(),
io::ErrorKind::Interrupted | io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut
),
Self::HandshakeTimeout(_) | Self::ChannelSend | Self::ChannelRecv => false,
_ => false,
}
}
#[must_use]
pub const fn is_connection_error(&self) -> bool {
matches!(
self,
Self::SocketClosed | Self::PeerDisconnected(_) | Self::HandshakeTimeout(_)
)
}
}