use serde::Deserialize;
use serde_json::Value;
use std::fmt;
#[derive(Debug, Clone, thiserror::Error)]
pub struct CdpError {
pub command: String,
pub code: i32,
pub message: String,
pub data: Option<Value>,
}
impl CdpError {
pub fn is_server_error(&self) -> bool {
(-32099..=-32000).contains(&self.code)
}
pub fn is_target_closed(&self) -> bool {
let m = self.message.to_ascii_lowercase();
m.contains("target closed") || m.contains("session closed")
}
}
impl fmt::Display for CdpError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "CDP command {} failed: {} {}", self.command, self.code, self.message)
}
}
impl From<CdpError> for crate::error::WebDriverError {
fn from(e: CdpError) -> Self {
crate::error::WebDriverError::FatalError(e.to_string())
}
}
#[derive(Debug, Clone, Deserialize)]
#[cfg_attr(not(any(feature = "cdp-events", test)), allow(dead_code))]
pub(crate) struct CdpErrorEnvelope {
pub code: i32,
pub message: String,
pub data: Option<Value>,
}
impl CdpErrorEnvelope {
#[cfg_attr(not(any(feature = "cdp-events", test)), allow(dead_code))]
pub(crate) fn into_error(self, command: impl Into<String>) -> CdpError {
CdpError {
command: command.into(),
code: self.code,
message: self.message,
data: self.data,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn into_error_preserves_command_name() {
let env = CdpErrorEnvelope {
code: -32601,
message: "x".to_string(),
data: None,
};
let err = env.into_error("Page.foo");
assert_eq!(err.command, "Page.foo");
assert_eq!(err.code, -32601);
assert!(!err.is_server_error());
}
#[test]
fn is_server_error_inclusive_range() {
for code in [-32000, -32050, -32099] {
let err = CdpError {
command: "x".to_string(),
code,
message: String::new(),
data: None,
};
assert!(err.is_server_error(), "{code} should be server error");
}
for code in [-31999, -32100, -32700, 0] {
let err = CdpError {
command: "x".to_string(),
code,
message: String::new(),
data: None,
};
assert!(!err.is_server_error(), "{code} should NOT be server error");
}
}
#[test]
fn is_target_closed_matches_message_substring() {
let mk = |msg: &str| CdpError {
command: "x".to_string(),
code: -32000,
message: msg.to_string(),
data: None,
};
assert!(mk("Target closed.").is_target_closed());
assert!(mk("target closed").is_target_closed());
assert!(mk("Session closed unexpectedly").is_target_closed());
assert!(!mk("Some other error").is_target_closed());
}
#[test]
fn display_format_is_useful() {
let err = CdpError {
command: "Page.foo".to_string(),
code: -32601,
message: "no such method".to_string(),
data: None,
};
let s = err.to_string();
assert!(s.contains("Page.foo"));
assert!(s.contains("-32601"));
assert!(s.contains("no such method"));
}
}