Skip to main content

ferrous_browser/
error.rs

1use thiserror::Error;
2
3/// Result type for ferrous-browser operations
4pub type Result<T> = std::result::Result<T, BrowserError>;
5
6/// Errors that can occur during browser automation
7#[derive(Error, Debug)]
8pub enum BrowserError {
9    /// WebSocket protocol error
10    #[error("WebSocket error during {operation}: {message}")]
11    WebSocket {
12        /// The operation that was being performed
13        operation: String,
14        /// The underlying error message
15        message: String,
16    },
17
18    /// Failed to establish initial connection
19    #[error("Failed to connect to '{endpoint}': {reason}")]
20    ConnectionFailed {
21        /// The endpoint being connected to
22        endpoint: String,
23        /// The reason for failure
24        reason: String,
25    },
26
27    /// Invalid or malformed CDP response
28    #[error("Invalid CDP response while {context}: {details}")]
29    InvalidResponse {
30        /// What was being done
31        context: String,
32        /// Specific problem with the response
33        details: String,
34    },
35
36    /// CDP command execution failed
37    #[error("Command '{command}' failed: {reason}")]
38    CommandFailed {
39        /// The CDP command that failed
40        command: String,
41        /// The reason for failure
42        reason: String,
43    },
44
45    /// CDP protocol error with code
46    #[error("CDP error {code} in '{method}': {message}")]
47    CdpError {
48        /// CDP error code
49        code: i32,
50        /// The method that returned the error
51        method: String,
52        /// Error message
53        message: String,
54    },
55
56    /// Operation exceeded timeout duration
57    #[error("Timed out {operation} after {timeout_secs}s")]
58    Timeout {
59        /// Description of the operation that timed out
60        operation: String,
61        /// Timeout duration in seconds
62        timeout_secs: u64,
63    },
64
65    /// JSON serialization/deserialization error
66    #[error("JSON error: {0}")]
67    Json(#[from] serde_json::Error),
68
69    /// IO error from standard library
70    #[error("IO error: {0}")]
71    Io(#[from] std::io::Error),
72
73    /// Requested page was not found
74    #[error("Page not found: {0}")]
75    PageNotFound(String),
76
77    /// Requested target was not found
78    #[error("Target not found: {0}")]
79    TargetNotFound(String),
80
81    /// Browser instance not yet launched
82    #[error("Browser not launched: {0}")]
83    BrowserNotLaunched(String),
84
85    /// Navigation failed
86    #[error("Navigation to '{url}' failed: {reason}")]
87    NavigationFailed {
88        /// The URL that failed to load
89        url: String,
90        /// The reason for failure (e.g. net::ERR_NAME_NOT_RESOLVED)
91        reason: String,
92    },
93}
94
95impl BrowserError {
96    /// Construct a WebSocket error
97    pub fn websocket(operation: impl Into<String>, message: impl Into<String>) -> Self {
98        Self::WebSocket {
99            operation: operation.into(),
100            message: message.into(),
101        }
102    }
103
104    /// Construct a connection-failed error
105    pub fn connection_failed(endpoint: impl Into<String>, reason: impl Into<String>) -> Self {
106        Self::ConnectionFailed {
107            endpoint: endpoint.into(),
108            reason: reason.into(),
109        }
110    }
111
112    /// Construct a command-failed error
113    pub fn command_failed(command: impl Into<String>, reason: impl Into<String>) -> Self {
114        Self::CommandFailed {
115            command: command.into(),
116            reason: reason.into(),
117        }
118    }
119
120    /// Construct an invalid-response error
121    pub fn invalid_response(context: impl Into<String>, details: impl Into<String>) -> Self {
122        Self::InvalidResponse {
123            context: context.into(),
124            details: details.into(),
125        }
126    }
127
128    /// Construct a timeout error
129    pub fn timeout(operation: impl Into<String>, timeout_secs: u64) -> Self {
130        Self::Timeout {
131            operation: operation.into(),
132            timeout_secs,
133        }
134    }
135
136    /// Construct a navigation-failed error
137    pub fn navigation_failed(url: impl Into<String>, reason: impl Into<String>) -> Self {
138        Self::NavigationFailed {
139            url: url.into(),
140            reason: reason.into(),
141        }
142    }
143}
144
145/// Extension trait to add `.context(msg)` to `Result<T, BrowserError>`
146pub trait ResultExt<T> {
147    /// Add context to an error, wrapping it in a `CommandFailed` variant
148    fn context(self, ctx: impl Into<String>) -> Result<T>;
149}
150
151impl<T> ResultExt<T> for Result<T> {
152    fn context(self, ctx: impl Into<String>) -> Result<T> {
153        self.map_err(|e| BrowserError::CommandFailed {
154            command: ctx.into(),
155            reason: e.to_string(),
156        })
157    }
158}