use base64::DecodeError;
use displaydoc::Display;
use serde::export::Formatter;
use serde::Deserialize;
use thiserror::Error;
pub type WebDriverResult<T> = Result<T, WebDriverError>;
fn indent_lines(message: &str, indent: usize) -> String {
let lines: Vec<String> =
message.split('\n').map(|s| format!("{0:i$}{1}", " ", s, i = indent)).collect();
lines.join("\n")
}
#[derive(Debug, Deserialize, Clone)]
pub struct WebDriverErrorValue {
pub message: String,
pub error: Option<String>,
pub stacktrace: Option<String>,
pub data: Option<serde_json::Value>,
}
impl WebDriverErrorValue {
pub fn new(message: &str) -> Self {
Self {
message: message.to_string(),
error: None,
stacktrace: None,
data: None,
}
}
}
impl std::fmt::Display for WebDriverErrorValue {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let stacktrace = self
.stacktrace
.as_ref()
.map(|x| format!("Stacktrace:\n{}", indent_lines(&x, 4)))
.unwrap_or_default();
let error = self.error.as_ref().map(|x| format!("Error: {}", x)).unwrap_or_default();
let data = self
.data
.as_ref()
.map(|x| format!("Data:\n{}", indent_lines(&format!("{:#?}", x), 4)))
.unwrap_or_default();
let lines: Vec<String> = vec![self.message.clone(), error, data, stacktrace]
.into_iter()
.filter(|l| !l.is_empty())
.collect();
write!(f, "{}", lines.join("\n"))
}
}
#[derive(Debug, Deserialize, Clone)]
pub struct WebDriverErrorInfo {
#[serde(skip)]
pub status: u16,
#[serde(default, rename(deserialize = "state"))]
pub error: String,
pub value: WebDriverErrorValue,
}
impl WebDriverErrorInfo {
pub fn new(message: &str) -> Self {
Self {
status: 0,
error: message.to_string(),
value: WebDriverErrorValue::new(message),
}
}
}
impl std::fmt::Display for WebDriverErrorInfo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut msg = String::new();
if self.status != 0 {
msg.push_str(&format!("\nStatus: {}\n", self.status));
}
if !self.error.is_empty() {
msg.push_str(&format!("State: {}\n", self.error));
}
let additional_info =
format!("Additional info:\n{}", indent_lines(&self.value.to_string(), 4),);
msg.push_str(&additional_info);
write!(f, "{}", indent_lines(&msg, 4))
}
}
#[non_exhaustive]
#[derive(Debug, Error, Display)]
pub enum WebDriverError {
UnknownResponse(String),
NotFound(String, String),
Timeout(String),
JsonError(#[from] serde_json::error::Error),
DecodeError(#[from] DecodeError),
IOError(#[from] std::io::Error),
#[cfg(all(feature = "tokio-runtime", not(feature = "async-std-runtime")))]
ReqwestError(#[from] reqwest::Error),
#[cfg(feature = "async-std-runtime")]
SurfError(surf::Error),
NotInSpec(WebDriverErrorInfo),
ElementClickIntercepted(WebDriverErrorInfo),
ElementNotInteractable(WebDriverErrorInfo),
InsecureCertificate(WebDriverErrorInfo),
InvalidArgument(WebDriverErrorInfo),
InvalidCookieDomain(WebDriverErrorInfo),
InvalidElementState(WebDriverErrorInfo),
InvalidSelector(WebDriverErrorInfo),
InvalidSessionId(WebDriverErrorInfo),
JavascriptError(WebDriverErrorInfo),
MoveTargetOutOfBounds(WebDriverErrorInfo),
NoSuchAlert(WebDriverErrorInfo),
NoSuchCookie(WebDriverErrorInfo),
NoSuchElement(WebDriverErrorInfo),
NoSuchFrame(WebDriverErrorInfo),
NoSuchWindow(WebDriverErrorInfo),
ScriptTimeout(WebDriverErrorInfo),
SessionNotCreated(WebDriverErrorInfo),
StaleElementReference(WebDriverErrorInfo),
WebDriverTimeout(WebDriverErrorInfo),
UnableToSetCookie(WebDriverErrorInfo),
UnableToCaptureScreen(WebDriverErrorInfo),
UnexpectedAlertOpen(WebDriverErrorInfo),
UnknownCommand(WebDriverErrorInfo),
UnknownError(WebDriverErrorInfo),
UnknownMethod(WebDriverErrorInfo),
UnsupportedOperation(WebDriverErrorInfo),
}
impl WebDriverError {
pub fn parse(status: u16, body: serde_json::Value) -> Self {
let mut payload: WebDriverErrorInfo = match serde_json::from_value(body.clone()) {
Ok(x) => x,
Err(_) => {
return WebDriverError::UnknownResponse(format!(
"Server returned unknown response: {}",
body.to_string()
))
}
};
payload.status = status;
let mut error = payload.error.clone();
if error.is_empty() {
error = payload.value.error.clone().unwrap_or_default();
if error.is_empty() {
return WebDriverError::NotInSpec(payload);
}
}
match error.as_str() {
"element click intercepted" => WebDriverError::ElementClickIntercepted(payload),
"element not interactable" => WebDriverError::ElementNotInteractable(payload),
"insecure certificate" => WebDriverError::InsecureCertificate(payload),
"invalid argument" => WebDriverError::InvalidArgument(payload),
"invalid cookie domain" => WebDriverError::InvalidCookieDomain(payload),
"invalid element state" => WebDriverError::InvalidElementState(payload),
"invalid selector" => WebDriverError::InvalidSelector(payload),
"invalid session id" => WebDriverError::InvalidSessionId(payload),
"javascript error" => WebDriverError::JavascriptError(payload),
"move target out of bounds" => WebDriverError::MoveTargetOutOfBounds(payload),
"no such alert" => WebDriverError::NoSuchAlert(payload),
"no such cookie" => WebDriverError::NoSuchCookie(payload),
"no such element" => WebDriverError::NoSuchElement(payload),
"no such frame" => WebDriverError::NoSuchFrame(payload),
"no such window" => WebDriverError::NoSuchWindow(payload),
"script timeout" => WebDriverError::ScriptTimeout(payload),
"session not created" => WebDriverError::SessionNotCreated(payload),
"stale element reference" => WebDriverError::StaleElementReference(payload),
"timeout" => WebDriverError::WebDriverTimeout(payload),
"unable to set cookie" => WebDriverError::UnableToSetCookie(payload),
"unable to capture screen" => WebDriverError::UnableToCaptureScreen(payload),
"unexpected alert open" => WebDriverError::UnexpectedAlertOpen(payload),
"unknown command" => WebDriverError::UnknownCommand(payload),
"unknown error" => WebDriverError::UnknownError(payload),
"unknown method" => WebDriverError::UnknownMethod(payload),
"unsupported operation" => WebDriverError::UnsupportedOperation(payload),
_ => WebDriverError::NotInSpec(payload),
}
}
}
#[cfg(feature = "async-std-runtime")]
impl From<surf::Error> for WebDriverError {
fn from(err: surf::Error) -> Self {
Self::SurfError(err)
}
}