1use std::{io::Error as IoError, sync::Arc, time::Duration};
18
19use as_variant::as_variant;
20use http::StatusCode;
21#[cfg(feature = "qrcode")]
22use matrix_sdk_base::crypto::ScanError;
23#[cfg(feature = "e2e-encryption")]
24use matrix_sdk_base::crypto::{
25 CryptoStoreError, DecryptorError, KeyExportError, MegolmError, OlmError,
26};
27use matrix_sdk_base::{
28 Error as SdkBaseError, QueueWedgeError, RoomState, StoreError,
29 event_cache::store::EventCacheStoreError, media::store::MediaStoreError,
30};
31use reqwest::Error as ReqwestError;
32use ruma::{
33 IdParseError,
34 api::{
35 client::{
36 error::{ErrorKind, RetryAfter},
37 uiaa::{UiaaInfo, UiaaResponse},
38 },
39 error::{FromHttpResponseError, IntoHttpError},
40 },
41 events::{room::power_levels::PowerLevelsError, tag::InvalidUserTagName},
42 push::{InsertPushRuleError, RemovePushRuleError},
43};
44#[cfg(target_os = "android")]
45use rustls::client::VerifierBuilderError;
46use serde_json::Error as JsonError;
47use thiserror::Error;
48use url::ParseError as UrlParseError;
49
50use crate::{
51 authentication::oauth::OAuthError, cross_process_lock::CrossProcessLockError,
52 event_cache::EventCacheError, media::MediaError, room::reply::ReplyError,
53 sliding_sync::Error as SlidingSyncError,
54};
55
56pub type Result<T, E = Error> = std::result::Result<T, E>;
58
59pub type HttpResult<T> = std::result::Result<T, HttpError>;
61
62#[derive(Error, Debug)]
65pub enum RumaApiError {
66 #[error(transparent)]
68 ClientApi(ruma::api::client::Error),
69
70 #[error("User-Interactive Authentication required.")]
77 Uiaa(UiaaInfo),
78
79 #[error(transparent)]
81 Other(ruma::api::error::MatrixError),
82}
83
84impl RumaApiError {
85 pub fn as_client_api_error(&self) -> Option<&ruma::api::client::Error> {
89 as_variant!(self, Self::ClientApi)
90 }
91}
92
93#[derive(Error, Debug)]
96pub enum HttpError {
97 #[error(transparent)]
99 Reqwest(#[from] ReqwestError),
100
101 #[error(transparent)]
104 Api(#[from] Box<FromHttpResponseError<RumaApiError>>),
105
106 #[error(transparent)]
109 IntoHttp(IntoHttpError),
110
111 #[error(transparent)]
113 RefreshToken(RefreshTokenError),
114
115 #[cfg(target_os = "android")]
117 #[error(transparent)]
118 VerifierBuilder(#[from] VerifierBuilderError),
119}
120
121#[rustfmt::skip] impl HttpError {
123 pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
129 match self {
130 Self::Api(error) => {
131 as_variant!(error.as_ref(), FromHttpResponseError::Server)
132 },
133 _ => None
134 }
135 }
136
137 pub fn as_client_api_error(&self) -> Option<&ruma::api::client::Error> {
140 self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
141 }
142}
143
144impl HttpError {
146 pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
149 self.as_client_api_error().and_then(ruma::api::client::Error::error_kind)
150 }
151
152 pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
164 self.as_ruma_api_error().and_then(as_variant!(RumaApiError::Uiaa))
165 }
166
167 pub(crate) fn retry_kind(&self) -> RetryKind {
170 match self {
171 HttpError::Reqwest(_) => RetryKind::NetworkFailure,
174
175 HttpError::Api(error) => match error.as_ref() {
176 FromHttpResponseError::Server(api_error) => RetryKind::from_api_error(api_error),
177 _ => RetryKind::Permanent,
178 },
179 _ => RetryKind::Permanent,
180 }
181 }
182}
183
184impl From<FromHttpResponseError<RumaApiError>> for HttpError {
185 fn from(value: FromHttpResponseError<RumaApiError>) -> Self {
186 Self::Api(Box::new(value))
187 }
188}
189
190pub(crate) enum RetryKind {
193 NetworkFailure,
195
196 Transient {
200 #[cfg_attr(target_family = "wasm", allow(dead_code))]
202 retry_after: Option<Duration>,
203 },
204
205 Permanent,
208}
209
210impl RetryKind {
211 fn from_api_error(api_error: &RumaApiError) -> Self {
218 match api_error {
219 RumaApiError::ClientApi(client_error) => match client_error.error_kind() {
220 Some(ErrorKind::LimitExceeded(limit_exceeded)) => {
221 RetryKind::from_retry_after(limit_exceeded.retry_after.as_ref())
222 }
223 Some(ErrorKind::Unrecognized) => RetryKind::Permanent,
224 _ => RetryKind::from_status_code(client_error.status_code),
225 },
226 RumaApiError::Other(e) => RetryKind::from_status_code(e.status_code),
227 RumaApiError::Uiaa(_) => RetryKind::Permanent,
228 }
229 }
230
231 fn from_retry_after(retry_after: Option<&RetryAfter>) -> Self {
237 let retry_after = retry_after
238 .and_then(|retry_after| match retry_after {
239 RetryAfter::Delay(d) => Some(d),
240 RetryAfter::DateTime(_) => None,
241 })
242 .copied();
243
244 Self::Transient { retry_after }
245 }
246
247 fn from_status_code(status_code: StatusCode) -> Self {
254 if status_code.as_u16() == 520 {
255 RetryKind::Permanent
258 } else if status_code == StatusCode::TOO_MANY_REQUESTS || status_code.is_server_error() {
259 RetryKind::Transient { retry_after: None }
263 } else {
264 RetryKind::Permanent
265 }
266 }
267}
268
269#[derive(Error, Debug)]
271#[non_exhaustive]
272pub enum Error {
273 #[error(transparent)]
275 Http(Box<HttpError>),
276
277 #[error("the queried endpoint requires authentication but was called before logging in")]
280 AuthenticationRequired,
281
282 #[error("Local cache doesn't contain all necessary data to perform the action.")]
284 InsufficientData,
285
286 #[cfg(feature = "e2e-encryption")]
289 #[error("The olm machine has already been initialized")]
290 BadCryptoStoreState,
291
292 #[cfg(feature = "e2e-encryption")]
294 #[error("The olm machine isn't yet available")]
295 NoOlmMachine,
296
297 #[error(transparent)]
299 SerdeJson(#[from] JsonError),
300
301 #[error(transparent)]
303 Io(#[from] IoError),
304
305 #[cfg(feature = "e2e-encryption")]
307 #[error(transparent)]
308 CryptoStoreError(Box<CryptoStoreError>),
309
310 #[error(transparent)]
312 CrossProcessLockError(Box<CrossProcessLockError>),
313
314 #[cfg(feature = "e2e-encryption")]
316 #[error(transparent)]
317 OlmError(Box<OlmError>),
318
319 #[cfg(feature = "e2e-encryption")]
321 #[error(transparent)]
322 MegolmError(Box<MegolmError>),
323
324 #[cfg(feature = "e2e-encryption")]
326 #[error(transparent)]
327 DecryptorError(#[from] DecryptorError),
328
329 #[error(transparent)]
331 StateStore(Box<StoreError>),
332
333 #[error(transparent)]
335 EventCacheStore(Box<EventCacheStoreError>),
336
337 #[error(transparent)]
339 MediaStore(Box<MediaStoreError>),
340
341 #[error(transparent)]
343 Identifier(#[from] IdParseError),
344
345 #[error(transparent)]
347 Url(#[from] UrlParseError),
348
349 #[cfg(feature = "qrcode")]
351 #[error(transparent)]
352 QrCodeScanError(Box<ScanError>),
353
354 #[error(transparent)]
356 UserTagName(#[from] InvalidUserTagName),
357
358 #[error(transparent)]
360 SlidingSync(Box<SlidingSyncError>),
361
362 #[error("wrong room state: {0}")]
366 WrongRoomState(Box<WrongRoomState>),
367
368 #[error("session callbacks have been set multiple times")]
370 MultipleSessionCallbacks,
371
372 #[error(transparent)]
374 OAuth(Box<OAuthError>),
375
376 #[error("a concurrent request failed; see logs for details")]
378 ConcurrentRequestFailed,
379
380 #[cfg(not(target_family = "wasm"))]
385 #[error("unknown error: {0}")]
386 UnknownError(Box<dyn std::error::Error + Send + Sync>),
387
388 #[cfg(target_family = "wasm")]
390 #[error("unknown error: {0}")]
391 UnknownError(Box<dyn std::error::Error>),
392
393 #[error(transparent)]
395 EventCache(Box<EventCacheError>),
396
397 #[error(transparent)]
399 SendQueueWedgeError(Box<QueueWedgeError>),
400
401 #[error("backups are not enabled")]
403 BackupNotEnabled,
404
405 #[error("can't ignore the logged-in user")]
407 CantIgnoreLoggedInUser,
408
409 #[error(transparent)]
411 Media(#[from] MediaError),
412
413 #[error(transparent)]
415 ReplyError(#[from] ReplyError),
416
417 #[error("power levels error: {0}")]
419 PowerLevels(#[from] PowerLevelsError),
420}
421
422#[rustfmt::skip] impl Error {
424 pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
430 as_variant!(self, Self::Http).and_then(|e| e.as_ruma_api_error())
431 }
432
433 pub fn as_client_api_error(&self) -> Option<&ruma::api::client::Error> {
436 self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
437 }
438
439 pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
442 self.as_client_api_error().and_then(ruma::api::client::Error::error_kind)
443 }
444
445 pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
457 self.as_ruma_api_error().and_then(as_variant!(RumaApiError::Uiaa))
458 }
459}
460
461impl From<HttpError> for Error {
462 fn from(error: HttpError) -> Self {
463 Error::Http(Box::new(error))
464 }
465}
466
467#[cfg(feature = "e2e-encryption")]
468impl From<CryptoStoreError> for Error {
469 fn from(error: CryptoStoreError) -> Self {
470 Error::CryptoStoreError(Box::new(error))
471 }
472}
473
474impl From<CrossProcessLockError> for Error {
475 fn from(error: CrossProcessLockError) -> Self {
476 Error::CrossProcessLockError(Box::new(error))
477 }
478}
479
480#[cfg(feature = "e2e-encryption")]
481impl From<OlmError> for Error {
482 fn from(error: OlmError) -> Self {
483 Error::OlmError(Box::new(error))
484 }
485}
486
487#[cfg(feature = "e2e-encryption")]
488impl From<MegolmError> for Error {
489 fn from(error: MegolmError) -> Self {
490 Error::MegolmError(Box::new(error))
491 }
492}
493
494impl From<StoreError> for Error {
495 fn from(error: StoreError) -> Self {
496 Error::StateStore(Box::new(error))
497 }
498}
499
500impl From<EventCacheStoreError> for Error {
501 fn from(error: EventCacheStoreError) -> Self {
502 Error::EventCacheStore(Box::new(error))
503 }
504}
505
506impl From<MediaStoreError> for Error {
507 fn from(error: MediaStoreError) -> Self {
508 Error::MediaStore(Box::new(error))
509 }
510}
511
512#[cfg(feature = "qrcode")]
513impl From<ScanError> for Error {
514 fn from(error: ScanError) -> Self {
515 Error::QrCodeScanError(Box::new(error))
516 }
517}
518
519impl From<SlidingSyncError> for Error {
520 fn from(error: SlidingSyncError) -> Self {
521 Error::SlidingSync(Box::new(error))
522 }
523}
524
525impl From<OAuthError> for Error {
526 fn from(error: OAuthError) -> Self {
527 Error::OAuth(Box::new(error))
528 }
529}
530
531impl From<EventCacheError> for Error {
532 fn from(error: EventCacheError) -> Self {
533 Error::EventCache(Box::new(error))
534 }
535}
536
537impl From<QueueWedgeError> for Error {
538 fn from(error: QueueWedgeError) -> Self {
539 Error::SendQueueWedgeError(Box::new(error))
540 }
541}
542
543#[cfg(feature = "e2e-encryption")]
545#[derive(Error, Debug)]
546#[allow(dead_code)]
548pub enum RoomKeyImportError {
549 #[error(transparent)]
551 SerdeJson(#[from] JsonError),
552
553 #[error("The crypto store hasn't been yet opened, can't import yet.")]
556 StoreClosed,
557
558 #[error(transparent)]
560 Io(#[from] IoError),
561
562 #[error(transparent)]
564 CryptoStore(#[from] CryptoStoreError),
565
566 #[error(transparent)]
568 Export(#[from] KeyExportError),
569}
570
571impl From<FromHttpResponseError<ruma::api::client::Error>> for HttpError {
572 fn from(err: FromHttpResponseError<ruma::api::client::Error>) -> Self {
573 Self::Api(Box::new(err.map(RumaApiError::ClientApi)))
574 }
575}
576
577impl From<FromHttpResponseError<UiaaResponse>> for HttpError {
578 fn from(err: FromHttpResponseError<UiaaResponse>) -> Self {
579 Self::Api(Box::new(err.map(|e| match e {
580 UiaaResponse::AuthResponse(i) => RumaApiError::Uiaa(i),
581 UiaaResponse::MatrixError(e) => RumaApiError::ClientApi(e),
582 })))
583 }
584}
585
586impl From<FromHttpResponseError<ruma::api::error::MatrixError>> for HttpError {
587 fn from(err: FromHttpResponseError<ruma::api::error::MatrixError>) -> Self {
588 Self::Api(Box::new(err.map(RumaApiError::Other)))
589 }
590}
591
592impl From<SdkBaseError> for Error {
593 fn from(e: SdkBaseError) -> Self {
594 match e {
595 SdkBaseError::StateStore(e) => Self::StateStore(Box::new(e)),
596 #[cfg(feature = "e2e-encryption")]
597 SdkBaseError::CryptoStore(e) => Self::CryptoStoreError(Box::new(e)),
598 #[cfg(feature = "e2e-encryption")]
599 SdkBaseError::BadCryptoStoreState => Self::BadCryptoStoreState,
600 #[cfg(feature = "e2e-encryption")]
601 SdkBaseError::OlmError(e) => Self::OlmError(Box::new(e)),
602 #[cfg(feature = "eyre")]
603 _ => Self::UnknownError(eyre::eyre!(e).into()),
604 #[cfg(all(not(feature = "eyre"), feature = "anyhow", not(target_family = "wasm")))]
605 _ => Self::UnknownError(anyhow::anyhow!(e).into()),
606 #[cfg(all(not(feature = "eyre"), feature = "anyhow", target_family = "wasm"))]
607 _ => Self::UnknownError(e.into()),
608 #[cfg(all(
609 not(feature = "eyre"),
610 not(feature = "anyhow"),
611 not(target_family = "wasm")
612 ))]
613 _ => {
614 let e: Box<dyn std::error::Error + Send + Sync> = format!("{e:?}").into();
615 Self::UnknownError(e)
616 }
617 #[cfg(all(not(feature = "eyre"), not(feature = "anyhow"), target_family = "wasm"))]
618 _ => {
619 let e: Box<dyn std::error::Error> = format!("{e:?}").into();
620 Self::UnknownError(e)
621 }
622 }
623 }
624}
625
626impl From<ReqwestError> for Error {
627 fn from(e: ReqwestError) -> Self {
628 Error::Http(Box::new(HttpError::Reqwest(e)))
629 }
630}
631
632#[derive(Debug, Error)]
634pub enum BeaconError {
635 #[error("Network error: {0}")]
637 Network(#[from] HttpError),
638
639 #[error("Existing beacon information not found.")]
641 NotFound,
642
643 #[error("Beacon event is redacted and cannot be processed.")]
645 Redacted,
646
647 #[error("Must join the room to access beacon information.")]
649 Stripped,
650
651 #[error("Deserialization error: {0}")]
653 Deserialization(#[from] serde_json::Error),
654
655 #[error("The beacon event has expired.")]
657 NotLive,
658
659 #[error("Other error: {0}")]
661 Other(Box<Error>),
662}
663
664impl From<Error> for BeaconError {
665 fn from(err: Error) -> Self {
666 BeaconError::Other(Box::new(err))
667 }
668}
669
670#[derive(Debug, Error, Clone)]
678pub enum RefreshTokenError {
679 #[error("missing refresh token")]
681 RefreshTokenRequired,
682
683 #[error(transparent)]
685 MatrixAuth(Arc<HttpError>),
686
687 #[error(transparent)]
689 OAuth(#[from] Arc<OAuthError>),
690}
691
692#[derive(Debug, Error, Clone, PartialEq)]
694pub enum NotificationSettingsError {
695 #[error("Invalid parameter `{0}`")]
697 InvalidParameter(String),
698 #[error("Unable to add push rule")]
700 UnableToAddPushRule,
701 #[error("Unable to remove push rule")]
703 UnableToRemovePushRule,
704 #[error("Unable to update push rule")]
706 UnableToUpdatePushRule,
707 #[error("Rule `{0}` not found")]
709 RuleNotFound(String),
710 #[error("Unable to save push rules")]
712 UnableToSavePushRules,
713}
714
715impl NotificationSettingsError {
716 pub fn is_rule_not_found(&self) -> bool {
718 matches!(self, Self::RuleNotFound(_))
719 }
720}
721
722impl From<InsertPushRuleError> for NotificationSettingsError {
723 fn from(_: InsertPushRuleError) -> Self {
724 Self::UnableToAddPushRule
725 }
726}
727
728impl From<RemovePushRuleError> for NotificationSettingsError {
729 fn from(_: RemovePushRuleError) -> Self {
730 Self::UnableToRemovePushRule
731 }
732}
733
734#[derive(Debug, Error)]
735#[error("expected: {expected}, got: {got:?}")]
736pub struct WrongRoomState {
737 expected: &'static str,
738 got: RoomState,
739}
740
741impl WrongRoomState {
742 pub(crate) fn new(expected: &'static str, got: RoomState) -> Self {
743 Self { expected, got }
744 }
745}