Skip to main content

zendriver_transport/
error.rs

1//! Transport-layer errors.
2
3/// Connection-level failure modes — anything that happens "below" a CDP
4/// response getting routed back to its caller.
5#[derive(Debug, thiserror::Error)]
6#[non_exhaustive]
7pub enum TransportError {
8    /// The WebSocket closed without Chrome having sent a Close frame.
9    #[error("websocket closed unexpectedly")]
10    Disconnected,
11
12    /// Tungstenite raised an error on the underlying WebSocket.
13    #[error("websocket: {0}")]
14    Ws(#[from] tokio_tungstenite::tungstenite::Error),
15
16    /// JSON serialization or framing failed.
17    #[error("framing: {0}")]
18    Frame(#[from] serde_json::Error),
19
20    /// The actor task has been told to shut down — pending calls drain with
21    /// this variant so callers don't hang forever.
22    #[error("connection shut down")]
23    Shutdown,
24
25    /// The actor sent a reply but the oneshot receiver had already been
26    /// dropped. Carries the originating command id for diagnostics.
27    #[error("response channel dropped before reply (id={id})")]
28    ResponseDropped {
29        /// Command id whose reply landed without a receiver.
30        id: u64,
31    },
32
33    /// An I/O error occurred (typically inside tungstenite).
34    #[error("io: {0}")]
35    Io(#[from] std::io::Error),
36}
37
38/// Result of a CDP call: either a transport-level failure, or a structured
39/// JSON-RPC error returned by Chrome. Higher layers (the `zendriver` crate)
40/// map `Rpc` into the typed `ZendriverError::Cdp` variant.
41#[derive(Debug, thiserror::Error)]
42#[non_exhaustive]
43pub enum CallError {
44    /// Connection-level failure (see [`TransportError`]).
45    #[error("transport: {0}")]
46    Transport(#[from] TransportError),
47    /// Chrome answered the command with a structured JSON-RPC error. Carries
48    /// the JSON-RPC `code`, `message`, and optional `data` payload.
49    #[error("CDP RPC error [{0}] {1}")]
50    Rpc(i32, String, Option<serde_json::Value>),
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn display_disconnected_is_stable() {
59        assert_eq!(
60            TransportError::Disconnected.to_string(),
61            "websocket closed unexpectedly"
62        );
63    }
64
65    #[test]
66    fn display_shutdown_is_stable() {
67        assert_eq!(TransportError::Shutdown.to_string(), "connection shut down");
68    }
69
70    #[test]
71    fn display_response_dropped_includes_id() {
72        let e = TransportError::ResponseDropped { id: 42 };
73        assert_eq!(
74            e.to_string(),
75            "response channel dropped before reply (id=42)"
76        );
77    }
78
79    #[test]
80    fn source_preserved_through_ws_wrap() {
81        // Construct a tungstenite error and wrap it; check source chain works.
82        let tung = tokio_tungstenite::tungstenite::Error::ConnectionClosed;
83        let wrapped = TransportError::Ws(tung);
84        // Display starts with "websocket: "
85        assert!(wrapped.to_string().starts_with("websocket: "));
86        // source() returns the inner
87        assert!(std::error::Error::source(&wrapped).is_some());
88    }
89}