webdriver 0.3.1

Library implementing the wire protocol for the W3C WebDriver specification.
Documentation
use regex::{Regex, Captures};

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

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

static routes: [(Method, &'static str, Route); 41] = [
    (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}/window_handle", Route::GetWindowHandle),
    (Get, "/session/{sessionId}/window_handles", Route::GetWindowHandles),
    (Delete, "/session/{sessionId}/window_handle", Route::Close),
    (Post, "/session/{sessionId}/window/size", Route::SetWindowSize),
    (Get, "/session/{sessionId}/window/size", Route::GetWindowSize),
    (Post, "/session/{sessionId}/window/maximize", Route::MaximizeWindow),
    (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),
    (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}/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", Route::ExecuteScript),
    (Post, "/session/{sessionId}/execute_async", Route::ExecuteAsyncScript),
    (Get, "/session/{sessionId}/cookie", Route::GetCookie),
    (Post, "/session/{sessionId}/cookie", Route::AddCookie),
    (Post, "/session/{sessionId}/timeouts", Route::SetTimeouts),
    //(Post, "/session/{sessionId}/actions", Route::Actions),
    (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}/sendKeys", Route::ElementSendKeys),
    (Post, "/session/{sessionId}/dismiss_alert", Route::DismissAlert),
    (Post, "/session/{sessionId}/accept_alert", Route::AcceptAlert),
    (Get, "/session/{sessionId}/alert_text", Route::GetAlertText),
    (Post, "/session/{sessionId}/alert_text", Route::SendAlertText),
    (Get, "/session/{sessionId}/screenshot", Route::TakeScreenshot)
];

#[derive(Clone, Copy)]
pub enum Route {
    NewSession,
    DeleteSession,
    Get,
    GetCurrentUrl,
    GoBack,
    GoForward,
    Refresh,
    GetTitle,
    GetWindowHandle,
    GetWindowHandles,
    Close,
    SetWindowSize,
    GetWindowSize,
    MaximizeWindow,
    SwitchToWindow,
    SwitchToFrame,
    SwitchToParentFrame,
    FindElement,
    FindElements,
    IsDisplayed,
    IsSelected,
    GetElementAttribute,
    GetCSSValue,
    GetElementText,
    GetElementTagName,
    GetElementRect,
    IsEnabled,
    ExecuteScript,
    ExecuteAsyncScript,
    GetCookie,
    AddCookie,
    SetTimeouts,
    //Actions XXX - once I understand the spec, perhaps
    ElementClick,
    ElementTap,
    ElementClear,
    ElementSendKeys,
    DismissAlert,
    AcceptAlert,
    GetAlertText,
    SendAlertText,
    TakeScreenshot
}

#[derive(Clone)]
struct RequestMatcher {
    method: Method,
    path_regexp: Regex,
    match_type: Route
}

impl RequestMatcher {
    pub fn new(method: Method, path: &str, match_type: Route) -> RequestMatcher {
        let path_regexp = RequestMatcher::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 mut 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]).as_slice());
            } else {
                rv.push_str(format!("{}/", component).as_slice());
            }
        }
        //Remove the trailing /
        rv.pop();
        rv.push_str("$");
        //This will fail at runtime if the regexp is invalid
        Regex::new(rv.as_slice()).unwrap()
    }
}

pub struct WebDriverHttpApi {
    routes: Vec<(Method, RequestMatcher)>
}

impl WebDriverHttpApi {
    pub fn new() -> MessageBuilder {
        let rv = MessageBuilder {
            http_matchers: vec![]
        }
        debug!("Creating routes");
        for &(ref method, ref url, ref match_type) in commands.iter() {
            rv.add(method.clone(), *url, *match_type);
        };
        rv
    }

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

    pub fn decode_request(&self, method: Method, path: &str, body: &str) -> WebDriverResult<WebDriverMessage> {
        let mut error = ErrorStatus::UnknownPath;
        for &(ref match_method, ref matcher) in self.http_matchers.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,
                                                           &captures.unwrap(),
                                                           body)
                    } else {
                        error = ErrorStatus::UnknownMethod;
                    }
                }
            }
        }
        Err(WebDriverError::new(error,
                                format!("{} {} did not match a known command", method, path).as_slice()))
    }
}