Blob Blame History Raw
use command::Parameters;
use common::{Nullable, WebElement};
use error::{WebDriverResult, WebDriverError, ErrorStatus};
use rustc_serialize::json::{ToJson, Json};
use unicode_segmentation::UnicodeSegmentation;
use std::collections::BTreeMap;
use std::default::Default;

#[derive(Debug, PartialEq)]
pub struct ActionSequence {
    pub id: Nullable<String>,
    pub actions: ActionsType
}

impl Parameters for ActionSequence {
    fn from_json(body: &Json) -> WebDriverResult<ActionSequence> {
        let data = try_opt!(body.as_object(),
                            ErrorStatus::InvalidArgument,
                            "Actions chain was not an object");

        let type_name = try_opt!(try_opt!(data.get("type"),
                                          ErrorStatus::InvalidArgument,
                                          "Missing type parameter").as_string(),
                                 ErrorStatus::InvalidArgument,
                                 "Parameter ;type' was not a string");

        let id = match data.get("id") {
            Some(x) => Some(try_opt!(x.as_string(),
                                     ErrorStatus::InvalidArgument,
                                     "Parameter 'id' was not a string").to_owned()),
            None => None
        };


        // Note that unlike the spec we get the pointer parameters in ActionsType::from_json

        let actions = match type_name {
            "none" | "key" | "pointer" => try!(ActionsType::from_json(&body)),
            _ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument,
                                                "Invalid action type"))
        };

        Ok(ActionSequence {
            id: id.into(),
            actions: actions
        })
    }
}

impl ToJson for ActionSequence {
    fn to_json(&self) -> Json {
        let mut data: BTreeMap<String, Json> = BTreeMap::new();
        data.insert("id".into(), self.id.to_json());
        let (action_type, actions) = match self.actions {
            ActionsType::Null(ref actions) => {
                ("none",
                 actions.iter().map(|x| x.to_json()).collect::<Vec<Json>>())
            }
            ActionsType::Key(ref actions) => {
                ("key",
                 actions.iter().map(|x| x.to_json()).collect::<Vec<Json>>())
            }
            ActionsType::Pointer(ref parameters, ref actions) => {
                data.insert("parameters".into(), parameters.to_json());
                ("pointer",
                 actions.iter().map(|x| x.to_json()).collect::<Vec<Json>>())
            }
        };
        data.insert("type".into(), action_type.to_json());
        data.insert("actions".into(), actions.to_json());
        Json::Object(data)
    }
}

#[derive(Debug, PartialEq)]
pub enum ActionsType {
    Null(Vec<NullActionItem>),
    Key(Vec<KeyActionItem>),
    Pointer(PointerActionParameters, Vec<PointerActionItem>)
}

