Blob Blame History Raw
use regex::{Regex, Captures};
use rustc_serialize::json::Json;

use hyper::method::Method;
use hyper::method::Method::{Get, Post, Delete};

use command::{WebDriverCommand, WebDriverMessage, WebDriverExtensionCommand,
              VoidWebDriverExtensionCommand};
use error::{WebDriverResult, WebDriverError, ErrorStatus};

fn standard_routes<U:WebDriverExtensionRoute>() -> Vec<(Method, &'static str, Route<U>)> {
    return vec![(Post, "/session", Route::NewSession),
                (Delete, "/session/{sessionId}", Route::DeleteSession),
                (Post, "/session/{sessionId}/url", Route::Get),
                (Get, "/session/{sessionId}/url", Route::GetCurrentUrl),
                (Post, "/session/{sessionId}/back", Route::GoBack),
                (Post, "/session/{sessionId}/forward", Route::GoForward),
                (Post, "/session/{sessionId}/refresh", Route::Refresh),
                (Get, "/session/{sessionId}/title", Route::GetTitle),
                (Get, "/session/{sessionId}/source", Route::GetPageSource),
                (Get, "/session/{sessionId}/window", Route::GetWindowHandle),
                (Get, "/session/{sessionId}/window/handles", Route::GetWindowHandles),
                (Delete, "/session/{sessionId}/window", Route::CloseWindow),
                (Get, "/session/{sessionId}/window/size", Route::GetWindowSize),
                (Post, "/session/{sessionId}/window/size", Route::SetWindowSize),
                (Get, "/session/{sessionId}/window/position", Route::GetWindowPosition),
                (Post, "/session/{sessionId}/window/position", Route::SetWindowPosition),
                (Get, "/session/{sessionId}/window/rect", Route::GetWindowRect),
                (Post, "/session/{sessionId}/window/rect", Route::SetWindowRect),
                (Post, "/session/{sessionId}/window/minimize", Route::MinimizeWindow),
                (Post, "/session/{sessionId}/window/maximize", Route::MaximizeWindow),
                (Post, "/session/{sessionId}/window/fullscreen", Route::FullscreenWindow),
                (Post, "/session/{sessionId}/window", Route::SwitchToWindow),
                (Post, "/session/{sessionId}/frame", Route::SwitchToFrame),
                (Post, "/session/{sessionId}/frame/parent", Route::SwitchToParentFrame),
                (Post, "/session/{sessionId}/element", Route::FindElement),
                (Post, "/session/{sessionId}/elements", Route::FindElements),
                (Post, "/session/{sessionId}/element/{elementId}/element", Route::FindElementElement),
                (Post, "/session/{sessionId}/element/{elementId}/elements", Route::FindElementElements),
                (Get, "/session/{sessionId}/element/active", Route::GetActiveElement),
                (Get, "/session/{sessionId}/element/{elementId}/displayed", Route::IsDisplayed),
                (Get, "/session/{sessionId}/element/{elementId}/selected", Route::IsSelected),
                (Get, "/session/{sessionId}/element/{elementId}/attribute/{name}", Route::GetElementAttribute),
                (Get, "/session/{sessionId}/element/{elementId}/property/{name}", Route::GetElementProperty),
                (Get, "/session/{sessionId}/element/{elementId}/css/{propertyName}", Route::GetCSSValue),
                (Get, "/session/{sessionId}/element/{elementId}/text", Route::GetElementText),
                (Get, "/session/{sessionId}/element/{elementId}/name", Route::GetElementTagName),
                (Get, "/session/{sessionId}/element/{elementId}/rect", Route::GetElementRect),
                (Get, "/session/{sessionId}/element/{elementId}/enabled", Route::IsEnabled),
                (Post, "/session/{sessionId}/execute/sync", Route::ExecuteScript),
                (Post, "/session/{sessionId}/execute/async", Route::ExecuteAsyncScript),
                (Get, "/session/{sessionId}/cookie", Route::GetCookies),
                (Get, "/session/{sessionId}/cookie/{name}", Route::GetNamedCookie),
                (Post, "/session/{sessionId}/cookie", Route::AddCookie),
                (Delete, "/session/{sessionId}/cookie", Route::DeleteCookies),
                (Delete, "/session/{sessionId}/cookie/{name}", Route::DeleteCookie),
                (Get, "/session/{sessionId}/timeouts", Route::GetTimeouts),
                (Post, "/session/{sessionId}/timeouts", Route::SetTimeouts),
                (Post, "/session/{sessionId}/element/{elementId}/click", Route::ElementClick),
                (Post, "/session/{sessionId}/element/{elementId}/tap", Route::ElementTap),
                (Post, "/session/{sessionId}/element/{elementId}/clear", Route::ElementClear),
                (Post, "/session/{sessionId}/element/{elementId}/value", Route::ElementSendKeys),
                (Post, "/session/{sessionId}/alert/dismiss", Route::DismissAlert),
                (Post, "/session/{sessionId}/alert/accept", Route::AcceptAlert),
                (Get, "/session/{sessionId}/alert/text", Route::GetAlertText),
                (Post, "/session/{sessionId}/alert/text", Route::SendAlertText),
                (Get, "/session/{sessionId}/screenshot", Route::TakeScreenshot),
                (Get, "/session/{sessionId}/element/{elementId}/screenshot", Route::TakeElementScreenshot),
                (Post, "/session/{sessionId}/actions", Route::PerformActions),
                (Delete, "/session/{sessionId}/actions", Route::ReleaseActions),
                (Get, "/status", Route::Status),]
}

