1#![allow(missing_docs)]
7#![allow(clippy::recursive_format_impl)]
8
9use std::fmt;
10use thiserror::Error;
11
12pub type Result<T> = std::result::Result<T, Error>;
14
15#[derive(Error, Debug)]
17pub enum Error {
18 #[error("Protocol error: {0}")]
20 Protocol(#[from] ProtocolError),
21
22 #[error("I/O error: {0}")]
24 Io(#[from] std::io::Error),
25
26 #[error("Configuration error: {0}")]
28 Config(#[from] ConfigError),
29
30 #[error("Frame error: {0}")]
32 Frame(#[from] FrameError),
33
34 #[error("Message error: {0}")]
36 Message(#[from] MessageError),
37
38 #[error("Security error: {0}")]
40 Security(#[from] SecurityError),
41
42 #[error("Timeout error: {0}")]
44 Timeout(#[from] TimeoutError),
45
46 #[error("Close error: {0}")]
48 Close(#[from] CloseError),
49
50 #[error("Connection error: {0}")]
52 Connection(String),
53
54 #[error("Error: {0}")]
56 Other(String),
57
58 #[error("Buffer capacity exceeded: {size} bytes")]
60 CapacityExceeded { size: usize },
61
62 #[error("Invalid UTF-8 in text frame")]
64 InvalidUtf8,
65
66 #[error("Connection closed: {code} - {reason}")]
68 Closed {
69 code: CloseCode,
71 reason: String,
73 },
74}
75
76#[derive(Error, Debug, Clone)]
78pub enum ProtocolError {
79 #[error("Unsupported WebSocket version")]
81 UnsupportedVersion,
82
83 #[error("Invalid WebSocket upgrade request")]
85 InvalidUpgradeRequest,
86
87 #[error("Missing required header: {0}")]
89 MissingHeader(String),
90
91 #[error("Invalid header value for {header}: {value}")]
93 InvalidHeader { header: String, value: String },
94
95 #[error("Extension negotiation failed: {0}")]
97 ExtensionNegotiation(String),
98
99 #[error("Subprotocol negotiation failed")]
101 SubprotocolNegotiation,
102
103 #[error("Invalid frame format: {0}")]
105 InvalidFrame(String),
106
107 #[error("Control frames cannot be fragmented")]
109 FragmentedControlFrame,
110
111 #[error("Invalid close code: {0}")]
113 InvalidCloseCode(u16),
114
115 #[error("Reserved bits set in frame")]
117 ReservedBitsSet,
118
119 #[error("Invalid HTTP method: {0}")]
121 InvalidMethod(String),
122
123 #[error("Invalid format: {0}")]
125 InvalidFormat(String),
126
127 #[error("Invalid header value for {header}: {value}")]
129 InvalidHeaderValue { header: String, value: String },
130
131 #[error("Invalid origin - expected: {expected}, received: {received}")]
133 InvalidOrigin { expected: String, received: String },
134
135 #[error("Unsupported WebSocket protocol: {0}")]
137 UnsupportedProtocol(String),
138
139 #[error("Unexpected HTTP status: {0}")]
141 UnexpectedStatus(u16),
142
143 #[error("Invalid WebSocket accept key - expected: {expected}, received: {received}")]
145 InvalidAcceptKey { expected: String, received: String },
146}
147
148#[derive(Error, Debug, Clone)]
150pub enum FrameError {
151 #[error("Insufficient data: need {needed} bytes, have {have}")]
153 InsufficientData { needed: usize, have: usize },
154
155 #[error("Frame too large: {size} bytes (max: {max})")]
157 TooLarge { size: usize, max: usize },
158
159 #[error("Invalid frame header: {0}")]
161 InvalidHeader(String),
162
163 #[error("Invalid masking: {0}")]
165 InvalidMasking(String),
166
167 #[error("Invalid opcode: {0}")]
169 InvalidOpcode(u8),
170
171 #[error("Reserved bits set in frame")]
173 ReservedBitsSet,
174
175 #[error("Control frames cannot be fragmented")]
177 FragmentedControlFrame,
178}
179
180#[derive(Error, Debug, Clone)]
182pub enum ConfigError {
183 #[error("Invalid configuration value for {field}: {value}")]
185 InvalidValue { field: String, value: String },
186
187 #[error("Missing required configuration: {field}")]
189 MissingField { field: String },
190
191 #[error("Configuration validation failed: {0}")]
193 Validation(String),
194}
195
196#[derive(Error, Debug, Clone)]
198pub enum MessageError {
199 #[error("Message too large: {size} bytes (max: {max})")]
201 TooLarge { size: usize, max: usize },
202
203 #[error("Invalid message format: {0}")]
205 InvalidFormat(String),
206
207 #[error("Control messages cannot be fragmented")]
209 FragmentedControl,
210
211 #[error("Incomplete message: missing {missing}")]
213 Incomplete { missing: String },
214}
215
216#[derive(Error, Debug, Clone)]
218pub enum SecurityError {
219 #[error("Authentication failed: {0}")]
221 Authentication(String),
222
223 #[error("Authorization failed: {0}")]
225 Authorization(String),
226
227 #[error("Rate limit exceeded")]
229 RateLimit,
230
231 #[error("Connection blocked: {reason}")]
233 Blocked { reason: String },
234
235 #[error("Security policy violation: {0}")]
237 PolicyViolation(String),
238}
239
240#[derive(Error, Debug, Clone)]
242pub enum TimeoutError {
243 #[error("Handshake timeout: {timeout:?}")]
245 Handshake { timeout: std::time::Duration },
246
247 #[error("Read timeout: {timeout:?}")]
249 Read { timeout: std::time::Duration },
250
251 #[error("Write timeout: {timeout:?}")]
253 Write { timeout: std::time::Duration },
254
255 #[error("Idle timeout: {timeout:?}")]
257 Idle { timeout: std::time::Duration },
258}
259
260#[derive(Error, Debug, Clone)]
262pub enum CloseError {
263 #[error("Invalid close code: {code}")]
265 InvalidCode { code: u16 },
266
267 #[error("Close reason too long: {len} bytes (max: {max})")]
269 ReasonTooLong { len: usize, max: usize },
270
271 #[error("Invalid UTF-8 in close reason")]
273 InvalidUtf8,
274}
275
276#[derive(Debug, Clone, Copy, PartialEq, Eq)]
278#[repr(u16)]
279pub enum CloseCode {
280 Normal = 1000,
282
283 Away = 1001,
285
286 ProtocolError = 1002,
288
289 Unsupported = 1003,
291
292 NoStatus = 1005,
294
295 Abnormal = 1006,
297
298 InvalidPayload = 1007,
300
301 PolicyViolation = 1008,
303
304 TooBig = 1009,
306
307 MandatoryExtension = 1010,
309
310 Internal = 1011,
312
313 TlsHandshake = 1015,
315
316 Application(u16) = 3000,
318}
319
320impl CloseCode {
321 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 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 pub fn is_reserved(&self) -> bool {
362 matches!(
363 self,
364 CloseCode::NoStatus | CloseCode::Abnormal | CloseCode::TlsHandshake
365 )
366 }
367
368 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); assert!(msg.contains("protocol") || msg.contains("version") || msg.contains("WebSocket"));
397 }
398}