html2pdf_api/
error.rs

1//! Error types for the browser pool.
2//!
3//! This module provides [`BrowserPoolError`], a unified error type for all
4//! browser pool operations, and a convenient [`Result`] type alias.
5//!
6//! # Example
7//!
8//! ```rust
9//! use html2pdf_api::{BrowserPoolError, Result};
10//!
11//! fn process_pdf() -> Result<Vec<u8>> {
12//!     // Your logic here...
13//!     Err(BrowserPoolError::Configuration("example error".to_string()))
14//! }
15//!
16//! match process_pdf() {
17//!     Ok(pdf) => println!("Generated {} bytes", pdf.len()),
18//!     Err(BrowserPoolError::ShuttingDown) => println!("Pool is shutting down"),
19//!     Err(e) => eprintln!("Error: {}", e),
20//! }
21//! ```
22
23/// Errors that can occur during browser pool operations.
24///
25/// This enum represents all possible error conditions when working with
26/// the browser pool. Each variant includes context about what went wrong.
27///
28/// # Example
29///
30/// ```rust
31/// use html2pdf_api::BrowserPoolError;
32///
33/// fn handle_error(error: BrowserPoolError) {
34///     match error {
35///         BrowserPoolError::BrowserCreation(msg) => {
36///             eprintln!("Browser creation failed: {}", msg);
37///         }
38///         BrowserPoolError::HealthCheckFailed(msg) => {
39///             eprintln!("Health check failed: {}", msg);
40///         }
41///         BrowserPoolError::ShuttingDown => {
42///             eprintln!("Pool is shutting down");
43///         }
44///         BrowserPoolError::Configuration(msg) => {
45///             eprintln!("Configuration error: {}", msg);
46///         }
47///     }
48/// }
49/// ```
50#[derive(Debug, thiserror::Error)]
51pub enum BrowserPoolError {
52    /// Failed to create a new browser instance.
53    ///
54    /// This typically indicates Chrome/Chromium binary issues or launch flag problems.
55    ///
56    /// # Common Causes
57    ///
58    /// - Chrome/Chromium binary not found or not installed
59    /// - Invalid Chrome binary path specified
60    /// - Insufficient permissions to execute Chrome
61    /// - Invalid or conflicting launch flags
62    /// - System resource limits exceeded (e.g., too many processes)
63    ///
64    /// # Example
65    ///
66    /// ```rust
67    /// use html2pdf_api::BrowserPoolError;
68    ///
69    /// let error = BrowserPoolError::BrowserCreation(
70    ///     "Chrome binary not found".to_string()
71    /// );
72    /// println!("{}", error); // "Failed to create browser: Chrome binary not found"
73    /// ```
74    #[error("Failed to create browser: {0}")]
75    BrowserCreation(String),
76
77    /// Browser failed a health check operation.
78    ///
79    /// Triggered when ping operations (new_tab, navigate, close) fail.
80    ///
81    /// # Common Causes
82    ///
83    /// - Browser process crashed unexpectedly
84    /// - Out of memory condition
85    /// - CDP (Chrome DevTools Protocol) connection lost
86    /// - Browser tab became unresponsive
87    ///
88    /// # Recovery
89    ///
90    /// The pool automatically removes unhealthy browsers and creates
91    /// replacements. Users typically don't need to handle this error
92    /// specially unless monitoring browser health.
93    ///
94    /// # Example
95    ///
96    /// ```rust
97    /// use html2pdf_api::BrowserPoolError;
98    ///
99    /// let error = BrowserPoolError::HealthCheckFailed(
100    ///     "new_tab() failed: connection refused".to_string()
101    /// );
102    /// println!("{}", error); // "Browser health check failed: new_tab() failed: connection refused"
103    /// ```
104    #[error("Browser health check failed: {0}")]
105    HealthCheckFailed(String),
106
107    /// Operation attempted during pool shutdown.
108    ///
109    /// All operations are rejected once shutdown begins.
110    ///
111    /// This error is returned when:
112    /// - [`BrowserPool::shutdown()`](crate::BrowserPool::shutdown) has been called
113    /// - [`BrowserPool::shutdown_async()`](crate::BrowserPool::shutdown_async) has been called
114    /// - The [`BrowserPool`](crate::BrowserPool) is being dropped
115    ///
116    /// # Handling
117    ///
118    /// This error typically occurs during application shutdown. Handle it
119    /// gracefully by stopping any pending work rather than retrying.
120    ///
121    /// # Example
122    ///
123    /// ```rust
124    /// use html2pdf_api::BrowserPoolError;
125    ///
126    /// let error = BrowserPoolError::ShuttingDown;
127    /// println!("{}", error); // "Pool is shutting down"
128    /// ```
129    #[error("Pool is shutting down")]
130    ShuttingDown,
131
132    /// Invalid configuration provided.
133    ///
134    /// This error occurs when pool configuration values are invalid.
135    ///
136    /// # Common Causes
137    ///
138    /// - `max_pool_size` is set to 0
139    /// - `warmup_count` exceeds `max_pool_size`
140    /// - Invalid Chrome binary path
141    /// - Invalid duration values
142    ///
143    /// # Prevention
144    ///
145    /// Use [`BrowserPoolConfigBuilder`](crate::BrowserPoolConfigBuilder)
146    /// which validates configuration at build time.
147    ///
148    /// # Example
149    ///
150    /// ```rust
151    /// use html2pdf_api::BrowserPoolError;
152    ///
153    /// let error = BrowserPoolError::Configuration(
154    ///     "max_pool_size must be greater than 0".to_string()
155    /// );
156    /// println!("{}", error); // "Configuration error: max_pool_size must be greater than 0"
157    /// ```
158    #[error("Configuration error: {0}")]
159    Configuration(String),
160}
161
162/// Convenience conversion from [`String`] to [`BrowserPoolError::Configuration`].
163///
164/// Allows using the `?` operator with functions that return `String` errors
165/// in contexts expecting [`BrowserPoolError`].
166///
167/// # Example
168///
169/// ```rust
170/// use html2pdf_api::BrowserPoolError;
171///
172/// let error: BrowserPoolError = "invalid configuration".to_string().into();
173/// assert!(matches!(error, BrowserPoolError::Configuration(_)));
174/// ```
175impl From<String> for BrowserPoolError {
176    fn from(msg: String) -> Self {
177        BrowserPoolError::Configuration(msg)
178    }
179}
180
181/// Convenience conversion from `&str` to [`BrowserPoolError::Configuration`].
182///
183/// Allows using string literals directly where [`BrowserPoolError`] is expected.
184///
185/// # Example
186///
187/// ```rust
188/// use html2pdf_api::BrowserPoolError;
189///
190/// let error: BrowserPoolError = "invalid setting".into();
191/// assert!(matches!(error, BrowserPoolError::Configuration(_)));
192/// ```
193impl From<&str> for BrowserPoolError {
194    fn from(msg: &str) -> Self {
195        BrowserPoolError::Configuration(msg.to_string())
196    }
197}
198
199/// Result type alias using [`BrowserPoolError`].
200///
201/// This is the standard result type returned by most browser pool operations.
202///
203/// # Example
204///
205/// ```rust
206/// use html2pdf_api::Result;
207///
208/// fn my_function() -> Result<String> {
209///     Ok("success".to_string())
210/// }
211/// ```
212pub type Result<T> = std::result::Result<T, BrowserPoolError>;
213
214// ============================================================================
215// Unit Tests
216// ============================================================================
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221
222    /// Verifies error type conversions from String and &str.
223    ///
224    /// The pool uses custom error types with convenient From implementations.
225    /// This test ensures error conversion works as expected.
226    #[test]
227    fn test_error_conversion() {
228        // Test conversion from &str
229        let error: BrowserPoolError = "test error".into();
230        match error {
231            BrowserPoolError::Configuration(msg) => {
232                assert_eq!(msg, "test error", "Error message should be preserved");
233            }
234            _ => panic!("Expected Configuration error variant"),
235        }
236
237        // Test conversion from String
238        let error: BrowserPoolError = "another error".to_string().into();
239        match error {
240            BrowserPoolError::Configuration(msg) => {
241                assert_eq!(msg, "another error", "Error message should be preserved");
242            }
243            _ => panic!("Expected Configuration error variant"),
244        }
245    }
246
247    /// Verifies that error Display formatting works correctly.
248    #[test]
249    fn test_error_display() {
250        let error = BrowserPoolError::BrowserCreation("chrome not found".to_string());
251        assert_eq!(
252            error.to_string(),
253            "Failed to create browser: chrome not found"
254        );
255
256        let error = BrowserPoolError::HealthCheckFailed("ping failed".to_string());
257        assert_eq!(
258            error.to_string(),
259            "Browser health check failed: ping failed"
260        );
261
262        let error = BrowserPoolError::ShuttingDown;
263        assert_eq!(error.to_string(), "Pool is shutting down");
264
265        let error = BrowserPoolError::Configuration("bad config".to_string());
266        assert_eq!(error.to_string(), "Configuration error: bad config");
267    }
268
269    /// Verifies that BrowserPoolError implements std::error::Error.
270    #[test]
271    fn test_error_is_std_error() {
272        fn assert_std_error<T: std::error::Error>() {}
273        assert_std_error::<BrowserPoolError>();
274    }
275
276    /// Verifies that BrowserPoolError is Send + Sync for thread safety.
277    #[test]
278    fn test_error_is_send_sync() {
279        fn assert_send_sync<T: Send + Sync>() {}
280        assert_send_sync::<BrowserPoolError>();
281    }
282}