1 use regex::{Regex, Captures};
2 use rustc_serialize::json::Json;
3
4 use hyper::method::Method;
5 use hyper::method::Method::{Get, Post, Delete};
6
7 use command::{WebDriverCommand, WebDriverMessage, WebDriverExtensionCommand,
8 VoidWebDriverExtensionCommand};
9 use error::{WebDriverResult, WebDriverError, ErrorStatus};
10
standard_routes<U:WebDriverExtensionRoute>() -> Vec<(Method, &'static str, Route<U>)>11 fn standard_routes<U:WebDriverExtensionRoute>() -> Vec<(Method, &'static str, Route<U>)> {
12 return vec![(Post, "/session", Route::NewSession),
13 (Delete, "/session/{sessionId}", Route::DeleteSession),
14 (Post, "/session/{sessionId}/url", Route::Get),
15 (Get, "/session/{sessionId}/url", Route::GetCurrentUrl),
16 (Post, "/session/{sessionId}/back", Route::GoBack),
17 (Post, "/session/{sessionId}/forward", Route::GoForward),
18 (Post, "/session/{sessionId}/refresh", Route::Refresh),
19 (Get, "/session/{sessionId}/title", Route::GetTitle),
20 (Get, "/session/{sessionId}/source", Route::GetPageSource),
21 (Get, "/session/{sessionId}/window", Route::GetWindowHandle),
22 (Get, "/session/{sessionId}/window/handles", Route::GetWindowHandles),
23 (Delete, "/session/{sessionId}/window", Route::CloseWindow),
24 (Get, "/session/{sessionId}/window/size", Route::GetWindowSize),
25 (Post, "/session/{sessionId}/window/size", Route::SetWindowSize),
26 (Get, "/session/{sessionId}/window/position", Route::GetWindowPosition),
27 (Post, "/session/{sessionId}/window/position", Route::SetWindowPosition),
28 (Get, "/session/{sessionId}/window/rect", Route::GetWindowRect),
29 (Post, "/session/{sessionId}/window/rect", Route::SetWindowRect),
30 (Post, "/session/{sessionId}/window/minimize", Route::MinimizeWindow),
31 (Post, "/session/{sessionId}/window/maximize", Route::MaximizeWindow),
32 (Post, "/session/{sessionId}/window/fullscreen", Route::FullscreenWindow),
33 (Post, "/session/{sessionId}/window", Route::SwitchToWindow),
34 (Post, "/session/{sessionId}/frame", Route::SwitchToFrame),
35 (Post, "/session/{sessionId}/frame/parent", Route::SwitchToParentFrame),
36 (Post, "/session/{sessionId}/element", Route::FindElement),
37 (Post, "/session/{sessionId}/elements", Route::FindElements),
38 (Post, "/session/{sessionId}/element/{elementId}/element", Route::FindElementElement),
39 (Post, "/session/{sessionId}/element/{elementId}/elements", Route::FindElementElements),
40 (Get, "/session/{sessionId}/element/active", Route::GetActiveElement),
41 (Get, "/session/{sessionId}/element/{elementId}/displayed", Route::IsDisplayed),
42 (Get, "/session/{sessionId}/element/{elementId}/selected", Route::IsSelected),
43 (Get, "/session/{sessionId}/element/{elementId}/attribute/{name}", Route::GetElementAttribute),
44 (Get, "/session/{sessionId}/element/{elementId}/property/{name}", Route::GetElementProperty),
45 (Get, "/session/{sessionId}/element/{elementId}/css/{propertyName}", Route::GetCSSValue),
46 (Get, "/session/{sessionId}/element/{elementId}/text", Route::GetElementText),
47 (Get, "/session/{sessionId}/element/{elementId}/name", Route::GetElementTagName),
48 (Get, "/session/{sessionId}/element/{elementId}/rect", Route::GetElementRect),
49 (Get, "/session/{sessionId}/element/{elementId}/enabled", Route::IsEnabled),
50 (Post, "/session/{sessionId}/execute/sync", Route::ExecuteScript),
51 (Post, "/session/{sessionId}/execute/async", Route::ExecuteAsyncScript),
52 (Get, "/session/{sessionId}/cookie", Route::GetCookies),
53 (Get, "/session/{sessionId}/cookie/{name}", Route::GetNamedCookie),
54 (Post, "/session/{sessionId}/cookie", Route::AddCookie),
55 (Delete, "/session/{sessionId}/cookie", Route::DeleteCookies),
56 (Delete, "/session/{sessionId}/cookie/{name}", Route::DeleteCookie),
57 (Get, "/session/{sessionId}/timeouts", Route::GetTimeouts),
58 (Post, "/session/{sessionId}/timeouts", Route::SetTimeouts),
59 (Post, "/session/{sessionId}/element/{elementId}/click", Route::ElementClick),
60 (Post, "/session/{sessionId}/element/{elementId}/tap", Route::ElementTap),
61 (Post, "/session/{sessionId}/element/{elementId}/clear", Route::ElementClear),
62 (Post, "/session/{sessionId}/element/{elementId}/value", Route::ElementSendKeys),
63 (Post, "/session/{sessionId}/alert/dismiss", Route::DismissAlert),
64 (Post, "/session/{sessionId}/alert/accept", Route::AcceptAlert),
65 (Get, "/session/{sessionId}/alert/text", Route::GetAlertText),
66 (Post, "/session/{sessionId}/alert/text", Route::SendAlertText),
67 (Get, "/session/{sessionId}/screenshot", Route::TakeScreenshot),
68 (Get, "/session/{sessionId}/element/{elementId}/screenshot", Route::TakeElementScreenshot),
69 (Post, "/session/{sessionId}/actions", Route::PerformActions),
70 (Delete, "/session/{sessionId}/actions", Route::ReleaseActions),
71 (Get, "/status", Route::Status),]
72 }
73
74 #[derive(Clone, Copy, Debug)]
75 pub enum Route<U:WebDriverExtensionRoute> {
76 NewSession,
77 DeleteSession,
78 Get,
79 GetCurrentUrl,
80 GoBack,
81 GoForward,
82 Refresh,
83 GetTitle,
84 GetPageSource,
85 GetWindowHandle,
86 GetWindowHandles,
87 CloseWindow,
88 GetWindowSize, // deprecated
89 SetWindowSize, // deprecated
90 GetWindowPosition, // deprecated
91 SetWindowPosition, // deprecated
92 GetWindowRect,
93 SetWindowRect,
94 MinimizeWindow,
95 MaximizeWindow,
96 FullscreenWindow,
97 SwitchToWindow,
98 SwitchToFrame,
99 SwitchToParentFrame,
100 FindElement,
101 FindElements,
102 FindElementElement,
103 FindElementElements,
104 GetActiveElement,
105 IsDisplayed,
106 IsSelected,
107 GetElementAttribute,
108 GetElementProperty,
109 GetCSSValue,
110 GetElementText,
111 GetElementTagName,
112 GetElementRect,
113 IsEnabled,
114 ExecuteScript,
115 ExecuteAsyncScript,
116 GetCookies,
117 GetNamedCookie,
118 AddCookie,
119 DeleteCookies,
120 DeleteCookie,
121 GetTimeouts,
122 SetTimeouts,
123 ElementClick,
124 ElementTap,
125 ElementClear,
126 ElementSendKeys,
127 PerformActions,
128 ReleaseActions,
129 DismissAlert,
130 AcceptAlert,
131 GetAlertText,
132 SendAlertText,
133 TakeScreenshot,
134 TakeElementScreenshot,
135 Status,
136 Extension(U),
137 }
138
139 pub trait WebDriverExtensionRoute : Clone + Send + PartialEq {
140 type Command: WebDriverExtensionCommand + 'static;
141
command(&self, &Captures, &Json) -> WebDriverResult<WebDriverCommand<Self::Command>>142 fn command(&self, &Captures, &Json) -> WebDriverResult<WebDriverCommand<Self::Command>>;
143 }
144
145 #[derive(Clone, Debug, PartialEq)]
146 pub struct VoidWebDriverExtensionRoute;
147
148 impl WebDriverExtensionRoute for VoidWebDriverExtensionRoute {
149 type Command = VoidWebDriverExtensionCommand;
150
command(&self, _:&Captures, _:&Json) -> WebDriverResult<WebDriverCommand<VoidWebDriverExtensionCommand>>151 fn command(&self, _:&Captures, _:&Json) -> WebDriverResult<WebDriverCommand<VoidWebDriverExtensionCommand>> {
152 panic!("No extensions implemented");
153 }
154 }
155
156 #[derive(Clone, Debug)]
157 struct RequestMatcher<U: WebDriverExtensionRoute> {
158 method: Method,
159 path_regexp: Regex,
160 match_type: Route<U>
161 }
162
163 impl <U: WebDriverExtensionRoute> RequestMatcher<U> {
new(method: Method, path: &str, match_type: Route<U>) -> RequestMatcher<U>164 pub fn new(method: Method, path: &str, match_type: Route<U>) -> RequestMatcher<U> {
165 let path_regexp = RequestMatcher::<U>::compile_path(path);
166 RequestMatcher {
167 method: method,
168 path_regexp: path_regexp,
169 match_type: match_type
170 }
171 }
172
get_match<'t>(&'t self, method: Method, path: &'t str) -> (bool, Option<Captures>)173 pub fn get_match<'t>(&'t self, method: Method, path: &'t str) -> (bool, Option<Captures>) {
174 let captures = self.path_regexp.captures(path);
175 (method == self.method, captures)
176 }
177
compile_path(path: &str) -> Regex178 fn compile_path(path: &str) -> Regex {
179 let mut rv = String::new();
180 rv.push_str("^");
181 let components = path.split('/');
182 for component in components {
183 if component.starts_with("{") {
184 if !component.ends_with("}") {
185 panic!("Invalid url pattern")
186 }
187 rv.push_str(&format!("(?P<{}>[^/]+)/", &component[1..component.len()-1])[..]);
188 } else {
189 rv.push_str(&format!("{}/", component)[..]);
190 }
191 }
192 //Remove the trailing /
193 rv.pop();
194 rv.push_str("$");
195 //This will fail at runtime if the regexp is invalid
196 Regex::new(&rv[..]).unwrap()
197 }
198 }
199
200 #[derive(Debug)]
201 pub struct WebDriverHttpApi<U: WebDriverExtensionRoute> {
202 routes: Vec<(Method, RequestMatcher<U>)>,
203 }
204
205 impl <U: WebDriverExtensionRoute> WebDriverHttpApi<U> {
new(extension_routes: &[(Method, &str, U)]) -> WebDriverHttpApi<U>206 pub fn new(extension_routes: &[(Method, &str, U)]) -> WebDriverHttpApi<U> {
207 let mut rv = WebDriverHttpApi::<U> {
208 routes: vec![],
209 };
210 debug!("Creating routes");
211 for &(ref method, ref url, ref match_type) in standard_routes::<U>().iter() {
212 rv.add(method.clone(), *url, (*match_type).clone());
213 };
214 for &(ref method, ref url, ref extension_route) in extension_routes.iter() {
215 rv.add(method.clone(), *url, Route::Extension(extension_route.clone()));
216 };
217 rv
218 }
219
add(&mut self, method: Method, path: &str, match_type: Route<U>)220 fn add(&mut self, method: Method, path: &str, match_type: Route<U>) {
221 let http_matcher = RequestMatcher::new(method.clone(), path, match_type);
222 self.routes.push((method, http_matcher));
223 }
224
decode_request(&self, method: Method, path: &str, body: &str) -> WebDriverResult<WebDriverMessage<U>>225 pub fn decode_request(&self, method: Method, path: &str, body: &str) -> WebDriverResult<WebDriverMessage<U>> {
226 let mut error = ErrorStatus::UnknownPath;
227 for &(ref match_method, ref matcher) in self.routes.iter() {
228 if method == *match_method {
229 let (method_match, captures) = matcher.get_match(method.clone(), path);
230 if captures.is_some() {
231 if method_match {
232 return WebDriverMessage::from_http(matcher.match_type.clone(),
233 &captures.unwrap(),
234 body,
235 method == Post)
236 } else {
237 error = ErrorStatus::UnknownMethod;
238 }
239 }
240 }
241 }
242 Err(WebDriverError::new(error,
243 format!("{} {} did not match a known command", method, path)))
244 }
245 }
246