tb_rs/
error.rs

1//! Error types for the TigerBeetle client.
2//!
3//! All error types implement `std::error::Error` for compatibility
4//! with error handling frameworks like `anyhow` and `thiserror`.
5
6use crate::protocol::header::EvictionReason;
7use std::error::Error;
8use std::fmt;
9
10/// Result type for client operations.
11pub type Result<T> = std::result::Result<T, ClientError>;
12
13/// Main error type for client operations.
14#[derive(Debug)]
15pub enum ClientError {
16    /// Connection error (connect, send, recv failures).
17    Connection(String),
18    /// Protocol error (invalid message, checksum failure, etc.).
19    Protocol(ProtocolError),
20    /// Client was evicted by the server.
21    Evicted(EvictionReason),
22    /// Operation timed out.
23    Timeout,
24    /// Client is not registered.
25    NotRegistered,
26    /// Client is shutting down.
27    Shutdown,
28    /// Request was too large for the server's batch size limit.
29    RequestTooLarge {
30        /// The size of the request body in bytes.
31        size: u32,
32        /// The server's batch size limit in bytes.
33        limit: u32,
34    },
35    /// Invalid operation for current state.
36    InvalidOperation,
37    /// Transport-level error (I/O, network, etc.).
38    /// Deprecated: Use Connection instead.
39    Transport(Box<dyn Error + Send + Sync>),
40}
41
42impl fmt::Display for ClientError {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        match self {
45            ClientError::Connection(msg) => write!(f, "connection error: {}", msg),
46            ClientError::Protocol(e) => write!(f, "protocol error: {}", e),
47            ClientError::Evicted(reason) => write!(f, "client evicted: {:?}", reason),
48            ClientError::Timeout => write!(f, "operation timed out"),
49            ClientError::NotRegistered => write!(f, "client not registered"),
50            ClientError::Shutdown => write!(f, "client is shutting down"),
51            ClientError::RequestTooLarge { size, limit } => {
52                write!(f, "request too large: {} bytes exceeds limit of {} bytes", size, limit)
53            }
54            ClientError::InvalidOperation => write!(f, "invalid operation for current state"),
55            ClientError::Transport(e) => write!(f, "transport error: {}", e),
56        }
57    }
58}
59
60impl Error for ClientError {
61    fn source(&self) -> Option<&(dyn Error + 'static)> {
62        match self {
63            ClientError::Transport(e) => Some(e.as_ref()),
64            ClientError::Protocol(e) => Some(e),
65            _ => None,
66        }
67    }
68}
69
70impl From<ProtocolError> for ClientError {
71    fn from(err: ProtocolError) -> Self {
72        ClientError::Protocol(err)
73    }
74}
75
76impl From<std::io::Error> for ClientError {
77    fn from(err: std::io::Error) -> Self {
78        ClientError::Transport(Box::new(err))
79    }
80}
81
82/// Protocol-level errors.
83#[derive(Clone, Copy, Debug, Eq, PartialEq)]
84pub enum ProtocolError {
85    /// Invalid header checksum.
86    InvalidHeaderChecksum,
87    /// Invalid body checksum.
88    InvalidBodyChecksum,
89    /// Invalid header structure.
90    InvalidHeader,
91    /// Invalid operation.
92    InvalidOperation,
93    /// Unexpected reply (wrong request number or parent).
94    UnexpectedReply,
95    /// Version mismatch.
96    VersionMismatch,
97    /// Invalid message size.
98    InvalidSize,
99    /// Invalid command.
100    InvalidCommand,
101}
102
103impl fmt::Display for ProtocolError {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        match self {
106            ProtocolError::InvalidHeaderChecksum => write!(f, "invalid header checksum"),
107            ProtocolError::InvalidBodyChecksum => write!(f, "invalid body checksum"),
108            ProtocolError::InvalidHeader => write!(f, "invalid header structure"),
109            ProtocolError::InvalidOperation => write!(f, "invalid operation"),
110            ProtocolError::UnexpectedReply => write!(f, "unexpected reply"),
111            ProtocolError::VersionMismatch => write!(f, "version mismatch"),
112            ProtocolError::InvalidSize => write!(f, "invalid message size"),
113            ProtocolError::InvalidCommand => write!(f, "invalid command"),
114        }
115    }
116}
117
118impl Error for ProtocolError {}
119
120/// Packet-level status codes (from C client API).
121#[derive(Clone, Copy, Debug, Eq, PartialEq)]
122pub enum PacketStatus {
123    /// Operation completed successfully.
124    Ok,
125    /// Request data was too large.
126    TooMuchData,
127    /// Client was evicted.
128    ClientEvicted,
129    /// Client release is too old.
130    ClientReleaseTooLow,
131    /// Client release is too new.
132    ClientReleaseTooHigh,
133    /// Client was shut down.
134    ClientShutdown,
135    /// Invalid operation.
136    InvalidOperation,
137    /// Invalid data size.
138    InvalidDataSize,
139}
140
141impl fmt::Display for PacketStatus {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        match self {
144            PacketStatus::Ok => write!(f, "ok"),
145            PacketStatus::TooMuchData => write!(f, "too much data"),
146            PacketStatus::ClientEvicted => write!(f, "client evicted"),
147            PacketStatus::ClientReleaseTooLow => write!(f, "client release too low"),
148            PacketStatus::ClientReleaseTooHigh => write!(f, "client release too high"),
149            PacketStatus::ClientShutdown => write!(f, "client shutdown"),
150            PacketStatus::InvalidOperation => write!(f, "invalid operation"),
151            PacketStatus::InvalidDataSize => write!(f, "invalid data size"),
152        }
153    }
154}
155
156impl Error for PacketStatus {}
157
158/// Initialization status codes.
159#[derive(Clone, Copy, Debug, Eq, PartialEq)]
160pub enum InitStatus {
161    /// Initialization succeeded.
162    Success,
163    /// Unexpected error.
164    Unexpected,
165    /// Out of memory.
166    OutOfMemory,
167    /// Invalid address.
168    AddressInvalid,
169    /// Too many addresses.
170    AddressLimitExceeded,
171    /// System resource error.
172    SystemResources,
173    /// Network subsystem error.
174    NetworkSubsystem,
175}
176
177impl fmt::Display for InitStatus {
178    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179        match self {
180            InitStatus::Success => write!(f, "success"),
181            InitStatus::Unexpected => write!(f, "unexpected error"),
182            InitStatus::OutOfMemory => write!(f, "out of memory"),
183            InitStatus::AddressInvalid => write!(f, "invalid address"),
184            InitStatus::AddressLimitExceeded => write!(f, "address limit exceeded"),
185            InitStatus::SystemResources => write!(f, "system resources error"),
186            InitStatus::NetworkSubsystem => write!(f, "network subsystem error"),
187        }
188    }
189}
190
191impl Error for InitStatus {}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196
197    #[test]
198    fn test_client_error_display() {
199        let err = ClientError::Timeout;
200        assert_eq!(format!("{}", err), "operation timed out");
201    }
202
203    #[test]
204    fn test_protocol_error_display() {
205        let err = ProtocolError::InvalidHeaderChecksum;
206        assert_eq!(format!("{}", err), "invalid header checksum");
207    }
208
209    #[test]
210    fn test_client_error_from_io() {
211        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
212        let client_err: ClientError = io_err.into();
213        assert!(matches!(client_err, ClientError::Transport(_)));
214    }
215
216    #[test]
217    fn test_error_source_chain() {
218        let protocol_err = ProtocolError::InvalidHeaderChecksum;
219        let client_err = ClientError::Protocol(protocol_err);
220
221        // Can get source
222        let source = client_err.source().unwrap();
223        assert!(source.is::<ProtocolError>());
224    }
225}