Skip to main content

millipede_browser/
error.rs

1//! Browser errors and their crawl retry semantics.
2
3use millipede_core::errors::CrawlError;
4
5/// An error produced while launching or operating a browser.
6#[derive(Debug, thiserror::Error)]
7#[non_exhaustive]
8pub enum BrowserError {
9    /// No supported browser executable could be discovered.
10    #[error("no usable browser binary found: {hint}")]
11    BrowserNotFound {
12        /// Actionable browser discovery guidance.
13        hint: String,
14    },
15    /// Starting the browser process failed.
16    #[error("browser launch failed: {0}")]
17    Launch(#[source] anyhow::Error),
18    /// Creating a page failed.
19    #[error("page creation failed: {0}")]
20    PageCreate(#[source] anyhow::Error),
21    /// Navigation failed before its timeout.
22    #[error("navigation to {url} failed: {source}")]
23    Navigation {
24        /// Navigation target.
25        url: url::Url,
26        /// Provider error that caused navigation to fail.
27        #[source]
28        source: anyhow::Error,
29    },
30    /// Navigation exceeded its configured timeout.
31    #[error("navigation to {url} timed out after {timeout:?}")]
32    NavigationTimeout {
33        /// Navigation target.
34        url: url::Url,
35        /// Configured navigation timeout.
36        timeout: std::time::Duration,
37    },
38    /// Waiting for a browser condition exceeded its timeout.
39    #[error("waiting for {what} timed out after {timeout:?}")]
40    WaitTimeout {
41        /// Description of the awaited condition.
42        what: String,
43        /// Configured wait timeout.
44        timeout: std::time::Duration,
45    },
46    /// JavaScript evaluation failed.
47    #[error("script evaluation failed: {0}")]
48    Evaluation(#[source] anyhow::Error),
49    /// Conversion between provider and Millipede cookies failed.
50    #[error("cookie conversion failed: {0}")]
51    CookieConversion(#[source] anyhow::Error),
52    /// An operation targeted a page that was already closed.
53    #[error("page is already closed")]
54    PageClosed,
55    /// An operation targeted a browser pool after shutdown.
56    #[error("browser pool is shut down")]
57    Shutdown,
58    /// A Chrome DevTools Protocol operation failed.
59    #[error("CDP protocol error: {0}")]
60    Protocol(#[source] anyhow::Error),
61}
62
63impl BrowserError {
64    /// Classifies this browser failure for the crawler retry engine.
65    #[allow(unreachable_patterns)]
66    pub fn classify(self) -> CrawlError {
67        match self {
68            Self::Navigation { .. }
69            | Self::NavigationTimeout { .. }
70            | Self::WaitTimeout { .. }
71            | Self::PageCreate(_)
72            | Self::PageClosed
73            | Self::Protocol(_) => CrawlError::retry(self),
74            Self::BrowserNotFound { .. } | Self::Launch(_) => CrawlError::critical(self),
75            Self::Shutdown | Self::Evaluation(_) | Self::CookieConversion(_) => {
76                CrawlError::non_retryable(self)
77            }
78            _ => CrawlError::non_retryable(self),
79        }
80    }
81}
82
83impl From<BrowserError> for CrawlError {
84    fn from(error: BrowserError) -> Self {
85        error.classify()
86    }
87}