impl Parameters for ActionsType {
    fn from_json(body: &Json) -> WebDriverResult<ActionsType> {
        // These unwraps are OK as long as this is only called from ActionSequence::from_json
        let data = body.as_object().expect("Body should be a JSON Object");
        let actions_type = body.find("type").and_then(|x| x.as_string()).expect("Type should be a string");
        let actions_chain = try_opt!(try_opt!(data.get("actions"),
                                              ErrorStatus::InvalidArgument,
                                              "Missing actions parameter").as_array(),
                                     ErrorStatus::InvalidArgument,
                                     "Parameter 'actions' was not an array");
        match actions_type {
            "none" => {
                let mut actions = Vec::with_capacity(actions_chain.len());
                for action_body in actions_chain.iter() {
                    actions.push(try!(NullActionItem::from_json(action_body)));
                };
                Ok(ActionsType::Null(actions))
            },
            "key" => {
                let mut actions = Vec::with_capacity(actions_chain.len());
                for action_body in actions_chain.iter() {
                    actions.push(try!(KeyActionItem::from_json(action_body)));
                };
                Ok(ActionsType::Key(actions))
            },
            "pointer" => {
                let mut actions = Vec::with_capacity(actions_chain.len());
                let parameters = match data.get("parameters") {
                    Some(x) => try!(PointerActionParameters::from_json(x)),
                    None => Default::default()
                };

                for action_body in actions_chain.iter() {
                    actions.push(try!(PointerActionItem::from_json(action_body)));
                }
                Ok(ActionsType::Pointer(parameters, actions))
            }
            _ => panic!("Got unexpected action type after checking type")
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum PointerType {
    Mouse,
    Pen,
    Touch,
}

impl Parameters for PointerType {
    fn from_json(body: &Json) -> WebDriverResult<PointerType> {
        match body.as_string() {
            Some("mouse") => Ok(PointerType::Mouse),
            Some("pen") => Ok(PointerType::Pen),
            Some("touch") => Ok(PointerType::Touch),
            Some(_) => Err(WebDriverError::new(
                ErrorStatus::InvalidArgument,
                "Unsupported pointer type"
            )),
            None => Err(WebDriverError::new(
                ErrorStatus::InvalidArgument,
                "Pointer type was not a string"
            ))
        }
    }
}

impl ToJson for PointerType {
    fn to_json(&self) -> Json {
        match *self {
            PointerType::Mouse => "mouse".to_json(),
            PointerType::Pen => "pen".to_json(),
            PointerType::Touch => "touch".to_json(),
        }.to_json()
    }
}

impl Default for PointerType {
    fn default() -> PointerType {
        PointerType::Mouse
    }
}

#[derive(Debug, Default, PartialEq)]
pub struct PointerActionParameters {
    pub pointer_type: PointerType
}

impl Parameters for PointerActionParameters {
    fn from_json(body: &Json) -> WebDriverResult<PointerActionParameters> {
        let data = try_opt!(body.as_object(),
                            ErrorStatus::InvalidArgument,
                            "Parameter 'parameters' was not an object");
        let pointer_type = match data.get("pointerType") {
            Some(x) => try!(PointerType::from_json(x)),
            None => PointerType::default()
        };
        Ok(PointerActionParameters {
            pointer_type: pointer_type
        })
    }
}

impl ToJson for PointerActionParameters {
    fn to_json(&self) -> Json {
        let mut data = BTreeMap::new();
        data.insert("pointerType".to_owned(),
                    self.pointer_type.to_json());
        Json::Object(data)
    }
}

#[derive(Debug, PartialEq)]
pub enum NullActionItem {
    General(GeneralAction)
}

impl Parameters for NullActionItem {
    fn from_json(body: &Json) -> WebDriverResult<NullActionItem> {
        let data = try_opt!(body.as_object(),
                            ErrorStatus::InvalidArgument,
                            "Actions chain was not an object");
        let type_name = try_opt!(
            try_opt!(data.get("type"),
                     ErrorStatus::InvalidArgument,
                     "Missing 'type' parameter").as_string(),
            ErrorStatus::InvalidArgument,
            "Parameter 'type' was not a string");
        match type_name {
            "pause" => Ok(NullActionItem::General(
                try!(GeneralAction::from_json(body)))),
            _ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument,
                                                "Invalid type attribute"))
        }
    }
}

impl ToJson for NullActionItem {
    fn to_json(&self) -> Json {
        match self {
            &NullActionItem::General(ref x) => x.to_json(),
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum KeyActionItem {
    General(GeneralAction),
    Key(KeyAction)
}

impl Parameters for KeyActionItem {
    fn from_json(body: &Json) -> WebDriverResult<KeyActionItem> {
        let data = try_opt!(body.as_object(),
                            ErrorStatus::InvalidArgument,
                            "Key action item was not an object");
        let type_name = try_opt!(
            try_opt!(data.get("type"),
                     ErrorStatus::InvalidArgument,
                     "Missing 'type' parameter").as_string(),
            ErrorStatus::InvalidArgument,
            "Parameter 'type' was not a string");
        match type_name {
            "pause" => Ok(KeyActionItem::General(
                try!(GeneralAction::from_json(body)))),
            _ => Ok(KeyActionItem::Key(
                try!(KeyAction::from_json(body))))
        }
    }
}

impl ToJson for KeyActionItem {
    fn to_json(&self) -> Json {
        match *self {
            KeyActionItem::General(ref x) => x.to_json(),
            KeyActionItem::Key(ref x) => x.to_json()
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum PointerActionItem {
    General(GeneralAction),
    Pointer(PointerAction)
}

impl Parameters for PointerActionItem {
    fn from_json(body: &Json) -> WebDriverResult<PointerActionItem> {
        let data = try_opt!(body.as_object(),
                            ErrorStatus::InvalidArgument,
                            "Pointer action item was not an object");
        let type_name = try_opt!(
            try_opt!(data.get("type"),
                     ErrorStatus::InvalidArgument,
                     "Missing 'type' parameter").as_string(),
            ErrorStatus::InvalidArgument,
            "Parameter 'type' was not a string");

        match type_name {
            "pause" => Ok(PointerActionItem::General(try!(GeneralAction::from_json(body)))),
            _ => Ok(PointerActionItem::Pointer(try!(PointerAction::from_json(body))))
        }
    }
}

impl ToJson for PointerActionItem {
    fn to_json(&self) -> Json {
        match self {
            &PointerActionItem::General(ref x) => x.to_json(),
            &PointerActionItem::Pointer(ref x) => x.to_json()
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum GeneralAction {
    Pause(PauseAction)
}

impl Parameters for GeneralAction {
    fn from_json(body: &Json) -> WebDriverResult<GeneralAction> {
        match body.find("type").and_then(|x| x.as_string()) {
            Some("pause") => Ok(GeneralAction::Pause(try!(PauseAction::from_json(body)))),
            _ => Err(WebDriverError::new(ErrorStatus::InvalidArgument,
                                         "Invalid or missing type attribute"))
        }
    }
}

impl ToJson for GeneralAction {
    fn to_json(&self) -> Json {
        match self {
            &GeneralAction::Pause(ref x) => x.to_json()
        }
    }
}

#[derive(Debug, PartialEq)]
pub struct PauseAction {
    pub duration: u64
}

impl Parameters for PauseAction {
    fn from_json(body: &Json) -> WebDriverResult<PauseAction> {
        let default = Json::U64(0);
        Ok(PauseAction {
            duration: try_opt!(body.find("duration").unwrap_or(&default).as_u64(),
                               ErrorStatus::InvalidArgument,
                               "Parameter 'duration' was not a positive integer")
        })
    }
}

impl ToJson for PauseAction {
    fn to_json(&self) -> Json {
        let mut data = BTreeMap::new();
        data.insert("type".to_owned(),
                    "pause".to_json());
        data.insert("duration".to_owned(),
                    self.duration.to_json());
        Json::Object(data)
    }
}

#[derive(Debug, PartialEq)]
pub enum KeyAction {
    Up(KeyUpAction),
    Down(KeyDownAction)
}

impl Parameters for KeyAction {
    fn from_json(body: &Json) -> WebDriverResult<KeyAction> {
        match body.find("type").and_then(|x| x.as_string()) {
            Some("keyDown") => Ok(KeyAction::Down(try!(KeyDownAction::from_json(body)))),
            Some("keyUp") => Ok(KeyAction::Up(try!(KeyUpAction::from_json(body)))),
            Some(_) | None => Err(WebDriverError::new(ErrorStatus::InvalidArgument,
                                                      "Invalid type attribute value for key action"))
        }
    }
}

impl ToJson for KeyAction {
    fn to_json(&self) -> Json {
        match self {
            &KeyAction::Down(ref x) => x.to_json(),
            &KeyAction::Up(ref x) => x.to_json(),
        }
    }
}

fn validate_key_value(value_str: &str) -> WebDriverResult<String> {
    let mut graphemes = value_str.graphemes(true);
    let value = if let Some(g) = graphemes.next() {
        g
    } else {
        return Err(WebDriverError::new(
            ErrorStatus::InvalidArgument,
            "Parameter 'value' was an empty string"))
    };
    if graphemes.next().is_some() {
        return Err(WebDriverError::new(
            ErrorStatus::InvalidArgument,
            "Parameter 'value' contained multiple graphemes"))
    };
    Ok(value.to_string())
}

#[derive(Debug, PartialEq)]
pub struct KeyUpAction {
    pub value: String
}

impl Parameters for KeyUpAction {
    fn from_json(body: &Json) -> WebDriverResult<KeyUpAction> {
        let value_str = try_opt!(
                try_opt!(body.find("value"),
                         ErrorStatus::InvalidArgument,
                         "Missing value parameter").as_string(),
                ErrorStatus::InvalidArgument,
            "Parameter 'value' was not a string");

        let value = try!(validate_key_value(value_str));
        Ok(KeyUpAction {
            value: value
        })
    }
}

impl ToJson for KeyUpAction {
    fn to_json(&self) -> Json {
        let mut data = BTreeMap::new();
        data.insert("type".to_owned(),
                    "keyUp".to_json());
        data.insert("value".to_string(),
                    self.value.to_string().to_json());
        Json::Object(data)
    }
}

#[derive(Debug, PartialEq)]
pub struct KeyDownAction {
    pub value: String
}

impl Parameters for KeyDownAction {
    fn from_json(body: &Json) -> WebDriverResult<KeyDownAction> {
        let value_str = try_opt!(
                try_opt!(body.find("value"),
                         ErrorStatus::InvalidArgument,
                         "Missing value parameter").as_string(),
                ErrorStatus::InvalidArgument,
            "Parameter 'value' was not a string");
        let value = try!(validate_key_value(value_str));
        Ok(KeyDownAction {
            value: value
        })
    }
}

impl ToJson for KeyDownAction {
    fn to_json(&self) -> Json {
        let mut data = BTreeMap::new();
        data.insert("type".to_owned(),
                    "keyDown".to_json());
        data.insert("value".to_owned(),
                    self.value.to_string().to_json());
        Json::Object(data)
    }
}

#[derive(Debug, PartialEq)]
pub enum PointerOrigin {
    Viewport,
    Pointer,
    Element(WebElement),
}

impl Parameters for PointerOrigin {
    fn from_json(body: &Json) -> WebDriverResult<PointerOrigin> {
        match *body {
            Json::String(ref x) => {
                match &**x {
                    "viewport" => Ok(PointerOrigin::Viewport),
                    "pointer" => Ok(PointerOrigin::Pointer),
                    _ => Err(WebDriverError::new(ErrorStatus::InvalidArgument,
                                                 "Unknown pointer origin"))
                }
            },
            Json::Object(_) => Ok(PointerOrigin::Element(try!(WebElement::from_json(body)))),
            _ => Err(WebDriverError::new(ErrorStatus::InvalidArgument,
                        "Pointer origin was not a string or an object"))
        }
    }
}

impl ToJson for PointerOrigin {
    fn to_json(&self) -> Json {
        match *self {
            PointerOrigin::Viewport => "viewport".to_json(),
            PointerOrigin::Pointer => "pointer".to_json(),
            PointerOrigin::Element(ref x) => x.to_json(),
        }
    }
}

impl Default for PointerOrigin {
    fn default() -> PointerOrigin {
        PointerOrigin::Viewport
    }
}

#[derive(Debug, PartialEq)]
pub enum PointerAction {
    Up(PointerUpAction),
    Down(PointerDownAction),
    Move(PointerMoveAction),
    Cancel
}

impl Parameters for PointerAction {
    fn from_json(body: &Json) -> WebDriverResult<PointerAction> {
        match body.find("type").and_then(|x| x.as_string()) {
            Some("pointerUp") => Ok(PointerAction::Up(try!(PointerUpAction::from_json(body)))),
            Some("pointerDown") => Ok(PointerAction::Down(try!(PointerDownAction::from_json(body)))),
            Some("pointerMove") => Ok(PointerAction::Move(try!(PointerMoveAction::from_json(body)))),
            Some("pointerCancel") => Ok(PointerAction::Cancel),
            Some(_) | None => Err(WebDriverError::new(
                ErrorStatus::InvalidArgument,
                "Missing or invalid type argument for pointer action"))
        }
    }
}

impl ToJson for PointerAction {
    fn to_json(&self) -> Json {
        match self {
            &PointerAction::Down(ref x) => x.to_json(),
            &PointerAction::Up(ref x) => x.to_json(),
            &PointerAction::Move(ref x) => x.to_json(),
            &PointerAction::Cancel => {
                let mut data = BTreeMap::new();
                data.insert("type".to_owned(),
                            "pointerCancel".to_json());
                Json::Object(data)
            }
        }
    }
}

#[derive(Debug, PartialEq)]
pub struct PointerUpAction {
    pub button: u64,
}

impl Parameters for PointerUpAction {
    fn from_json(body: &Json) -> WebDriverResult<PointerUpAction> {
        let button = try_opt!(
            try_opt!(body.find("button"),
                     ErrorStatus::InvalidArgument,
                     "Missing button parameter").as_u64(),
            ErrorStatus::InvalidArgument,
            "Parameter 'button' was not a positive integer");

        Ok(PointerUpAction {
            button: button
        })
    }
}

impl ToJson for PointerUpAction {
    fn to_json(&self) -> Json {
        let mut data = BTreeMap::new();
        data.insert("type".to_owned(),
                    "pointerUp".to_json());
        data.insert("button".to_owned(), self.button.to_json());
        Json::Object(data)
    }
}

#[derive(Debug, PartialEq)]
pub struct PointerDownAction {
    pub button: u64,
}

impl Parameters for PointerDownAction {
    fn from_json(body: &Json) -> WebDriverResult<PointerDownAction> {
        let button = try_opt!(
            try_opt!(body.find("button"),
                     ErrorStatus::InvalidArgument,
                     "Missing button parameter").as_u64(),
            ErrorStatus::InvalidArgument,
            "Parameter 'button' was not a positive integer");

        Ok(PointerDownAction {
            button: button
        })
    }
}

impl ToJson for PointerDownAction {
    fn to_json(&self) -> Json {
        let mut data = BTreeMap::new();
        data.insert("type".to_owned(),
                    "pointerDown".to_json());
        data.insert("button".to_owned(), self.button.to_json());
        Json::Object(data)
    }
}

#[derive(Debug, PartialEq)]
pub struct PointerMoveAction {
    pub duration: Nullable<u64>,
    pub origin: PointerOrigin,
    pub x: Nullable<i64>,
    pub y: Nullable<i64>
}

impl Parameters for PointerMoveAction {
    fn from_json(body: &Json) -> WebDriverResult<PointerMoveAction> {
        let duration = match body.find("duration") {
            Some(duration) => Some(try_opt!(duration.as_u64(),
                                            ErrorStatus::InvalidArgument,
                                            "Parameter 'duration' was not a positive integer")),
            None => None

        };

        let origin = match body.find("origin") {
            Some(o) => try!(PointerOrigin::from_json(o)),
            None => PointerOrigin::default()
        };

        let x = match body.find("x") {
            Some(x) => {
                Some(try_opt!(x.as_i64(),
                              ErrorStatus::InvalidArgument,
                              "Parameter 'x' was not an integer"))
            },
            None => None
        };

        let y = match body.find("y") {
            Some(y) => {
                Some(try_opt!(y.as_i64(),
                              ErrorStatus::InvalidArgument,
                              "Parameter 'y' was not an integer"))
            },
            None => None
        };

        Ok(PointerMoveAction {
            duration: duration.into(),
            origin: origin.into(),
            x: x.into(),
            y: y.into(),
        })
    }
}

impl ToJson for PointerMoveAction {
    fn to_json(&self) -> Json {
        let mut data = BTreeMap::new();
        data.insert("type".to_owned(), "pointerMove".to_json());
        if self.duration.is_value() {
            data.insert("duration".to_owned(),
                        self.duration.to_json());
        }

        data.insert("origin".to_owned(), self.origin.to_json());

        if self.x.is_value() {
            data.insert("x".to_owned(), self.x.to_json());
        }
        if self.y.is_value() {
            data.insert("y".to_owned(), self.y.to_json());
        }
        Json::Object(data)
    }
}