error.rs (17686B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 use base64::DecodeError; 6 use http::StatusCode; 7 use serde::de::{Deserialize, Deserializer}; 8 use serde::ser::{Serialize, Serializer}; 9 use serde_json::Value; 10 use std::borrow::Cow; 11 use std::collections::BTreeMap; 12 use std::error; 13 use std::io; 14 use thiserror::Error; 15 16 #[derive(Debug, PartialEq)] 17 pub enum ErrorStatus { 18 /// The [element]'s [ShadowRoot] is not attached to the active document, 19 /// or the reference is stale 20 /// [element]: ../common/struct.WebElement.html 21 /// [ShadowRoot]: ../common/struct.ShadowRoot.html 22 DetachedShadowRoot, 23 24 /// The [`ElementClick`] command could not be completed because the 25 /// [element] receiving the events is obscuring the element that was 26 /// requested clicked. 27 /// 28 /// [`ElementClick`]: 29 /// ../command/enum.WebDriverCommand.html#variant.ElementClick 30 /// [element]: ../common/struct.WebElement.html 31 ElementClickIntercepted, 32 33 /// A [command] could not be completed because the element is not pointer- 34 /// or keyboard interactable. 35 /// 36 /// [command]: ../command/index.html 37 ElementNotInteractable, 38 39 /// An attempt was made to select an [element] that cannot be selected. 40 /// 41 /// [element]: ../common/struct.WebElement.html 42 ElementNotSelectable, 43 44 /// Navigation caused the user agent to hit a certificate warning, which is 45 /// usually the result of an expired or invalid TLS certificate. 46 InsecureCertificate, 47 48 /// The arguments passed to a [command] are either invalid or malformed. 49 /// 50 /// [command]: ../command/index.html 51 InvalidArgument, 52 53 /// An illegal attempt was made to set a cookie under a different domain 54 /// than the current page. 55 InvalidCookieDomain, 56 57 /// The coordinates provided to an interactions operation are invalid. 58 InvalidCoordinates, 59 60 /// A [command] could not be completed because the element is an invalid 61 /// state, e.g. attempting to click an element that is no longer attached 62 /// to the document. 63 /// 64 /// [command]: ../command/index.html 65 InvalidElementState, 66 67 /// Argument was an invalid selector. 68 InvalidSelector, 69 70 /// Occurs if the given session ID is not in the list of active sessions, 71 /// meaning the session either does not exist or that it’s not active. 72 InvalidSessionId, 73 74 /// An error occurred while executing JavaScript supplied by the user. 75 JavascriptError, 76 77 /// The target for mouse interaction is not in the browser’s viewport and 78 /// cannot be brought into that viewport. 79 MoveTargetOutOfBounds, 80 81 /// An attempt was made to operate on a modal dialogue when one was not 82 /// open. 83 NoSuchAlert, 84 85 /// No cookie matching the given path name was found amongst the associated 86 /// cookies of the current browsing context’s active document. 87 NoSuchCookie, 88 89 /// An [element] could not be located on the page using the given search 90 /// parameters. 91 /// 92 /// [element]: ../common/struct.WebElement.html 93 NoSuchElement, 94 95 /// A [command] to switch to a frame could not be satisfied because the 96 /// frame could not be found. 97 /// 98 /// [command]: ../command/index.html 99 NoSuchFrame, 100 101 /// An [element]'s [ShadowRoot] was not found attached to the element. 102 /// 103 /// [element]: ../common/struct.WebElement.html 104 /// [ShadowRoot]: ../common/struct.ShadowRoot.html 105 NoSuchShadowRoot, 106 107 /// A [command] to switch to a window could not be satisfied because the 108 /// window could not be found. 109 /// 110 /// [command]: ../command/index.html 111 NoSuchWindow, 112 113 /// A script did not complete before its timeout expired. 114 ScriptTimeout, 115 116 /// A new session could not be created. 117 SessionNotCreated, 118 119 /// A [command] failed because the referenced [element] is no longer 120 /// attached to the DOM. 121 /// 122 /// [command]: ../command/index.html 123 /// [element]: ../common/struct.WebElement.html 124 StaleElementReference, 125 126 /// An operation did not complete before its timeout expired. 127 Timeout, 128 129 /// A screen capture was made impossible. 130 UnableToCaptureScreen, 131 132 /// Setting the cookie’s value could not be done. 133 UnableToSetCookie, 134 135 /// A modal dialogue was open, blocking this operation. 136 UnexpectedAlertOpen, 137 138 /// The requested command could not be executed because it does not exist. 139 UnknownCommand, 140 141 /// An unknown error occurred in the remote end whilst processing the 142 /// [command]. 143 /// 144 /// [command]: ../command/index.html 145 UnknownError, 146 147 /// The requested [command] matched a known endpoint, but did not match a 148 /// method for that endpoint. 149 /// 150 /// [command]: ../command/index.html 151 UnknownMethod, 152 153 /// Indicates that a [command] that should have executed properly is not 154 /// currently supported. 155 UnsupportedOperation, 156 } 157 158 impl 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 167 impl<'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 177 impl ErrorStatus { 178 /// Returns the string serialisation of the error type. 179 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 /// Returns the correct HTTP status code associated with the error type. 216 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 253 /// Deserialises error type from string. 254 impl 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 291 pub type WebDriverResult<T> = Result<T, WebDriverError>; 292 293 #[derive(Debug, PartialEq, Serialize, Error)] 294 #[serde(remote = "Self")] 295 #[error("{}", .error.error_code())] 296 pub 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 307 impl 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 322 impl 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 363 impl 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 369 impl From<io::Error> for WebDriverError { 370 fn from(err: io::Error) -> WebDriverError { 371 WebDriverError::new(ErrorStatus::UnknownError, err.to_string()) 372 } 373 } 374 375 impl From<DecodeError> for WebDriverError { 376 fn from(err: DecodeError) -> WebDriverError { 377 WebDriverError::new(ErrorStatus::UnknownError, err.to_string()) 378 } 379 } 380 381 impl 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)] 388 mod 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 }