Skip to main content

monocoque_core/
error.rs

1/// Monocoque Error Types
2///
3/// Comprehensive error handling for all Monocoque operations.
4use std::io;
5use thiserror::Error;
6
7/// Main error type for Monocoque operations
8#[derive(Error, Debug)]
9pub enum MonocoqueError {
10    /// IO error during socket operations
11    #[error("IO error: {0}")]
12    Io(#[from] io::Error),
13
14    /// Protocol error during ZMTP handshake or framing
15    #[error("Protocol error: {0}")]
16    Protocol(String),
17
18    /// Handshake timeout
19    #[error("Handshake timeout after {0:?}")]
20    HandshakeTimeout(std::time::Duration),
21
22    /// Invalid greeting received
23    #[error("Invalid greeting: {0}")]
24    InvalidGreeting(String),
25
26    /// Invalid frame format
27    #[error("Invalid frame: {0}")]
28    InvalidFrame(String),
29
30    /// Socket closed
31    #[error("Socket closed")]
32    SocketClosed,
33
34    /// Channel send error
35    #[error("Channel send error")]
36    ChannelSend,
37
38    /// Channel receive error
39    #[error("Channel receive error")]
40    ChannelRecv,
41
42    /// Peer disconnected
43    #[error("Peer disconnected: {0}")]
44    PeerDisconnected(String),
45
46    /// Invalid routing ID
47    #[error("Invalid routing ID")]
48    InvalidRoutingId,
49
50    /// Message too large
51    #[error("Message too large: {size} bytes (max: {max})")]
52    MessageTooLarge { size: usize, max: usize },
53
54    /// Subscription error
55    #[error("Subscription error: {0}")]
56    Subscription(String),
57}
58
59/// Result type alias for Monocoque operations
60pub type Result<T> = std::result::Result<T, MonocoqueError>;
61
62/// Extension trait for adding context to results
63pub trait ResultExt<T> {
64    /// Add context to an error
65    fn context(self, context: impl Into<String>) -> Result<T>;
66
67    /// Add context via a closure (lazy evaluation)
68    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    /// Create a protocol error with a message
106    pub fn protocol(msg: impl Into<String>) -> Self {
107        Self::Protocol(msg.into())
108    }
109
110    /// Create an invalid greeting error
111    pub fn invalid_greeting(msg: impl Into<String>) -> Self {
112        Self::InvalidGreeting(msg.into())
113    }
114
115    /// Create an invalid frame error
116    pub fn invalid_frame(msg: impl Into<String>) -> Self {
117        Self::InvalidFrame(msg.into())
118    }
119
120    /// Create a peer disconnected error
121    pub fn peer_disconnected(peer_id: impl Into<String>) -> Self {
122        Self::PeerDisconnected(peer_id.into())
123    }
124
125    /// Check if this error is recoverable
126    #[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    /// Check if this is a connection error
139    #[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}