wsforge_core/error.rs
1//! Error types and result handling for WsForge.
2//!
3//! This module provides a unified error type that covers all possible error conditions
4//! in the WsForge framework, from WebSocket protocol errors to application-level errors.
5//!
6//! # Overview
7//!
8//! The error handling in WsForge is designed to be:
9//! - **Ergonomic**: Using `Result<T>` as a type alias for `std::result::Result<T, Error>`
10//! - **Informative**: Each variant provides context about what went wrong
11//! - **Composable**: Implements `From` traits for automatic error conversion
12//! - **Debuggable**: All errors implement `Display` and `Debug` for easy troubleshooting
13//!
14//! # Error Categories
15//!
16//! Errors are organized into several categories:
17//! - **Protocol Errors**: WebSocket and IO errors
18//! - **Serialization Errors**: JSON parsing and serialization failures
19//! - **Framework Errors**: Connection and routing issues
20//! - **Application Errors**: Custom business logic errors
21//!
22//! # Examples
23//!
24//! ## Basic Error Handling
25//!
26//! ```
27//! use wsforge::prelude::*;
28//!
29//! async fn handler(msg: Message) -> Result<String> {
30//! // Automatic error conversion from serde_json::Error
31//! let data: serde_json::Value = msg.json()?;
32//!
33//! // Create custom error
34//! if data.is_null() {
35//! return Err(Error::custom("Data cannot be null"));
36//! }
37//!
38//! Ok("Success".to_string())
39//! }
40//! ```
41//!
42//! ## Pattern Matching on Errors
43//!
44//! ```
45//! use wsforge::prelude::*;
46//!
47//! # async fn example() {
48//! # let result: Result<()> = Ok(());
49//! match result {
50//! Ok(_) => println!("Success!"),
51//! Err(Error::ConnectionNotFound(id)) => {
52//! eprintln!("Connection {} not found", id);
53//! }
54//! Err(Error::Json(e)) => {
55//! eprintln!("JSON error: {}", e);
56//! }
57//! Err(e) => {
58//! eprintln!("Other error: {}", e);
59//! }
60//! }
61//! # }
62//! ```
63//!
64//! ## Creating Custom Errors
65//!
66//! ```
67//! use wsforge::prelude::*;
68//!
69//! async fn validate_user(username: &str) -> Result<()> {
70//! if username.is_empty() {
71//! return Err(Error::custom("Username cannot be empty"));
72//! }
73//!
74//! if username.len() < 3 {
75//! return Err(Error::handler(
76//! format!("Username '{}' is too short (minimum 3 characters)", username)
77//! ));
78//! }
79//!
80//! Ok(())
81//! }
82//! ```
83
84use std::fmt;
85use thiserror::Error;
86
87/// The main error type for WsForge operations.
88///
89/// This enum represents all possible errors that can occur in the WsForge framework.
90/// It uses the [`thiserror`](https://docs.rs/thiserror) crate to automatically
91/// implement `std::error::Error` and provide good error messages.
92///
93/// # Variants
94///
95/// Each variant represents a specific category of error:
96///
97/// - [`WebSocket`](Error::WebSocket): WebSocket protocol errors from tungstenite
98/// - [`Io`](Error::Io): I/O errors from file operations or network
99/// - [`Json`](Error::Json): JSON serialization/deserialization errors
100/// - [`ConnectionNotFound`](Error::ConnectionNotFound): Connection lookup failures
101/// - [`RouteNotFound`](Error::RouteNotFound): Message routing failures
102/// - [`InvalidMessage`](Error::InvalidMessage): Malformed message format
103/// - [`Handler`](Error::Handler): Handler execution errors
104/// - [`Extractor`](Error::Extractor): Type extraction errors
105/// - [`Custom`](Error::Custom): Application-defined errors
106///
107/// # Examples
108///
109/// ## Handling Specific Error Types
110///
111/// ```
112/// use wsforge::prelude::*;
113///
114/// # async fn example() {
115/// # let result: Result<()> = Ok(());
116/// match result {
117/// Ok(_) => println!("Success"),
118/// Err(Error::ConnectionNotFound(id)) => {
119/// println!("Connection {} not found - may have disconnected", id);
120/// }
121/// Err(Error::Json(e)) => {
122/// println!("Failed to parse JSON: {}", e);
123/// }
124/// Err(e) => {
125/// println!("Unexpected error: {}", e);
126/// }
127/// }
128/// # }
129/// ```
130///
131/// ## Error Propagation with `?`
132///
133/// ```
134/// use wsforge::prelude::*;
135///
136/// async fn process_message(msg: Message) -> Result<String> {
137/// // JSON errors are automatically converted to Error::Json
138/// let data: serde_json::Value = msg.json()?;
139///
140/// // Custom validation
141/// let username = data["username"]
142/// .as_str()
143/// .ok_or_else(|| Error::custom("Missing username field"))?;
144///
145/// Ok(format!("Hello, {}", username))
146/// }
147/// ```
148#[derive(Debug, Error)]
149pub enum Error {
150 /// WebSocket protocol error.
151 ///
152 /// This variant wraps errors from the `tokio-tungstenite` crate,
153 /// which include protocol violations, connection issues, and
154 /// framing errors.
155 ///
156 /// # Examples
157 ///
158 /// ```
159 /// use wsforge::prelude::*;
160 ///
161 /// # fn example(err: tokio_tungstenite::tungstenite::Error) {
162 /// let error = Error::from(err);
163 /// match error {
164 /// Error::WebSocket(e) => println!("WebSocket error: {}", e),
165 /// _ => {}
166 /// }
167 /// # }
168 /// ```
169 #[error("WebSocket error: {0}")]
170 WebSocket(#[from] tokio_tungstenite::tungstenite::Error),
171
172 /// I/O error.
173 ///
174 /// This variant wraps standard I/O errors that can occur during
175 /// file operations, network operations, or other system-level
176 /// operations.
177 ///
178 /// # Examples
179 ///
180 /// ```
181 /// use wsforge::prelude::*;
182 /// use std::io;
183 ///
184 /// # fn example(io_err: io::Error) {
185 /// let error = Error::from(io_err);
186 /// match error {
187 /// Error::Io(e) => println!("I/O error: {}", e),
188 /// _ => {}
189 /// }
190 /// # }
191 /// ```
192 #[error("IO error: {0}")]
193 Io(#[from] std::io::Error),
194
195 /// JSON serialization or deserialization error.
196 ///
197 /// This variant wraps errors from `serde_json`, which can occur when:
198 /// - Parsing invalid JSON
199 /// - Serializing data with unsupported types
200 /// - Missing required fields during deserialization
201 ///
202 /// # Examples
203 ///
204 /// ```
205 /// use wsforge::prelude::*;
206 /// use serde::Deserialize;
207 ///
208 /// #[derive(Deserialize)]
209 /// struct User {
210 /// id: u32,
211 /// name: String,
212 /// }
213 ///
214 /// async fn parse_user(msg: Message) -> Result<User> {
215 /// // If JSON is invalid or missing fields, Error::Json is returned
216 /// let user: User = msg.json()?;
217 /// Ok(user)
218 /// }
219 /// ```
220 #[error("JSON error: {0}")]
221 Json(#[from] serde_json::Error),
222
223 /// Connection not found error.
224 ///
225 /// This error occurs when attempting to send a message to a connection
226 /// that no longer exists (usually because the client disconnected).
227 ///
228 /// The variant contains the ID of the connection that couldn't be found.
229 ///
230 /// # Examples
231 ///
232 /// ```
233 /// use wsforge::prelude::*;
234 ///
235 /// async fn send_notification(
236 /// manager: &ConnectionManager,
237 /// user_id: &str,
238 /// msg: &str,
239 /// ) -> Result<()> {
240 /// let conn = manager
241 /// .get(&user_id.to_string())
242 /// .ok_or_else(|| Error::ConnectionNotFound(user_id.to_string()))?;
243 ///
244 /// conn.send_text(msg)?;
245 /// Ok(())
246 /// }
247 /// ```
248 #[error("Connection not found: {0}")]
249 ConnectionNotFound(String),
250
251 /// Route not found error.
252 ///
253 /// This error occurs when a message is sent to a route that doesn't
254 /// have a registered handler.
255 ///
256 /// The variant contains the route path that couldn't be found.
257 ///
258 /// # Examples
259 ///
260 /// ```
261 /// use wsforge::prelude::*;
262 ///
263 /// # fn example() {
264 /// let route = "/unknown/path";
265 /// let error = Error::RouteNotFound(route.to_string());
266 /// println!("{}", error); // "Route not found: /unknown/path"
267 /// # }
268 /// ```
269 #[error("Route not found: {0}")]
270 RouteNotFound(String),
271
272 /// Invalid message format error.
273 ///
274 /// This error occurs when a message has an unexpected format or
275 /// structure that the handler cannot process.
276 ///
277 /// # Examples
278 ///
279 /// ```
280 /// use wsforge::prelude::*;
281 ///
282 /// async fn validate_message(msg: &Message) -> Result<()> {
283 /// if msg.is_binary() {
284 /// return Err(Error::InvalidMessage);
285 /// }
286 ///
287 /// let text = msg.as_text()
288 /// .ok_or(Error::InvalidMessage)?;
289 ///
290 /// if text.is_empty() {
291 /// return Err(Error::InvalidMessage);
292 /// }
293 ///
294 /// Ok(())
295 /// }
296 /// ```
297 #[error("Invalid message format")]
298 InvalidMessage,
299
300 /// Handler execution error.
301 ///
302 /// This error occurs when a message handler encounters an error
303 /// during execution. The variant contains a description of what
304 /// went wrong.
305 ///
306 /// # Examples
307 ///
308 /// ```
309 /// use wsforge::prelude::*;
310 ///
311 /// async fn process_command(cmd: &str) -> Result<()> {
312 /// match cmd {
313 /// "start" => Ok(()),
314 /// "stop" => Ok(()),
315 /// unknown => Err(Error::handler(
316 /// format!("Unknown command: {}", unknown)
317 /// )),
318 /// }
319 /// }
320 /// ```
321 #[error("Handler error: {0}")]
322 Handler(String),
323
324 /// Type extractor error.
325 ///
326 /// This error occurs when a type extractor fails to extract data
327 /// from the message or connection context. Common causes include:
328 /// - Missing required data in state
329 /// - Type mismatches
330 /// - Missing path or query parameters
331 ///
332 /// # Examples
333 ///
334 /// ```
335 /// use wsforge::prelude::*;
336 ///
337 /// async fn get_user_state(state: &AppState) -> Result<String> {
338 /// state
339 /// .get::<String>()
340 /// .ok_or_else(|| Error::extractor("User state not found"))
341 /// .map(|arc| (*arc).clone())
342 /// }
343 /// ```
344 #[error("Extractor error: {0}")]
345 Extractor(String),
346
347 /// Custom application-defined error.
348 ///
349 /// This variant allows applications to create custom errors with
350 /// arbitrary messages. Use this for application-specific error
351 /// conditions that don't fit other categories.
352 ///
353 /// # Examples
354 ///
355 /// ```
356 /// use wsforge::prelude::*;
357 ///
358 /// async fn check_rate_limit(user_id: &str) -> Result<()> {
359 /// let request_count = get_request_count(user_id);
360 ///
361 /// if request_count > 100 {
362 /// return Err(Error::custom(
363 /// format!("Rate limit exceeded for user {}", user_id)
364 /// ));
365 /// }
366 ///
367 /// Ok(())
368 /// }
369 ///
370 /// # fn get_request_count(_: &str) -> u32 { 0 }
371 /// ```
372 #[error("Custom error: {0}")]
373 Custom(String),
374}
375
376/// A type alias for `Result<T, Error>`.
377///
378/// This alias is provided for convenience and consistency across the codebase.
379/// Most functions in WsForge return this type.
380///
381/// # Examples
382///
383/// ```
384/// use wsforge::prelude::*;
385///
386/// async fn my_handler(msg: Message) -> Result<String> {
387/// Ok("Success".to_string())
388/// }
389/// ```
390pub type Result<T> = std::result::Result<T, Error>;
391
392impl Error {
393 /// Creates a custom error with the given message.
394 ///
395 /// This is a convenience method for creating [`Error::Custom`] variants.
396 /// Use this for application-specific errors that don't fit into other
397 /// error categories.
398 ///
399 /// # Arguments
400 ///
401 /// * `msg` - Any type that implements `Display` (like `&str`, `String`, etc.)
402 ///
403 /// # Examples
404 ///
405 /// ## With String Literals
406 ///
407 /// ```
408 /// use wsforge::prelude::*;
409 ///
410 /// # fn example() -> Result<()> {
411 /// if some_condition() {
412 /// return Err(Error::custom("Something went wrong"));
413 /// }
414 /// Ok(())
415 /// # }
416 /// # fn some_condition() -> bool { false }
417 /// ```
418 ///
419 /// ## With Formatted Strings
420 ///
421 /// ```
422 /// use wsforge::prelude::*;
423 ///
424 /// # fn example(user_id: u32, max_age: u32) -> Result<()> {
425 /// let age = get_user_age(user_id);
426 /// if age > max_age {
427 /// return Err(Error::custom(
428 /// format!("User {} age {} exceeds maximum {}", user_id, age, max_age)
429 /// ));
430 /// }
431 /// Ok(())
432 /// # }
433 /// # fn get_user_age(_: u32) -> u32 { 25 }
434 /// ```
435 pub fn custom<T: fmt::Display>(msg: T) -> Self {
436 Error::Custom(msg.to_string())
437 }
438
439 /// Creates a handler error with the given message.
440 ///
441 /// This is a convenience method for creating [`Error::Handler`] variants.
442 /// Use this for errors that occur during handler execution.
443 ///
444 /// # Arguments
445 ///
446 /// * `msg` - Any type that implements `Display`
447 ///
448 /// # Examples
449 ///
450 /// ## Validation Errors
451 ///
452 /// ```
453 /// use wsforge::prelude::*;
454 ///
455 /// async fn validate_input(data: &str) -> Result<()> {
456 /// if data.is_empty() {
457 /// return Err(Error::handler("Input cannot be empty"));
458 /// }
459 ///
460 /// if data.len() > 1000 {
461 /// return Err(Error::handler(
462 /// format!("Input too long: {} characters (max 1000)", data.len())
463 /// ));
464 /// }
465 ///
466 /// Ok(())
467 /// }
468 /// ```
469 ///
470 /// ## Command Processing Errors
471 ///
472 /// ```
473 /// use wsforge::prelude::*;
474 ///
475 /// async fn execute_command(cmd: &str, args: &[&str]) -> Result<String> {
476 /// match cmd {
477 /// "echo" if args.is_empty() => {
478 /// Err(Error::handler("echo command requires arguments"))
479 /// }
480 /// "echo" => Ok(args.join(" ")),
481 /// unknown => Err(Error::handler(
482 /// format!("Unknown command: {}", unknown)
483 /// )),
484 /// }
485 /// }
486 /// ```
487 pub fn handler<T: fmt::Display>(msg: T) -> Self {
488 Error::Handler(msg.to_string())
489 }
490
491 /// Creates an extractor error with the given message.
492 ///
493 /// This is a convenience method for creating [`Error::Extractor`] variants.
494 /// Use this when type extraction fails, such as when required data is
495 /// missing from the request context.
496 ///
497 /// # Arguments
498 ///
499 /// * `msg` - Any type that implements `Display`
500 ///
501 /// # Examples
502 ///
503 /// ## Missing State Data
504 ///
505 /// ```
506 /// use wsforge::prelude::*;
507 /// use std::sync::Arc;
508 ///
509 /// struct DatabasePool;
510 ///
511 /// async fn get_database(state: &AppState) -> Result<Arc<DatabasePool>> {
512 /// state
513 /// .get::<DatabasePool>()
514 /// .ok_or_else(|| Error::extractor("Database pool not found in state"))
515 /// }
516 /// ```
517 ///
518 /// ## Missing Path Parameters
519 ///
520 /// ```
521 /// use wsforge::prelude::*;
522 ///
523 /// async fn extract_user_id(extensions: &Extensions) -> Result<u32> {
524 /// extensions
525 /// .get::<u32>("user_id")
526 /// .map(|arc| *arc)
527 /// .ok_or_else(|| Error::extractor("User ID not found in path"))
528 /// }
529 /// ```
530 ///
531 /// ## Type Mismatch
532 ///
533 /// ```
534 /// use wsforge::prelude::*;
535 ///
536 /// async fn parse_count(value: &str) -> Result<usize> {
537 /// value.parse::<usize>().map_err(|_| {
538 /// Error::extractor(format!(
539 /// "Failed to parse '{}' as count (expected positive integer)",
540 /// value
541 /// ))
542 /// })
543 /// }
544 /// ```
545 pub fn extractor<T: fmt::Display>(msg: T) -> Self {
546 Error::Extractor(msg.to_string())
547 }
548}
549
550#[cfg(test)]
551mod tests {
552 use super::*;
553
554 #[test]
555 fn test_custom_error() {
556 let err = Error::custom("test error");
557 assert!(matches!(err, Error::Custom(_)));
558 assert_eq!(err.to_string(), "Custom error: test error");
559 }
560
561 #[test]
562 fn test_handler_error() {
563 let err = Error::handler("handler failed");
564 assert!(matches!(err, Error::Handler(_)));
565 assert_eq!(err.to_string(), "Handler error: handler failed");
566 }
567
568 #[test]
569 fn test_extractor_error() {
570 let err = Error::extractor("missing field");
571 assert!(matches!(err, Error::Extractor(_)));
572 assert_eq!(err.to_string(), "Extractor error: missing field");
573 }
574
575 #[test]
576 fn test_connection_not_found() {
577 let err = Error::ConnectionNotFound("conn_123".to_string());
578 assert_eq!(err.to_string(), "Connection not found: conn_123");
579 }
580
581 #[test]
582 fn test_route_not_found() {
583 let err = Error::RouteNotFound("/api/users".to_string());
584 assert_eq!(err.to_string(), "Route not found: /api/users");
585 }
586
587 #[test]
588 fn test_error_from_io() {
589 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
590 let err = Error::from(io_err);
591 assert!(matches!(err, Error::Io(_)));
592 }
593
594 #[test]
595 fn test_error_from_json() {
596 let json_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
597 let err = Error::from(json_err);
598 assert!(matches!(err, Error::Json(_)));
599 }
600
601 #[test]
602 fn test_result_type_alias() {
603 fn returns_result() -> Result<String> {
604 Ok("success".to_string())
605 }
606
607 assert!(returns_result().is_ok());
608 }
609
610 #[test]
611 fn test_error_display_formatting() {
612 let errors = vec![
613 Error::custom("custom message"),
614 Error::handler("handler message"),
615 Error::extractor("extractor message"),
616 Error::InvalidMessage,
617 ];
618
619 for err in errors {
620 let display = format!("{}", err);
621 assert!(!display.is_empty());
622 }
623 }
624}