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}