use http::StatusCode;
use hyper::Error as HError;
use serde::{Serialize, Serializer};
use std::borrow::Cow;
use std::error::Error;
use std::fmt;
use std::io::Error as IOError;
use std::str::FromStr;
use url::ParseError;
#[derive(Debug)]
pub enum NewSessionError {
BadWebdriverUrl(ParseError),
Failed(HError),
Lost(IOError),
NotW3C(serde_json::Value),
SessionNotCreated(WebDriver),
}
impl Error for NewSessionError {
fn description(&self) -> &str {
match *self {
NewSessionError::BadWebdriverUrl(..) => "webdriver url is invalid",
NewSessionError::Failed(..) => "webdriver server did not respond",
NewSessionError::Lost(..) => "webdriver server disconnected",
NewSessionError::NotW3C(..) => "webdriver server gave non-conformant response",
NewSessionError::SessionNotCreated(..) => "webdriver did not create session",
}
}
fn cause(&self) -> Option<&dyn Error> {
match *self {
NewSessionError::BadWebdriverUrl(ref e) => Some(e),
NewSessionError::Failed(ref e) => Some(e),
NewSessionError::Lost(ref e) => Some(e),
NewSessionError::NotW3C(..) => None,
NewSessionError::SessionNotCreated(ref e) => Some(e),
}
}
}
impl fmt::Display for NewSessionError {
#[allow(deprecated)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: ", self.description())?;
match *self {
NewSessionError::BadWebdriverUrl(ref e) => write!(f, "{}", e),
NewSessionError::Failed(ref e) => write!(f, "{}", e),
NewSessionError::Lost(ref e) => write!(f, "{}", e),
NewSessionError::NotW3C(ref e) => write!(f, "{:?}", e),
NewSessionError::SessionNotCreated(ref e) => write!(f, "{}", e),
}
}
}
#[derive(Debug)]
pub enum CmdError {
Standard(WebDriver),
BadUrl(ParseError),
Failed(HError),
Lost(IOError),
NotJson(String),
Json(serde_json::Error),
NotW3C(serde_json::Value),
InvalidArgument(String, String),
ImageDecodeError(base64::DecodeError),
WaitTimeout,
}
macro_rules! is_helper {
($($variant:ident => $name:ident$(,)?),*) => {
$(
/// Return true if this error matches
#[doc = concat!("[`ErrorStatus::", stringify!($variant), "`].")]
pub fn $name(&self) -> bool {
matches!(self, CmdError::Standard(w) if w.error == ErrorStatus::$variant)
}
)*
}
}
impl CmdError {
is_helper! {
DetachedShadowRoot => is_detached_shadow_root,
ElementNotInteractable => is_element_not_interactable,
ElementNotSelectable => is_element_not_selectable,
InsecureCertificate => is_insecure_certificate,
InvalidArgument => is_invalid_argument,
InvalidCookieDomain => is_invalid_cookie_domain,
InvalidCoordinates => is_invalid_coordinates,
InvalidElementState => is_invalid_element_state,
InvalidSelector => is_invalid_selector,
InvalidSessionId => is_invalid_session_id,
JavascriptError => is_javascript_error,
MoveTargetOutOfBounds => is_move_target_out_of_bounds,
NoSuchAlert => is_no_such_alert,
NoSuchCookie => is_no_such_cookie,
NoSuchElement => is_no_such_element,
NoSuchFrame => is_no_such_frame,
NoSuchShadowRoot => is_no_such_shadow_root,
NoSuchWindow => is_no_such_window,
ScriptTimeout => is_script_timeout,
SessionNotCreated => is_session_not_created,
StaleElementReference => is_stale_element_reference,
Timeout => is_timeout,
UnableToCaptureScreen => is_unable_to_capture_screen,
UnableToSetCookie => is_unable_to_set_cookie,
UnexpectedAlertOpen => is_unexpected_alert_open,
UnknownCommand => is_unknown_command,
UnknownError => is_unknown_error,
UnknownMethod => is_unknown_method,
UnknownPath => is_unknown_path,
UnsupportedOperation => is_unsupported_operation
}
pub(crate) fn from_webdriver_error(e: WebDriver) -> Self {
CmdError::Standard(e)
}
}
impl Error for CmdError {
fn description(&self) -> &str {
match *self {
CmdError::Standard(..) => "webdriver returned error",
CmdError::BadUrl(..) => "bad url provided",
CmdError::Failed(..) => "webdriver could not be reached",
CmdError::Lost(..) => "webdriver connection lost",
CmdError::NotJson(..) => "webdriver returned invalid response",
CmdError::Json(..) => "webdriver returned incoherent response",
CmdError::NotW3C(..) => "webdriver returned non-conforming response",
CmdError::InvalidArgument(..) => "invalid argument provided",
CmdError::ImageDecodeError(..) => "error decoding image",
CmdError::WaitTimeout => "timeout waiting on condition",
}
}
fn cause(&self) -> Option<&dyn Error> {
match *self {
CmdError::Standard(ref e) => Some(e),
CmdError::BadUrl(ref e) => Some(e),
CmdError::Failed(ref e) => Some(e),
CmdError::Lost(ref e) => Some(e),
CmdError::Json(ref e) => Some(e),
CmdError::ImageDecodeError(ref e) => Some(e),
CmdError::NotJson(_)
| CmdError::NotW3C(_)
| CmdError::InvalidArgument(..)
| CmdError::WaitTimeout => None,
}
}
}
impl fmt::Display for CmdError {
#[allow(deprecated)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: ", self.description())?;
match *self {
CmdError::Standard(ref e) => write!(f, "{}", e),
CmdError::BadUrl(ref e) => write!(f, "{}", e),
CmdError::Failed(ref e) => write!(f, "{}", e),
CmdError::Lost(ref e) => write!(f, "{}", e),
CmdError::NotJson(ref e) => write!(f, "{}", e),
CmdError::Json(ref e) => write!(f, "{}", e),
CmdError::NotW3C(ref e) => write!(f, "{:?}", e),
CmdError::ImageDecodeError(ref e) => write!(f, "{:?}", e),
CmdError::InvalidArgument(ref arg, ref msg) => {
write!(f, "Invalid argument `{}`: {}", arg, msg)
}
CmdError::WaitTimeout => Ok(()),
}
}
}
impl From<IOError> for CmdError {
fn from(e: IOError) -> Self {
CmdError::Lost(e)
}
}
impl From<ParseError> for CmdError {
fn from(e: ParseError) -> Self {
CmdError::BadUrl(e)
}
}
impl From<HError> for CmdError {
fn from(e: HError) -> Self {
CmdError::Failed(e)
}
}
impl From<serde_json::Error> for CmdError {
fn from(e: serde_json::Error) -> Self {
CmdError::Json(e)
}
}
#[derive(Clone, Copy, Debug)]
pub struct InvalidWindowHandle;
impl fmt::Display for InvalidWindowHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, r#"Window handle cannot be "current""#)
}
}
impl Error for InvalidWindowHandle {}
impl From<InvalidWindowHandle> for CmdError {
fn from(_: InvalidWindowHandle) -> Self {
Self::NotW3C(serde_json::Value::String("current".to_string()))
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ErrorStatus {
DetachedShadowRoot,
ElementClickIntercepted,
ElementNotInteractable,
ElementNotSelectable,
InsecureCertificate,
InvalidArgument,
InvalidCookieDomain,
InvalidCoordinates,
InvalidElementState,
InvalidSelector,
InvalidSessionId,
JavascriptError,
MoveTargetOutOfBounds,
NoSuchAlert,
NoSuchCookie,
NoSuchElement,
NoSuchFrame,
NoSuchShadowRoot,
NoSuchWindow,
ScriptTimeout,
SessionNotCreated,
StaleElementReference,
Timeout,
UnableToCaptureScreen,
UnableToSetCookie,
UnexpectedAlertOpen,
UnknownCommand,
UnknownError,
UnknownMethod,
UnknownPath,
UnsupportedOperation,
}
impl ErrorStatus {
pub fn http_status(&self) -> StatusCode {
use self::ErrorStatus::*;
match *self {
DetachedShadowRoot => StatusCode::NOT_FOUND,
ElementClickIntercepted => StatusCode::BAD_REQUEST,
ElementNotInteractable => StatusCode::BAD_REQUEST,
ElementNotSelectable => StatusCode::BAD_REQUEST,
InsecureCertificate => StatusCode::BAD_REQUEST,
InvalidArgument => StatusCode::BAD_REQUEST,
InvalidCookieDomain => StatusCode::BAD_REQUEST,
InvalidCoordinates => StatusCode::BAD_REQUEST,
InvalidElementState => StatusCode::BAD_REQUEST,
InvalidSelector => StatusCode::BAD_REQUEST,
InvalidSessionId => StatusCode::NOT_FOUND,
JavascriptError => StatusCode::INTERNAL_SERVER_ERROR,
MoveTargetOutOfBounds => StatusCode::INTERNAL_SERVER_ERROR,
NoSuchAlert => StatusCode::NOT_FOUND,
NoSuchCookie => StatusCode::NOT_FOUND,
NoSuchElement => StatusCode::NOT_FOUND,
NoSuchFrame => StatusCode::NOT_FOUND,
NoSuchShadowRoot => StatusCode::NOT_FOUND,
NoSuchWindow => StatusCode::NOT_FOUND,
ScriptTimeout => StatusCode::INTERNAL_SERVER_ERROR,
SessionNotCreated => StatusCode::INTERNAL_SERVER_ERROR,
StaleElementReference => StatusCode::NOT_FOUND,
Timeout => StatusCode::INTERNAL_SERVER_ERROR,
UnableToCaptureScreen => StatusCode::BAD_REQUEST,
UnableToSetCookie => StatusCode::INTERNAL_SERVER_ERROR,
UnexpectedAlertOpen => StatusCode::INTERNAL_SERVER_ERROR,
UnknownCommand => StatusCode::NOT_FOUND,
UnknownError => StatusCode::INTERNAL_SERVER_ERROR,
UnknownMethod => StatusCode::METHOD_NOT_ALLOWED,
UnknownPath => StatusCode::NOT_FOUND,
UnsupportedOperation => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
impl fmt::Display for ErrorStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.description())
}
}
impl Error for ErrorStatus {}
impl Serialize for ErrorStatus {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.description().serialize(serializer)
}
}
macro_rules! define_error_strings {
($($variant:ident => $error_str:literal $(| $error_str_aliases:literal)*$(,)?),*) => {
impl ErrorStatus {
pub fn description(&self) -> &'static str {
use self::ErrorStatus::*;
match self {
$(
$variant => $error_str,
)*
}
}
}
impl FromStr for ErrorStatus {
type Err = CmdError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use self::ErrorStatus::*;
let status: ErrorStatus = match s {
$(
$error_str$( | $error_str_aliases)* => $variant,
)*
_ => return Err(CmdError::NotW3C(serde_json::Value::String(s.to_string()))),
};
Ok(status)
}
}
}
}
define_error_strings! {
DetachedShadowRoot => "detached shadow root",
ElementClickIntercepted => "element click intercepted",
ElementNotInteractable => "element not interactable" | "element not visible",
ElementNotSelectable => "element not selectable",
InsecureCertificate => "insecure certificate",
InvalidArgument => "invalid argument",
InvalidCookieDomain => "invalid cookie domain",
InvalidCoordinates => "invalid coordinates" | "invalid element coordinates",
InvalidElementState => "invalid element state",
InvalidSelector => "invalid selector",
InvalidSessionId => "invalid session id",
JavascriptError => "javascript error",
MoveTargetOutOfBounds => "move target out of bounds",
NoSuchAlert => "no such alert",
NoSuchCookie => "no such cookie",
NoSuchElement => "no such element",
NoSuchFrame => "no such frame",
NoSuchShadowRoot => "no such shadow root",
NoSuchWindow => "no such window",
ScriptTimeout => "script timeout",
SessionNotCreated => "session not created",
StaleElementReference => "stale element reference",
Timeout => "timeout",
UnableToCaptureScreen => "unable to capture screen",
UnableToSetCookie => "unable to set cookie",
UnexpectedAlertOpen => "unexpected alert open",
UnknownCommand => "unknown command",
UnknownError => "unknown error",
UnknownMethod => "unknown method",
UnknownPath => "unknown path",
UnsupportedOperation => "unsupported operation",
}
impl TryFrom<&str> for ErrorStatus {
type Error = CmdError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
value.parse()
}
}
impl TryFrom<CmdError> for ErrorStatus {
type Error = CmdError;
fn try_from(value: CmdError) -> Result<Self, Self::Error> {
match value {
CmdError::Standard(w) => Ok(w.error),
e => Err(e),
}
}
}
#[derive(Debug, Serialize)]
pub struct WebDriver {
pub error: ErrorStatus,
pub message: Cow<'static, str>,
pub stacktrace: String,
pub data: Option<serde_json::Value>,
}
impl fmt::Display for WebDriver {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl Error for WebDriver {}
impl WebDriver {
pub fn new(error: ErrorStatus, message: impl Into<Cow<'static, str>>) -> Self {
Self {
error,
message: message.into(),
stacktrace: String::new(),
data: None,
}
}
pub fn with_stacktrace(mut self, stacktrace: String) -> Self {
self.stacktrace = stacktrace;
self
}
pub fn with_data(mut self, data: serde_json::Value) -> Self {
self.data = Some(data);
self
}
pub fn error(&self) -> String {
self.error.to_string()
}
pub fn http_status(&self) -> StatusCode {
self.error.http_status()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ensure_display_error_doesnt_stackoverflow() {
println!("{}", CmdError::NotJson("test".to_string()));
println!("{}", NewSessionError::Lost(IOError::last_os_error()));
}
}