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}