Skip to main content

chrome_cli/chrome/
error.rs

1use std::fmt;
2
3/// Errors that can occur during Chrome discovery and launch.
4#[derive(Debug)]
5pub enum ChromeError {
6    /// Chrome executable was not found on the system.
7    NotFound(String),
8
9    /// Chrome process failed to launch.
10    LaunchFailed(String),
11
12    /// Chrome did not start accepting connections within the timeout.
13    StartupTimeout {
14        /// The port Chrome was expected to listen on.
15        port: u16,
16    },
17
18    /// HTTP request to Chrome's debug endpoint failed.
19    HttpError(String),
20
21    /// Failed to parse a response from Chrome.
22    ParseError(String),
23
24    /// The `DevToolsActivePort` file was not found.
25    NoActivePort,
26
27    /// No running Chrome instance could be discovered.
28    NotRunning(String),
29
30    /// An I/O error occurred.
31    Io(std::io::Error),
32}
33
34impl fmt::Display for ChromeError {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        match self {
37            Self::NotFound(msg) => write!(f, "Chrome not found: {msg}"),
38            Self::LaunchFailed(msg) => write!(f, "Chrome launch failed: {msg}"),
39            Self::StartupTimeout { port } => {
40                write!(
41                    f,
42                    "Chrome startup timed out on port {port}. Try --timeout to increase the wait time, or --headless for headless mode"
43                )
44            }
45            Self::HttpError(msg) => write!(f, "Chrome HTTP error: {msg}"),
46            Self::ParseError(msg) => write!(f, "Chrome parse error: {msg}"),
47            Self::NoActivePort => write!(f, "DevToolsActivePort file not found"),
48            Self::NotRunning(detail) => {
49                write!(
50                    f,
51                    "no running Chrome instance found with remote debugging: {detail}"
52                )
53            }
54            Self::Io(e) => write!(f, "Chrome I/O error: {e}"),
55        }
56    }
57}
58
59impl std::error::Error for ChromeError {
60    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
61        match self {
62            Self::Io(e) => Some(e),
63            _ => None,
64        }
65    }
66}
67
68impl From<std::io::Error> for ChromeError {
69    fn from(e: std::io::Error) -> Self {
70        Self::Io(e)
71    }
72}
73
74impl From<ChromeError> for crate::error::AppError {
75    fn from(e: ChromeError) -> Self {
76        use crate::error::ExitCode;
77        let code = match &e {
78            ChromeError::NotFound(_) | ChromeError::ParseError(_) | ChromeError::Io(_) => {
79                ExitCode::GeneralError
80            }
81            ChromeError::LaunchFailed(_)
82            | ChromeError::HttpError(_)
83            | ChromeError::NotRunning(_)
84            | ChromeError::NoActivePort => ExitCode::ConnectionError,
85            ChromeError::StartupTimeout { .. } => ExitCode::TimeoutError,
86        };
87        Self {
88            message: e.to_string(),
89            code,
90            custom_json: None,
91        }
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn display_not_found() {
101        let err = ChromeError::NotFound("try --chrome-path".into());
102        assert_eq!(err.to_string(), "Chrome not found: try --chrome-path");
103    }
104
105    #[test]
106    fn display_launch_failed() {
107        let err = ChromeError::LaunchFailed("permission denied".into());
108        assert_eq!(err.to_string(), "Chrome launch failed: permission denied");
109    }
110
111    #[test]
112    fn display_startup_timeout() {
113        let err = ChromeError::StartupTimeout { port: 9222 };
114        assert_eq!(
115            err.to_string(),
116            "Chrome startup timed out on port 9222. Try --timeout to increase the wait time, or --headless for headless mode"
117        );
118    }
119
120    #[test]
121    fn display_http_error() {
122        let err = ChromeError::HttpError("connection refused".into());
123        assert_eq!(err.to_string(), "Chrome HTTP error: connection refused");
124    }
125
126    #[test]
127    fn display_parse_error() {
128        let err = ChromeError::ParseError("invalid JSON".into());
129        assert_eq!(err.to_string(), "Chrome parse error: invalid JSON");
130    }
131
132    #[test]
133    fn display_no_active_port() {
134        let err = ChromeError::NoActivePort;
135        assert_eq!(err.to_string(), "DevToolsActivePort file not found");
136    }
137
138    #[test]
139    fn display_not_running() {
140        let err = ChromeError::NotRunning("port 9222 refused".into());
141        assert_eq!(
142            err.to_string(),
143            "no running Chrome instance found with remote debugging: port 9222 refused"
144        );
145    }
146
147    #[test]
148    fn display_io() {
149        let err = ChromeError::Io(std::io::Error::new(
150            std::io::ErrorKind::NotFound,
151            "file gone",
152        ));
153        assert_eq!(err.to_string(), "Chrome I/O error: file gone");
154    }
155
156    #[test]
157    fn error_source_is_none_for_non_io() {
158        let err: &dyn std::error::Error = &ChromeError::NotRunning("no instance".into());
159        assert!(err.source().is_none());
160    }
161
162    #[test]
163    fn error_source_returns_io_error() {
164        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file gone");
165        let err: &dyn std::error::Error = &ChromeError::Io(io_err);
166        assert!(err.source().is_some());
167    }
168}