Blob Blame History Raw
use hyper::status::StatusCode;
use rustc_serialize::base64::FromBase64Error;
use rustc_serialize::json::{DecoderError, Json, ParserError, ToJson};
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::convert::From;
use std::error::Error;
use std::fmt;
use std::io;

#[derive(Debug, PartialEq)]
pub enum ErrorStatus {
    /// The [`ElementClick`] command could not be completed because the
    /// [element] receiving the events is obscuring the element that was
    /// requested clicked.
    ///
    /// [`ElementClick`]:
    /// ../command/enum.WebDriverCommand.html#variant.ElementClick
    /// [element]: ../common/struct.WebElement.html
    ElementClickIntercepted,

    /// A [command] could not be completed because the element is not pointer-
    /// or keyboard interactable.
    ///
    /// [command]: ../command/index.html
    ElementNotInteractable,

    /// An attempt was made to select an [element] that cannot be selected.
    ///
    /// [element]: ../common/struct.WebElement.html
    ElementNotSelectable,

    /// Navigation caused the user agent to hit a certificate warning, which is
    /// usually the result of an expired or invalid TLS certificate.
    InsecureCertificate,

    /// The arguments passed to a [command] are either invalid or malformed.
    ///
    /// [command]: ../command/index.html
    InvalidArgument,

    /// An illegal attempt was made to set a cookie under a different domain
    /// than the current page.
    InvalidCookieDomain,

    /// The coordinates provided to an interactions operation are invalid.
    InvalidCoordinates,

    /// A [command] could not be completed because the element is an invalid
    /// state, e.g. attempting to click an element that is no longer attached
    /// to the document.
    ///
    /// [command]: ../command/index.html
    InvalidElementState,

    /// Argument was an invalid selector.
    InvalidSelector,

    /// Occurs if the given session ID is not in the list of active sessions,
    /// meaning the session either does not exist or that it’s not active.
    InvalidSessionId,

    /// An error occurred while executing JavaScript supplied by the user.
    JavascriptError,

    /// The target for mouse interaction is not in the browser’s viewport and
    /// cannot be brought into that viewport.
    MoveTargetOutOfBounds,

    /// An attempt was made to operate on a modal dialogue when one was not
    /// open.
    NoSuchAlert,

    /// No cookie matching the given path name was found amongst the associated
    /// cookies of the current browsing context’s active document.
    NoSuchCookie,

    /// An [element] could not be located on the page using the given search
    /// parameters.
    ///
    /// [element]: ../common/struct.WebElement.html
    NoSuchElement,

    /// A [command] to switch to a frame could not be satisfied because the
    /// frame could not be found.
    ///
    /// [command]: ../command/index.html
    NoSuchFrame,

    /// A [command] to switch to a window could not be satisfied because the
    /// window could not be found.
    ///
    /// [command]: ../command/index.html
    NoSuchWindow,

    /// A script did not complete before its timeout expired.
    ScriptTimeout,

    /// A new session could not be created.
    SessionNotCreated,

    /// A [command] failed because the referenced [element] is no longer
    /// attached to the DOM.
    ///
    /// [command]: ../command/index.html
    /// [element]: ../common/struct.WebElement.html
    StaleElementReference,

    /// An operation did not complete before its timeout expired.
    Timeout,

    /// A screen capture was made impossible.
    UnableToCaptureScreen,

    /// Setting the cookie’s value could not be done.
    UnableToSetCookie,

    /// A modal dialogue was open, blocking this operation.
    UnexpectedAlertOpen,

    /// The requested command could not be executed because it does not exist.
    UnknownCommand,

    /// An unknown error occurred in the remote end whilst processing the
    /// [command].
    ///
    /// [command]: ../command/index.html
    UnknownError,

    /// The requested [command] matched a known endpoint, but did not match a
    /// method for that endpoint.
    ///
    /// [command]: ../command/index.html
    UnknownMethod,

    UnknownPath,

    /// Indicates that a [command] that should have executed properly is not
    /// currently supported.
    UnsupportedOperation,
}

impl ErrorStatus {
    /// Returns the string serialisation of the error type.
    pub fn error_code(&self) -> &'static str {
        use self::ErrorStatus::*;
        match *self {
            ElementClickIntercepted => "element click intercepted",
            ElementNotInteractable => "element not interactable",
            ElementNotSelectable => "element not selectable",
            InsecureCertificate => "insecure certificate",
            InvalidArgument => "invalid argument",
            InvalidCookieDomain => "invalid cookie domain",
            InvalidCoordinates => "invalid 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",
            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 |
            UnknownError => "unknown error",
            UnknownMethod => "unknown method",
            UnknownPath => "unknown command",
            UnsupportedOperation => "unsupported operation",
        }
    }

    /// Returns the correct HTTP status code associated with the error type.
    pub fn http_status(&self) -> StatusCode {
        use self::ErrorStatus::*;
        use self::StatusCode::*;
        match *self {
            ElementClickIntercepted => BadRequest,
            ElementNotInteractable => BadRequest,
            ElementNotSelectable => BadRequest,
            InsecureCertificate => BadRequest,
            InvalidArgument => BadRequest,
            InvalidCookieDomain => BadRequest,
            InvalidCoordinates => BadRequest,
            InvalidElementState => BadRequest,
            InvalidSelector => BadRequest,
            InvalidSessionId => NotFound,
            JavascriptError => InternalServerError,
            MoveTargetOutOfBounds => InternalServerError,
            NoSuchAlert => NotFound,
            NoSuchCookie => NotFound,
            NoSuchElement => NotFound,
            NoSuchFrame => NotFound,
            NoSuchWindow => NotFound,
            ScriptTimeout => RequestTimeout,
            SessionNotCreated => InternalServerError,
            StaleElementReference => NotFound,
            Timeout => RequestTimeout,
            UnableToCaptureScreen => BadRequest,
            UnableToSetCookie => InternalServerError,
            UnexpectedAlertOpen => InternalServerError,
            UnknownCommand => NotFound,
            UnknownError => InternalServerError,
            UnknownMethod => MethodNotAllowed,
            UnknownPath => NotFound,
            UnsupportedOperation => InternalServerError,
        }
    }
}

