Skip to main content

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