1use std::io;
5use thiserror::Error;
6
7#[derive(Error, Debug)]
9pub enum MonocoqueError {
10 #[error("IO error: {0}")]
12 Io(#[from] io::Error),
13
14 #[error("Protocol error: {0}")]
16 Protocol(String),
17
18 #[error("Handshake timeout after {0:?}")]
20 HandshakeTimeout(std::time::Duration),
21
22 #[error("Invalid greeting: {0}")]
24 InvalidGreeting(String),
25
26 #[error("Invalid frame: {0}")]
28 InvalidFrame(String),
29
30 #[error("Socket closed")]
32 SocketClosed,
33
34 #[error("Channel send error")]
36 ChannelSend,
37
38 #[error("Channel receive error")]
40 ChannelRecv,
41
42 #[error("Peer disconnected: {0}")]
44 PeerDisconnected(String),
45
46 #[error("Invalid routing ID")]
48 InvalidRoutingId,
49
50 #[error("Message too large: {size} bytes (max: {max})")]
52 MessageTooLarge { size: usize, max: usize },
53
54 #[error("Subscription error: {0}")]
56 Subscription(String),
57}
58
59pub type Result<T> = std::result::Result<T, MonocoqueError>;
61
62pub trait ResultExt<T> {
64 fn context(self, context: impl Into<String>) -> Result<T>;
66
67 fn with_context<F>(self, f: F) -> Result<T>
69 where
70 F: FnOnce() -> String;
71}
72
73impl<T> ResultExt<T> for Result<T> {
74 fn context(self, context: impl Into<String>) -> Self {
75 self.map_err(|e| {
76 let ctx = context.into();
77 match e {
78 MonocoqueError::Io(io_err) => {
79 MonocoqueError::Io(io::Error::new(io_err.kind(), format!("{ctx}: {io_err}")))
80 }
81 MonocoqueError::Protocol(msg) => MonocoqueError::Protocol(format!("{ctx}: {msg}")),
82 other => other,
83 }
84 })
85 }
86
87 fn with_context<F>(self, f: F) -> Self
88 where
89 F: FnOnce() -> String,
90 {
91 self.map_err(|e| {
92 let ctx = f();
93 match e {
94 MonocoqueError::Io(io_err) => {
95 MonocoqueError::Io(io::Error::new(io_err.kind(), format!("{ctx}: {io_err}")))
96 }
97 MonocoqueError::Protocol(msg) => MonocoqueError::Protocol(format!("{ctx}: {msg}")),
98 other => other,
99 }
100 })
101 }
102}
103
104impl MonocoqueError {
105 pub fn protocol(msg: impl Into<String>) -> Self {
107 Self::Protocol(msg.into())
108 }
109
110 pub fn invalid_greeting(msg: impl Into<String>) -> Self {
112 Self::InvalidGreeting(msg.into())
113 }
114
115 pub fn invalid_frame(msg: impl Into<String>) -> Self {
117 Self::InvalidFrame(msg.into())
118 }
119
120 pub fn peer_disconnected(peer_id: impl Into<String>) -> Self {
122 Self::PeerDisconnected(peer_id.into())
123 }
124
125 #[must_use]
127 pub fn is_recoverable(&self) -> bool {
128 match self {
129 Self::Io(e) => matches!(
130 e.kind(),
131 io::ErrorKind::Interrupted | io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut
132 ),
133 Self::HandshakeTimeout(_) | Self::ChannelSend | Self::ChannelRecv => false,
134 _ => false,
135 }
136 }
137
138 #[must_use]
140 pub const fn is_connection_error(&self) -> bool {
141 matches!(
142 self,
143 Self::SocketClosed | Self::PeerDisconnected(_) | Self::HandshakeTimeout(_)
144 )
145 }
146}