Skip to main content

chrome_cli/cdp/
error.rs

1use std::fmt;
2
3/// Errors that can occur during CDP communication.
4#[derive(Debug)]
5pub enum CdpError {
6    /// WebSocket connection could not be established.
7    Connection(String),
8
9    /// Connection attempt exceeded the configured timeout.
10    ConnectionTimeout,
11
12    /// A command did not receive a response within the configured timeout.
13    CommandTimeout {
14        /// The CDP method that timed out.
15        method: String,
16    },
17
18    /// Chrome returned a CDP protocol-level error.
19    Protocol {
20        /// The CDP error code (e.g., -32000).
21        code: i64,
22        /// The CDP error message.
23        message: String,
24    },
25
26    /// The WebSocket connection was closed unexpectedly.
27    ConnectionClosed,
28
29    /// Failed to parse a message received from Chrome.
30    InvalidResponse(String),
31
32    /// Reconnection failed after all retry attempts were exhausted.
33    ReconnectFailed {
34        /// Number of reconnection attempts made.
35        attempts: u32,
36        /// The error from the last reconnection attempt.
37        last_error: String,
38    },
39
40    /// Internal error (e.g., transport task died or channel closed).
41    Internal(String),
42}
43
44impl fmt::Display for CdpError {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        match self {
47            Self::Connection(msg) => write!(f, "CDP connection error: {msg}"),
48            Self::ConnectionTimeout => write!(f, "CDP connection timed out"),
49            Self::CommandTimeout { method } => {
50                write!(f, "CDP command timed out: {method}")
51            }
52            Self::Protocol { code, message } => {
53                write!(f, "CDP protocol error ({code}): {message}")
54            }
55            Self::ConnectionClosed => write!(f, "CDP connection closed"),
56            Self::InvalidResponse(msg) => {
57                write!(f, "CDP invalid response: {msg}")
58            }
59            Self::ReconnectFailed {
60                attempts,
61                last_error,
62            } => {
63                write!(
64                    f,
65                    "CDP reconnection failed after {attempts} attempts: {last_error}"
66                )
67            }
68            Self::Internal(msg) => write!(f, "CDP internal error: {msg}"),
69        }
70    }
71}
72
73impl std::error::Error for CdpError {}
74
75impl From<CdpError> for crate::error::AppError {
76    fn from(e: CdpError) -> Self {
77        use crate::error::ExitCode;
78        let code = match &e {
79            CdpError::Connection(_)
80            | CdpError::ConnectionClosed
81            | CdpError::ReconnectFailed { .. } => ExitCode::ConnectionError,
82            CdpError::ConnectionTimeout | CdpError::CommandTimeout { .. } => ExitCode::TimeoutError,
83            CdpError::Protocol { .. } => ExitCode::ProtocolError,
84            CdpError::InvalidResponse(_) | CdpError::Internal(_) => ExitCode::GeneralError,
85        };
86        Self {
87            message: e.to_string(),
88            code,
89            custom_json: None,
90        }
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn display_connection() {
100        let err = CdpError::Connection("refused".into());
101        assert_eq!(err.to_string(), "CDP connection error: refused");
102    }
103
104    #[test]
105    fn display_connection_timeout() {
106        let err = CdpError::ConnectionTimeout;
107        assert_eq!(err.to_string(), "CDP connection timed out");
108    }
109
110    #[test]
111    fn display_command_timeout() {
112        let err = CdpError::CommandTimeout {
113            method: "Page.navigate".into(),
114        };
115        assert_eq!(err.to_string(), "CDP command timed out: Page.navigate");
116    }
117
118    #[test]
119    fn display_protocol() {
120        let err = CdpError::Protocol {
121            code: -32000,
122            message: "Not found".into(),
123        };
124        assert_eq!(err.to_string(), "CDP protocol error (-32000): Not found");
125    }
126
127    #[test]
128    fn display_connection_closed() {
129        let err = CdpError::ConnectionClosed;
130        assert_eq!(err.to_string(), "CDP connection closed");
131    }
132
133    #[test]
134    fn display_invalid_response() {
135        let err = CdpError::InvalidResponse("bad json".into());
136        assert_eq!(err.to_string(), "CDP invalid response: bad json");
137    }
138
139    #[test]
140    fn display_reconnect_failed() {
141        let err = CdpError::ReconnectFailed {
142            attempts: 3,
143            last_error: "connection refused".into(),
144        };
145        assert_eq!(
146            err.to_string(),
147            "CDP reconnection failed after 3 attempts: connection refused"
148        );
149    }
150
151    #[test]
152    fn display_internal() {
153        let err = CdpError::Internal("channel closed".into());
154        assert_eq!(err.to_string(), "CDP internal error: channel closed");
155    }
156
157    #[test]
158    fn error_trait_is_implemented() {
159        let err: &dyn std::error::Error = &CdpError::ConnectionClosed;
160        // Ensure we can call source() without panic
161        assert!(err.source().is_none());
162    }
163}