#[derive(Clone, Copy, Debug)]
pub enum Route<U:WebDriverExtensionRoute> {
    NewSession,
    DeleteSession,
    Get,
    GetCurrentUrl,
    GoBack,
    GoForward,
    Refresh,
    GetTitle,
    GetPageSource,
    GetWindowHandle,
    GetWindowHandles,
    CloseWindow,
    GetWindowSize,  // deprecated
    SetWindowSize,  // deprecated
    GetWindowPosition,  // deprecated
    SetWindowPosition,  // deprecated
    GetWindowRect,
    SetWindowRect,
    MinimizeWindow,
    MaximizeWindow,
    FullscreenWindow,
    SwitchToWindow,
    SwitchToFrame,
    SwitchToParentFrame,
    FindElement,
    FindElements,
    FindElementElement,
    FindElementElements,
    GetActiveElement,
    IsDisplayed,
    IsSelected,
    GetElementAttribute,
    GetElementProperty,
    GetCSSValue,
    GetElementText,
    GetElementTagName,
    GetElementRect,
    IsEnabled,
    ExecuteScript,
    ExecuteAsyncScript,
    GetCookies,
    GetNamedCookie,
    AddCookie,
    DeleteCookies,
    DeleteCookie,
    GetTimeouts,
    SetTimeouts,
    ElementClick,
    ElementTap,
    ElementClear,
    ElementSendKeys,
    PerformActions,
    ReleaseActions,
    DismissAlert,
    AcceptAlert,
    GetAlertText,
    SendAlertText,
    TakeScreenshot,
    TakeElementScreenshot,
    Status,
    Extension(U),
}

pub trait WebDriverExtensionRoute : Clone + Send + PartialEq {
    type Command: WebDriverExtensionCommand + 'static;

    fn command(&self, &Captures, &Json) -> WebDriverResult<WebDriverCommand<Self::Command>>;
}

#[derive(Clone, Debug, PartialEq)]
pub struct VoidWebDriverExtensionRoute;

impl WebDriverExtensionRoute for VoidWebDriverExtensionRoute {
    type Command = VoidWebDriverExtensionCommand;

    fn command(&self, _:&Captures, _:&Json) -> WebDriverResult<WebDriverCommand<VoidWebDriverExtensionCommand>> {
        panic!("No extensions implemented");
    }
}

#[derive(Clone, Debug)]
struct RequestMatcher<U: WebDriverExtensionRoute> {
    method: Method,
    path_regexp: Regex,
    match_type: Route<U>
}

impl <U: WebDriverExtensionRoute> RequestMatcher<U> {
    pub fn new(method: Method, path: &str, match_type: Route<U>) -> RequestMatcher<U> {
        let path_regexp = RequestMatcher::<U>::compile_path(path);
        RequestMatcher {
            method: method,
            path_regexp: path_regexp,
            match_type: match_type
        }
    }

    pub fn get_match<'t>(&'t self, method: Method, path: &'t str) -> (bool, Option<Captures>) {
        let captures = self.path_regexp.captures(path);
        (method == self.method, captures)
    }

    fn compile_path(path: &str) -> Regex {
        let mut rv = String::new();
        rv.push_str("^");
        let components = path.split('/');
        for component in components {
            if component.starts_with("{") {
                if !component.ends_with("}") {
                    panic!("Invalid url pattern")
                }
                rv.push_str(&format!("(?P<{}>[^/]+)/", &component[1..component.len()-1])[..]);
            } else {
                rv.push_str(&format!("{}/", component)[..]);
            }
        }
        //Remove the trailing /
        rv.pop();
        rv.push_str("$");
        //This will fail at runtime if the regexp is invalid
        Regex::new(&rv[..]).unwrap()
    }
}

#[derive(Debug)]
pub struct WebDriverHttpApi<U: WebDriverExtensionRoute> {
    routes: Vec<(Method, RequestMatcher<U>)>,
}

impl <U: WebDriverExtensionRoute> WebDriverHttpApi<U> {
    pub fn new(extension_routes: &[(Method, &str, U)]) -> WebDriverHttpApi<U> {
        let mut rv = WebDriverHttpApi::<U> {
            routes: vec![],
        };
        debug!("Creating routes");
        for &(ref method, ref url, ref match_type) in standard_routes::<U>().iter() {
            rv.add(method.clone(), *url, (*match_type).clone());
        };
        for &(ref method, ref url, ref extension_route) in extension_routes.iter() {
            rv.add(method.clone(), *url, Route::Extension(extension_route.clone()));
        };
        rv
    }

    fn add(&mut self, method: Method, path: &str, match_type: Route<U>) {
        let http_matcher = RequestMatcher::new(method.clone(), path, match_type);
        self.routes.push((method, http_matcher));
    }

    pub fn decode_request(&self, method: Method, path: &str, body: &str) -> WebDriverResult<WebDriverMessage<U>> {
        let mut error = ErrorStatus::UnknownPath;
        for &(ref match_method, ref matcher) in self.routes.iter() {
            if method == *match_method {
                let (method_match, captures) = matcher.get_match(method.clone(), path);
                if captures.is_some() {
                    if method_match {
                        return WebDriverMessage::from_http(matcher.match_type.clone(),
                                                           &captures.unwrap(),
                                                           body,
                                                           method == Post)
                    } else {
                        error = ErrorStatus::UnknownMethod;
                    }
                }
            }
        }
        Err(WebDriverError::new(error,
                                format!("{} {} did not match a known command", method, path)))
    }
}