aerosocket_core/
error.rs

1//! Error types for AeroSocket
2//!
3//! This module defines all error types used throughout the AeroSocket library.
4//! Errors are designed to be ergonomic and provide clear context for debugging.
5
6#![allow(missing_docs)]
7#![allow(clippy::recursive_format_impl)]
8
9use std::fmt;
10use thiserror::Error;
11
12/// Result type alias for AeroSocket operations
13pub type Result<T> = std::result::Result<T, Error>;
14
15/// Comprehensive error type for AeroSocket operations
16#[derive(Error, Debug)]
17pub enum Error {
18    /// Protocol errors
19    #[error("Protocol error: {0}")]
20    Protocol(#[from] ProtocolError),
21
22    /// I/O errors
23    #[error("I/O error: {0}")]
24    Io(#[from] std::io::Error),
25
26    /// Configuration errors
27    #[error("Configuration error: {0}")]
28    Config(#[from] ConfigError),
29
30    /// Frame errors
31    #[error("Frame error: {0}")]
32    Frame(#[from] FrameError),
33
34    /// Message errors
35    #[error("Message error: {0}")]
36    Message(#[from] MessageError),
37
38    /// Security errors
39    #[error("Security error: {0}")]
40    Security(#[from] SecurityError),
41
42    /// Timeout errors
43    #[error("Timeout error: {0}")]
44    Timeout(#[from] TimeoutError),
45
46    /// Close errors
47    #[error("Close error: {0}")]
48    Close(#[from] CloseError),
49
50    /// Connection errors
51    #[error("Connection error: {0}")]
52    Connection(String),
53
54    /// Generic errors
55    #[error("Error: {0}")]
56    Other(String),
57
58    /// Buffer capacity exceeded
59    #[error("Buffer capacity exceeded: {size} bytes")]
60    CapacityExceeded { size: usize },
61
62    /// Invalid UTF-8 in text frame
63    #[error("Invalid UTF-8 in text frame")]
64    InvalidUtf8,
65
66    /// Connection closed
67    #[error("Connection closed: {code} - {reason}")]
68    Closed {
69        /// Close code
70        code: CloseCode,
71        /// Close reason
72        reason: String,
73    },
74}
75
76/// WebSocket protocol specific errors
77#[derive(Error, Debug, Clone)]
78pub enum ProtocolError {
79    /// Invalid WebSocket version
80    #[error("Unsupported WebSocket version")]
81    UnsupportedVersion,
82
83    /// Invalid upgrade request
84    #[error("Invalid WebSocket upgrade request")]
85    InvalidUpgradeRequest,
86
87    /// Missing required headers
88    #[error("Missing required header: {0}")]
89    MissingHeader(String),
90
91    /// Invalid header value
92    #[error("Invalid header value for {header}: {value}")]
93    InvalidHeader { header: String, value: String },
94
95    /// Extension negotiation failed
96    #[error("Extension negotiation failed: {0}")]
97    ExtensionNegotiation(String),
98
99    /// Subprotocol negotiation failed
100    #[error("Subprotocol negotiation failed")]
101    SubprotocolNegotiation,
102
103    /// Invalid frame format
104    #[error("Invalid frame format: {0}")]
105    InvalidFrame(String),
106
107    /// Control frame fragmentation not allowed
108    #[error("Control frames cannot be fragmented")]
109    FragmentedControlFrame,
110
111    /// Invalid close code
112    #[error("Invalid close code: {0}")]
113    InvalidCloseCode(u16),
114
115    /// Reserved bits set in frame
116    #[error("Reserved bits set in frame")]
117    ReservedBitsSet,
118
119    /// Invalid HTTP method
120    #[error("Invalid HTTP method: {0}")]
121    InvalidMethod(String),
122
123    /// Invalid format
124    #[error("Invalid format: {0}")]
125    InvalidFormat(String),
126
127    /// Invalid header value
128    #[error("Invalid header value for {header}: {value}")]
129    InvalidHeaderValue { header: String, value: String },
130
131    /// Invalid origin
132    #[error("Invalid origin - expected: {expected}, received: {received}")]
133    InvalidOrigin { expected: String, received: String },
134
135    /// Unsupported protocol
136    #[error("Unsupported WebSocket protocol: {0}")]
137    UnsupportedProtocol(String),
138
139    /// Unexpected HTTP status
140    #[error("Unexpected HTTP status: {0}")]
141    UnexpectedStatus(u16),
142
143    /// Invalid accept key
144    #[error("Invalid WebSocket accept key - expected: {expected}, received: {received}")]
145    InvalidAcceptKey { expected: String, received: String },
146}
147
148/// Frame parsing and processing errors
149#[derive(Error, Debug, Clone)]
150pub enum FrameError {
151    /// Insufficient data to parse frame
152    #[error("Insufficient data: need {needed} bytes, have {have}")]
153    InsufficientData { needed: usize, have: usize },
154
155    /// Frame too large
156    #[error("Frame too large: {size} bytes (max: {max})")]
157    TooLarge { size: usize, max: usize },
158
159    /// Invalid frame header
160    #[error("Invalid frame header: {0}")]
161    InvalidHeader(String),
162
163    /// Invalid masking
164    #[error("Invalid masking: {0}")]
165    InvalidMasking(String),
166
167    /// Invalid opcode
168    #[error("Invalid opcode: {0}")]
169    InvalidOpcode(u8),
170
171    /// Reserved bits set
172    #[error("Reserved bits set in frame")]
173    ReservedBitsSet,
174
175    /// Control frames cannot be fragmented
176    #[error("Control frames cannot be fragmented")]
177    FragmentedControlFrame,
178}
179
180/// Configuration errors
181#[derive(Error, Debug, Clone)]
182pub enum ConfigError {
183    /// Invalid configuration value
184    #[error("Invalid configuration value for {field}: {value}")]
185    InvalidValue { field: String, value: String },
186
187    /// Missing required configuration
188    #[error("Missing required configuration: {field}")]
189    MissingField { field: String },
190
191    /// Configuration validation failed
192    #[error("Configuration validation failed: {0}")]
193    Validation(String),
194}
195
196/// Message errors
197#[derive(Error, Debug, Clone)]
198pub enum MessageError {
199    /// Message too large
200    #[error("Message too large: {size} bytes (max: {max})")]
201    TooLarge { size: usize, max: usize },
202
203    /// Invalid message format
204    #[error("Invalid message format: {0}")]
205    InvalidFormat(String),
206
207    /// Fragmented control message
208    #[error("Control messages cannot be fragmented")]
209    FragmentedControl,
210
211    /// Incomplete message
212    #[error("Incomplete message: missing {missing}")]
213    Incomplete { missing: String },
214}
215
216/// Security errors
217#[derive(Error, Debug, Clone)]
218pub enum SecurityError {
219    /// Authentication failed
220    #[error("Authentication failed: {0}")]
221    Authentication(String),
222
223    /// Authorization failed
224    #[error("Authorization failed: {0}")]
225    Authorization(String),
226
227    /// Rate limit exceeded
228    #[error("Rate limit exceeded")]
229    RateLimit,
230
231    /// Blocked connection
232    #[error("Connection blocked: {reason}")]
233    Blocked { reason: String },
234
235    /// Security policy violation
236    #[error("Security policy violation: {0}")]
237    PolicyViolation(String),
238}
239
240/// Timeout errors
241#[derive(Error, Debug, Clone)]
242pub enum TimeoutError {
243    /// Handshake timeout
244    #[error("Handshake timeout: {timeout:?}")]
245    Handshake { timeout: std::time::Duration },
246
247    /// Read timeout
248    #[error("Read timeout: {timeout:?}")]
249    Read { timeout: std::time::Duration },
250
251    /// Write timeout
252    #[error("Write timeout: {timeout:?}")]
253    Write { timeout: std::time::Duration },
254
255    /// Idle timeout
256    #[error("Idle timeout: {timeout:?}")]
257    Idle { timeout: std::time::Duration },
258}
259
260/// Close errors
261#[derive(Error, Debug, Clone)]
262pub enum CloseError {
263    /// Invalid close code
264    #[error("Invalid close code: {code}")]
265    InvalidCode { code: u16 },
266
267    /// Close reason too long
268    #[error("Close reason too long: {len} bytes (max: {max})")]
269    ReasonTooLong { len: usize, max: usize },
270
271    /// UTF-8 error in close reason
272    #[error("Invalid UTF-8 in close reason")]
273    InvalidUtf8,
274}
275
276/// WebSocket close codes as defined in RFC 6455
277#[derive(Debug, Clone, Copy, PartialEq, Eq)]
278#[repr(u16)]
279pub enum CloseCode {
280    /// Normal closure
281    Normal = 1000,
282
283    /// Going away
284    Away = 1001,
285
286    /// Protocol error
287    ProtocolError = 1002,
288
289    /// Unsupported data
290    Unsupported = 1003,
291
292    /// No status received
293    NoStatus = 1005,
294
295    /// Abnormal closure
296    Abnormal = 1006,
297
298    /// Invalid frame payload data
299    InvalidPayload = 1007,
300
301    /// Policy violation
302    PolicyViolation = 1008,
303
304    /// Message too big
305    TooBig = 1009,
306
307    /// Mandatory extension
308    MandatoryExtension = 1010,
309
310    /// Internal server error
311    Internal = 1011,
312
313    /// TLS handshake failure
314    TlsHandshake = 1015,
315
316    /// Application-specific close code
317    Application(u16) = 3000,
318}
319
320impl CloseCode {
321    /// Create a CloseCode from a u16
322    pub fn from(code: u16) -> Self {
323        match code {
324            1000 => CloseCode::Normal,
325            1001 => CloseCode::Away,
326            1002 => CloseCode::ProtocolError,
327            1003 => CloseCode::Unsupported,
328            1005 => CloseCode::NoStatus,
329            1006 => CloseCode::Abnormal,
330            1007 => CloseCode::InvalidPayload,
331            1008 => CloseCode::PolicyViolation,
332            1009 => CloseCode::TooBig,
333            1010 => CloseCode::MandatoryExtension,
334            1011 => CloseCode::Internal,
335            1015 => CloseCode::TlsHandshake,
336            code if (3000..=4999).contains(&code) => CloseCode::Application(code),
337            _ => CloseCode::ProtocolError,
338        }
339    }
340
341    /// Get the numeric value of the close code
342    pub fn code(&self) -> u16 {
343        match self {
344            CloseCode::Normal => 1000,
345            CloseCode::Away => 1001,
346            CloseCode::ProtocolError => 1002,
347            CloseCode::Unsupported => 1003,
348            CloseCode::NoStatus => 1005,
349            CloseCode::Abnormal => 1006,
350            CloseCode::InvalidPayload => 1007,
351            CloseCode::PolicyViolation => 1008,
352            CloseCode::TooBig => 1009,
353            CloseCode::MandatoryExtension => 1010,
354            CloseCode::Internal => 1011,
355            CloseCode::TlsHandshake => 1015,
356            CloseCode::Application(code) => *code,
357        }
358    }
359
360    /// Check if this is a reserved close code
361    pub fn is_reserved(&self) -> bool {
362        matches!(
363            self,
364            CloseCode::NoStatus | CloseCode::Abnormal | CloseCode::TlsHandshake
365        )
366    }
367
368    /// Check if this close code indicates an error
369    pub fn is_error(&self) -> bool {
370        !matches!(self, CloseCode::Normal | CloseCode::Away)
371    }
372}
373
374impl fmt::Display for CloseCode {
375    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
376        write!(f, "{} ({})", self, self.code())
377    }
378}
379
380#[cfg(test)]
381mod tests {
382    use super::*;
383
384    #[test]
385    fn test_close_code_conversion() {
386        assert_eq!(CloseCode::from(1000), CloseCode::Normal);
387        assert_eq!(CloseCode::from(3000), CloseCode::Application(3000));
388        assert_eq!(CloseCode::from(999), CloseCode::ProtocolError);
389    }
390
391    #[test]
392    fn test_error_display() {
393        let err = Error::Protocol(ProtocolError::UnsupportedVersion);
394        let msg = err.to_string();
395        println!("Error message: {}", msg); // Debug output
396        assert!(msg.contains("protocol") || msg.contains("version") || msg.contains("WebSocket"));
397    }
398}