1use base64::DecodeError;
6use http::StatusCode;
7use serde::de::{Deserialize, Deserializer};
8use serde::ser::{Serialize, Serializer};
9use serde_json::Value;
10use std::borrow::Cow;
11use std::collections::BTreeMap;
12use std::error;
13use std::io;
14use thiserror::Error;
15
16#[derive(Debug, PartialEq)]
17pub enum ErrorStatus {
18 DetachedShadowRoot,
23
24 ElementClickIntercepted,
32
33 ElementNotInteractable,
38
39 ElementNotSelectable,
43
44 InsecureCertificate,
47
48 InvalidArgument,
52
53 InvalidCookieDomain,
56
57 InvalidCoordinates,
59
60 InvalidElementState,
66
67 InvalidSelector,
69
70 InvalidSessionId,
73
74 JavascriptError,
76
77 MoveTargetOutOfBounds,
80
81 NoSuchAlert,
84
85 NoSuchCookie,
88
89 NoSuchElement,
94
95 NoSuchFrame,
100
101 NoSuchShadowRoot,
106
107 NoSuchWindow,
112
113 ScriptTimeout,
115
116 SessionNotCreated,
118
119 StaleElementReference,
125
126 Timeout,
128
129 UnableToCaptureScreen,
131
132 UnableToSetCookie,
134
135 UnexpectedAlertOpen,
137
138 UnknownCommand,
140
141 UnknownError,
146
147 UnknownMethod,
152
153 UnsupportedOperation,
156}
157
158impl Serialize for ErrorStatus {
159 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
160 where
161 S: Serializer,
162 {
163 self.error_code().serialize(serializer)
164 }
165}
166
167impl<'de> Deserialize<'de> for ErrorStatus {
168 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
169 where
170 D: Deserializer<'de>,
171 {
172 let error_string = String::deserialize(deserializer)?;
173 Ok(ErrorStatus::from(error_string))
174 }
175}
176
177impl ErrorStatus {
178 pub fn error_code(&self) -> &'static str {
180 use self::ErrorStatus::*;
181 match *self {
182 DetachedShadowRoot => "detached shadow root",
183 ElementClickIntercepted => "element click intercepted",
184 ElementNotInteractable => "element not interactable",
185 ElementNotSelectable => "element not selectable",
186 InsecureCertificate => "insecure certificate",
187 InvalidArgument => "invalid argument",
188 InvalidCookieDomain => "invalid cookie domain",
189 InvalidCoordinates => "invalid coordinates",
190 InvalidElementState => "invalid element state",
191 InvalidSelector => "invalid selector",
192 InvalidSessionId => "invalid session id",
193 JavascriptError => "javascript error",
194 MoveTargetOutOfBounds => "move target out of bounds",
195 NoSuchAlert => "no such alert",
196 NoSuchCookie => "no such cookie",
197 NoSuchElement => "no such element",
198 NoSuchFrame => "no such frame",
199 NoSuchShadowRoot => "no such shadow root",
200 NoSuchWindow => "no such window",
201 ScriptTimeout => "script timeout",
202 SessionNotCreated => "session not created",
203 StaleElementReference => "stale element reference",
204 Timeout => "timeout",
205 UnableToCaptureScreen => "unable to capture screen",
206 UnableToSetCookie => "unable to set cookie",
207 UnexpectedAlertOpen => "unexpected alert open",
208 UnknownError => "unknown error",
209 UnknownMethod => "unknown method",
210 UnknownCommand => "unknown command",
211 UnsupportedOperation => "unsupported operation",
212 }
213 }
214
215 pub fn http_status(&self) -> StatusCode {
217 use self::ErrorStatus::*;
218 match *self {
219 DetachedShadowRoot => StatusCode::NOT_FOUND,
220 ElementClickIntercepted => StatusCode::BAD_REQUEST,
221 ElementNotInteractable => StatusCode::BAD_REQUEST,
222 ElementNotSelectable => StatusCode::BAD_REQUEST,
223 InsecureCertificate => StatusCode::BAD_REQUEST,
224 InvalidArgument => StatusCode::BAD_REQUEST,
225 InvalidCookieDomain => StatusCode::BAD_REQUEST,
226 InvalidCoordinates => StatusCode::BAD_REQUEST,
227 InvalidElementState => StatusCode::BAD_REQUEST,
228 InvalidSelector => StatusCode::BAD_REQUEST,
229 InvalidSessionId => StatusCode::NOT_FOUND,
230 JavascriptError => StatusCode::INTERNAL_SERVER_ERROR,
231 MoveTargetOutOfBounds => StatusCode::INTERNAL_SERVER_ERROR,
232 NoSuchAlert => StatusCode::NOT_FOUND,
233 NoSuchCookie => StatusCode::NOT_FOUND,
234 NoSuchElement => StatusCode::NOT_FOUND,
235 NoSuchFrame => StatusCode::NOT_FOUND,
236 NoSuchShadowRoot => StatusCode::NOT_FOUND,
237 NoSuchWindow => StatusCode::NOT_FOUND,
238 ScriptTimeout => StatusCode::INTERNAL_SERVER_ERROR,
239 SessionNotCreated => StatusCode::INTERNAL_SERVER_ERROR,
240 StaleElementReference => StatusCode::NOT_FOUND,
241 Timeout => StatusCode::INTERNAL_SERVER_ERROR,
242 UnableToCaptureScreen => StatusCode::BAD_REQUEST,
243 UnableToSetCookie => StatusCode::INTERNAL_SERVER_ERROR,
244 UnexpectedAlertOpen => StatusCode::INTERNAL_SERVER_ERROR,
245 UnknownCommand => StatusCode::NOT_FOUND,
246 UnknownError => StatusCode::INTERNAL_SERVER_ERROR,
247 UnknownMethod => StatusCode::METHOD_NOT_ALLOWED,
248 UnsupportedOperation => StatusCode::INTERNAL_SERVER_ERROR,
249 }
250 }
251}
252
253impl From<String> for ErrorStatus {
255 fn from(s: String) -> ErrorStatus {
256 use self::ErrorStatus::*;
257 match &*s {
258 "detached shadow root" => DetachedShadowRoot,
259 "element click intercepted" => ElementClickIntercepted,
260 "element not interactable" | "element not visible" => ElementNotInteractable,
261 "element not selectable" => ElementNotSelectable,
262 "insecure certificate" => InsecureCertificate,
263 "invalid argument" => InvalidArgument,
264 "invalid cookie domain" => InvalidCookieDomain,
265 "invalid coordinates" | "invalid element coordinates" => InvalidCoordinates,
266 "invalid element state" => InvalidElementState,
267 "invalid selector" => InvalidSelector,
268 "invalid session id" => InvalidSessionId,
269 "javascript error" => JavascriptError,
270 "move target out of bounds" => MoveTargetOutOfBounds,
271 "no such alert" => NoSuchAlert,
272 "no such element" => NoSuchElement,
273 "no such frame" => NoSuchFrame,
274 "no such shadow root" => NoSuchShadowRoot,
275 "no such window" => NoSuchWindow,
276 "script timeout" => ScriptTimeout,
277 "session not created" => SessionNotCreated,
278 "stale element reference" => StaleElementReference,
279 "timeout" => Timeout,
280 "unable to capture screen" => UnableToCaptureScreen,
281 "unable to set cookie" => UnableToSetCookie,
282 "unexpected alert open" => UnexpectedAlertOpen,
283 "unknown command" => UnknownCommand,
284 "unknown error" => UnknownError,
285 "unsupported operation" => UnsupportedOperation,
286 _ => UnknownError,
287 }
288 }
289}
290
291pub type WebDriverResult<T> = Result<T, WebDriverError>;
292
293#[derive(Debug, PartialEq, Serialize, Error)]
294#[serde(remote = "Self")]
295#[error("{}", .error.error_code())]
296pub struct WebDriverError {
297 pub error: ErrorStatus,
298 pub message: Cow<'static, str>,
299 #[serde(rename = "stacktrace")]
300 pub stack: Cow<'static, str>,
301 #[serde(skip_serializing_if = "Option::is_none")]
302 pub data: Option<BTreeMap<Cow<'static, str>, Value>>,
303 #[serde(skip)]
304 pub delete_session: bool,
305}
306
307impl Serialize for WebDriverError {
308 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
309 where
310 S: Serializer,
311 {
312 #[derive(Serialize)]
313 struct Wrapper<'a> {
314 #[serde(with = "WebDriverError")]
315 value: &'a WebDriverError,
316 }
317
318 Wrapper { value: self }.serialize(serializer)
319 }
320}
321
322impl WebDriverError {
323 pub fn new<S>(error: ErrorStatus, message: S) -> WebDriverError
324 where
325 S: Into<Cow<'static, str>>,
326 {
327 WebDriverError {
328 error,
329 message: message.into(),
330 data: None,
331 stack: "".into(),
332 delete_session: false,
333 }
334 }
335
336 pub fn new_with_data<S>(
337 error: ErrorStatus,
338 message: S,
339 data: Option<BTreeMap<Cow<'static, str>, Value>>,
340 stacktrace: Option<S>,
341 ) -> WebDriverError
342 where
343 S: Into<Cow<'static, str>>,
344 {
345 WebDriverError {
346 error,
347 message: message.into(),
348 data,
349 stack: stacktrace.map_or_else(|| "".into(), Into::into),
350 delete_session: false,
351 }
352 }
353
354 pub fn error_code(&self) -> &'static str {
355 self.error.error_code()
356 }
357
358 pub fn http_status(&self) -> StatusCode {
359 self.error.http_status()
360 }
361}
362
363impl From<serde_json::Error> for WebDriverError {
364 fn from(err: serde_json::Error) -> WebDriverError {
365 WebDriverError::new(ErrorStatus::InvalidArgument, err.to_string())
366 }
367}
368
369impl From<io::Error> for WebDriverError {
370 fn from(err: io::Error) -> WebDriverError {
371 WebDriverError::new(ErrorStatus::UnknownError, err.to_string())
372 }
373}
374
375impl From<DecodeError> for WebDriverError {
376 fn from(err: DecodeError) -> WebDriverError {
377 WebDriverError::new(ErrorStatus::UnknownError, err.to_string())
378 }
379}
380
381impl From<Box<dyn error::Error>> for WebDriverError {
382 fn from(err: Box<dyn error::Error>) -> WebDriverError {
383 WebDriverError::new(ErrorStatus::UnknownError, err.to_string())
384 }
385}
386
387#[cfg(test)]
388mod tests {
389 use serde_json::json;
390
391 use super::*;
392 use crate::test::assert_ser;
393
394 #[test]
395 fn test_error_status_serialization() {
396 assert_ser(&ErrorStatus::UnknownError, json!("unknown error"));
397 }
398
399 #[test]
400 fn test_webdriver_error_json() {
401 let data: Option<BTreeMap<Cow<'static, str>, Value>> = Some(
402 [
403 (Cow::Borrowed("foo"), Value::from(42)),
404 (Cow::Borrowed("bar"), Value::from(vec![true])),
405 ]
406 .into_iter()
407 .collect(),
408 );
409
410 let json = json!({"value": {
411 "error": "unknown error",
412 "message": "serialized error",
413 "stacktrace": "foo\nbar",
414 "data": {
415 "foo": 42,
416 "bar": [
417 true
418 ]
419 }
420 }});
421
422 let error = WebDriverError {
423 error: ErrorStatus::UnknownError,
424 message: "serialized error".into(),
425 stack: "foo\nbar".into(),
426 data,
427 delete_session: true,
428 };
429
430 assert_ser(&error, json);
431 }
432
433 #[test]
434 fn test_webdriver_error_new() {
435 let json = json!({"value": {
436 "error": "unknown error",
437 "message": "error with default stack",
438 "stacktrace": "",
439 }});
440
441 let error = WebDriverError::new::<Cow<'static, str>>(
442 ErrorStatus::UnknownError,
443 "error with default stack".into(),
444 );
445 assert_ser(&error, json);
446 }
447
448 #[test]
449 fn test_webdriver_error_new_with_data_serialization() {
450 let mut data_map = BTreeMap::new();
451 data_map.insert("foo".into(), json!(42));
452 data_map.insert("bar".into(), json!(vec!(true)));
453
454 let error = WebDriverError::new_with_data(
455 ErrorStatus::UnknownError,
456 "serialization test",
457 Some(data_map.clone()),
458 Some("foo\nbar"),
459 );
460
461 let serialized = serde_json::to_string(&error).unwrap();
462 let expected_json = json!({"value": {
463 "error": "unknown error",
464 "message": "serialization test",
465 "stacktrace": "foo\nbar",
466 "data": {
467 "foo": 42,
468 "bar": [true]
469 }
470 }});
471
472 assert_eq!(
473 serde_json::from_str::<Value>(&serialized).unwrap(),
474 expected_json
475 );
476 }
477
478 #[test]
479 fn test_webdriver_error_new_with_data_no_data_no_stacktrace() {
480 let error = WebDriverError::new_with_data(
481 ErrorStatus::UnknownError,
482 "error with no data and stack",
483 None,
484 None,
485 );
486
487 assert_eq!(error.error, ErrorStatus::UnknownError);
488 assert_eq!(error.message, "error with no data and stack");
489 assert_eq!(error.stack, "");
490 assert_eq!(error.data, None);
491 }
492
493 #[test]
494 fn test_webdriver_error_new_with_data_no_stacktrace() {
495 let mut data_map = BTreeMap::new();
496 data_map.insert("foo".into(), json!(42));
497
498 let error = WebDriverError::new_with_data(
499 ErrorStatus::UnknownError,
500 "error with no stack",
501 Some(data_map.clone()),
502 None,
503 );
504
505 assert_eq!(error.error, ErrorStatus::UnknownError);
506 assert_eq!(error.message, "error with no stack");
507 assert_eq!(error.stack, "");
508 assert_eq!(error.data, Some(data_map));
509 }
510
511 #[test]
512 fn test_webdriver_error_new_with_data_no_data() {
513 let error = WebDriverError::new_with_data(
514 ErrorStatus::UnknownError,
515 "error with no data",
516 None,
517 Some("foo\nbar"),
518 );
519
520 assert_eq!(error.error, ErrorStatus::UnknownError);
521 assert_eq!(error.message, "error with no data");
522 assert_eq!(error.stack, "foo\nbar");
523 assert_eq!(error.data, None);
524 }
525}