/// Deserialises error type from string.
impl From<String> for ErrorStatus {
    fn from(s: String) -> ErrorStatus {
        use self::ErrorStatus::*;
        match &*s {
            "element click intercepted" => ElementClickIntercepted,
            "element not interactable" | "element not visible" => ElementNotInteractable,
            "element not selectable" => ElementNotSelectable,
            "insecure certificate" => InsecureCertificate,
            "invalid argument" => InvalidArgument,
            "invalid cookie domain" => InvalidCookieDomain,
            "invalid coordinates" | "invalid element coordinates" => InvalidCoordinates,
            "invalid element state" => InvalidElementState,
            "invalid selector" => InvalidSelector,
            "invalid session id" => InvalidSessionId,
            "javascript error" => JavascriptError,
            "move target out of bounds" => MoveTargetOutOfBounds,
            "no such alert" => NoSuchAlert,
            "no such element" => NoSuchElement,
            "no such frame" => NoSuchFrame,
            "no such window" => NoSuchWindow,
            "script timeout" => ScriptTimeout,
            "session not created" => SessionNotCreated,
            "stale element reference" => StaleElementReference,
            "timeout" => Timeout,
            "unable to capture screen" => UnableToCaptureScreen,
            "unable to set cookie" => UnableToSetCookie,
            "unexpected alert open" => UnexpectedAlertOpen,
            "unknown command" => UnknownCommand,
            "unknown error" => UnknownError,
            "unsupported operation" => UnsupportedOperation,
            _ => UnknownError,
        }
    }
}

pub type WebDriverResult<T> = Result<T, WebDriverError>;

#[derive(Debug)]
pub struct WebDriverError {
    pub error: ErrorStatus,
    pub message: Cow<'static, str>,
    pub stack: Cow<'static, str>,
    pub delete_session: bool,
}

impl WebDriverError {
    pub fn new<S>(error: ErrorStatus, message: S) -> WebDriverError
        where S: Into<Cow<'static, str>>
    {
        WebDriverError {
            error: error,
            message: message.into(),
            stack: "".into(),
            delete_session: false,
        }
    }

    pub fn new_with_stack<S>(error: ErrorStatus, message: S, stack: S) -> WebDriverError
        where S: Into<Cow<'static, str>>
    {
        WebDriverError {
            error: error,
            message: message.into(),
            stack: stack.into(),
            delete_session: false,
        }
    }

    pub fn error_code(&self) -> &'static str {
        self.error.error_code()
    }

    pub fn http_status(&self) -> StatusCode {
        self.error.http_status()
    }

    pub fn to_json_string(&self) -> String {
        self.to_json().to_string()
    }
}

impl ToJson for WebDriverError {
    fn to_json(&self) -> Json {
        let mut data = BTreeMap::new();
        data.insert("error".into(), self.error_code().to_json());
        data.insert("message".into(), self.message.to_json());
        data.insert("stacktrace".into(), self.stack.to_json());

        let mut wrapper = BTreeMap::new();
        wrapper.insert("value".into(), Json::Object(data));
        Json::Object(wrapper)
    }
}

impl Error for WebDriverError {
    fn description(&self) -> &str {
        self.error_code()
    }

    fn cause(&self) -> Option<&Error> {
        None
    }
}

impl fmt::Display for WebDriverError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.message.fmt(f)
    }
}

impl From<ParserError> for WebDriverError {
    fn from(err: ParserError) -> WebDriverError {
        WebDriverError::new(ErrorStatus::UnknownError, err.description().to_string())
    }
}

impl From<io::Error> for WebDriverError {
    fn from(err: io::Error) -> WebDriverError {
        WebDriverError::new(ErrorStatus::UnknownError, err.description().to_string())
    }
}

impl From<DecoderError> for WebDriverError {
    fn from(err: DecoderError) -> WebDriverError {
        WebDriverError::new(ErrorStatus::UnknownError, err.description().to_string())
    }
}

impl From<FromBase64Error> for WebDriverError {
    fn from(err: FromBase64Error) -> WebDriverError {
        WebDriverError::new(ErrorStatus::UnknownError, err.description().to_string())
    }
}

impl From<Box<Error>> for WebDriverError {
    fn from(err: Box<Error>) -> WebDriverError {
        WebDriverError::new(ErrorStatus::UnknownError, err.description().to_string())
    }
}