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::{ErrorBody, 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};
44use serde_json::Error as JsonError;
45use thiserror::Error;
46use url::ParseError as UrlParseError;
47
48use crate::{
49 authentication::oauth::OAuthError, cross_process_lock::CrossProcessLockError,
50 event_cache::EventCacheError, media::MediaError, room::reply::ReplyError,
51 sliding_sync::Error as SlidingSyncError,
52};
53
54pub type Result<T, E = Error> = std::result::Result<T, E>;
56
57pub type HttpResult<T> = std::result::Result<T, HttpError>;
59
60#[derive(Error, Debug)]
63pub enum RumaApiError {
64 #[error(transparent)]
66 ClientApi(ruma::api::client::Error),
67
68 #[error("User-Interactive Authentication required.")]
75 Uiaa(UiaaInfo),
76
77 #[error(transparent)]
79 Other(ruma::api::error::MatrixError),
80}
81
82impl RumaApiError {
83 pub fn as_client_api_error(&self) -> Option<&ruma::api::client::Error> {
87 as_variant!(self, Self::ClientApi)
88 }
89}
90
91#[derive(Error, Debug)]
94pub enum HttpError {
95 #[error(transparent)]
97 Reqwest(#[from] ReqwestError),
98
99 #[error(transparent)]
102 Api(#[from] Box<FromHttpResponseError<RumaApiError>>),
103
104 #[error(transparent)]
107 IntoHttp(IntoHttpError),
108
109 #[error(transparent)]
111 RefreshToken(RefreshTokenError),
112}
113
114#[rustfmt::skip] impl HttpError {
116 pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
122 match self {
123 Self::Api(error) => {
124 as_variant!(error.as_ref(), FromHttpResponseError::Server)
125 },
126 _ => None
127 }
128 }
129
130 pub fn as_client_api_error(&self) -> Option<&ruma::api::client::Error> {
133 self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
134 }
135}
136
137impl HttpError {
139 pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
142 self.as_client_api_error()
143 .and_then(|e| as_variant!(&e.body, ErrorBody::Standard { kind, .. } => kind))
144 }
145
146 pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
158 self.as_ruma_api_error().and_then(as_variant!(RumaApiError::Uiaa))
159 }
160
161 pub(crate) fn retry_kind(&self) -> RetryKind {
164 match self {
165 HttpError::Reqwest(_) => RetryKind::NetworkFailure,
168
169 HttpError::Api(error) => match error.as_ref() {
170 FromHttpResponseError::Server(api_error) => RetryKind::from_api_error(api_error),
171 _ => RetryKind::Permanent,
172 },
173 _ => RetryKind::Permanent,
174 }
175 }
176}
177
178impl From<FromHttpResponseError<RumaApiError>> for HttpError {
179 fn from(value: FromHttpResponseError<RumaApiError>) -> Self {
180 Self::Api(Box::new(value))
181 }
182}
183
184pub(crate) enum RetryKind {
187 NetworkFailure,
189
190 Transient {
194 #[cfg_attr(target_family = "wasm", allow(dead_code))]
196 retry_after: Option<Duration>,
197 },
198
199 Permanent,
202}
203
204impl RetryKind {
205 fn from_api_error(api_error: &RumaApiError) -> Self {
212 use ruma::api::client::Error;
213
214 match api_error {
215 RumaApiError::ClientApi(client_error) => {
216 let Error { status_code, body, .. } = client_error;
217
218 match body {
219 ErrorBody::Standard { kind, .. } => match kind {
220 ErrorKind::LimitExceeded { retry_after } => {
221 RetryKind::from_retry_after(retry_after.as_ref())
222 }
223 ErrorKind::Unrecognized => RetryKind::Permanent,
224 _ => RetryKind::from_status_code(*status_code),
225 },
226 _ => RetryKind::from_status_code(*status_code),
227 }
228 }
229 RumaApiError::Other(e) => RetryKind::from_status_code(e.status_code),
230 RumaApiError::Uiaa(_) => RetryKind::Permanent,
231 }
232 }
233
234 fn from_retry_after(retry_after: Option<&RetryAfter>) -> Self {
240 let retry_after = retry_after
241 .and_then(|retry_after| match retry_after {
242 RetryAfter::Delay(d) => Some(d),
243 RetryAfter::DateTime(_) => None,
244 })
245 .copied();
246
247 Self::Transient { retry_after }
248 }
249
250 fn from_status_code(status_code: StatusCode) -> Self {
257 if status_code.as_u16() == 520 {
258 RetryKind::Permanent
261 } else if status_code == StatusCode::TOO_MANY_REQUESTS || status_code.is_server_error() {
262 RetryKind::Transient { retry_after: None }
266 } else {
267 RetryKind::Permanent
268 }
269 }
270}
271
272#[derive(Error, Debug)]
274#[non_exhaustive]
275pub enum Error {
276 #[error(transparent)]
278 Http(Box<HttpError>),
279
280 #[error("the queried endpoint requires authentication but was called before logging in")]
283 AuthenticationRequired,
284
285 #[error("Local cache doesn't contain all necessary data to perform the action.")]
287 InsufficientData,
288
289 #[cfg(feature = "e2e-encryption")]
292 #[error("The olm machine has already been initialized")]
293 BadCryptoStoreState,
294
295 #[cfg(feature = "e2e-encryption")]
297 #[error("The olm machine isn't yet available")]
298 NoOlmMachine,
299
300 #[error(transparent)]
302 SerdeJson(#[from] JsonError),
303
304 #[error(transparent)]
306 Io(#[from] IoError),
307
308 #[cfg(feature = "e2e-encryption")]
310 #[error(transparent)]
311 CryptoStoreError(Box<CryptoStoreError>),
312
313 #[error(transparent)]
315 CrossProcessLockError(Box<CrossProcessLockError>),
316
317 #[cfg(feature = "e2e-encryption")]
319 #[error(transparent)]
320 OlmError(Box<OlmError>),
321
322 #[cfg(feature = "e2e-encryption")]
324 #[error(transparent)]
325 MegolmError(Box<MegolmError>),
326
327 #[cfg(feature = "e2e-encryption")]
329 #[error(transparent)]
330 DecryptorError(#[from] DecryptorError),
331
332 #[error(transparent)]
334 StateStore(Box<StoreError>),
335
336 #[error(transparent)]
338 EventCacheStore(Box<EventCacheStoreError>),
339
340 #[error(transparent)]
342 MediaStore(Box<MediaStoreError>),
343
344 #[error(transparent)]
346 Identifier(#[from] IdParseError),
347
348 #[error(transparent)]
350 Url(#[from] UrlParseError),
351
352 #[cfg(feature = "qrcode")]
354 #[error(transparent)]
355 QrCodeScanError(Box<ScanError>),
356
357 #[error(transparent)]
359 UserTagName(#[from] InvalidUserTagName),
360
361 #[error(transparent)]
363 SlidingSync(Box<SlidingSyncError>),
364
365 #[error("wrong room state: {0}")]
369 WrongRoomState(Box<WrongRoomState>),
370
371 #[error("session callbacks have been set multiple times")]
373 MultipleSessionCallbacks,
374
375 #[error(transparent)]
377 OAuth(Box<OAuthError>),
378
379 #[error("a concurrent request failed; see logs for details")]
381 ConcurrentRequestFailed,
382
383 #[cfg(not(target_family = "wasm"))]
388 #[error("unknown error: {0}")]
389 UnknownError(Box<dyn std::error::Error + Send + Sync>),
390
391 #[cfg(target_family = "wasm")]
393 #[error("unknown error: {0}")]
394 UnknownError(Box<dyn std::error::Error>),
395
396 #[error(transparent)]
398 EventCache(Box<EventCacheError>),
399
400 #[error(transparent)]
402 SendQueueWedgeError(Box<QueueWedgeError>),
403
404 #[error("backups are not enabled")]
406 BackupNotEnabled,
407
408 #[error("can't ignore the logged-in user")]
410 CantIgnoreLoggedInUser,
411
412 #[error(transparent)]
414 Media(#[from] MediaError),
415
416 #[error(transparent)]
418 ReplyError(#[from] ReplyError),
419
420 #[error("power levels error: {0}")]
422 PowerLevels(#[from] PowerLevelsError),
423}
424
425#[rustfmt::skip] impl Error {
427 pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
433 as_variant!(self, Self::Http).and_then(|e| e.as_ruma_api_error())
434 }
435
436 pub fn as_client_api_error(&self) -> Option<&ruma::api::client::Error> {
439 self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
440 }
441
442 pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
445 self.as_client_api_error().and_then(|e| {
446 as_variant!(&e.body, ErrorBody::Standard { kind, .. } => kind)
447 })
448 }
449
450 pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
462 self.as_ruma_api_error().and_then(as_variant!(RumaApiError::Uiaa))
463 }
464}
465
466impl From<HttpError> for Error {
467 fn from(error: HttpError) -> Self {
468 Error::Http(Box::new(error))
469 }
470}
471
472#[cfg(feature = "e2e-encryption")]
473impl From<CryptoStoreError> for Error {
474 fn from(error: CryptoStoreError) -> Self {
475 Error::CryptoStoreError(Box::new(error))
476 }
477}
478
479impl From<CrossProcessLockError> for Error {
480 fn from(error: CrossProcessLockError) -> Self {
481 Error::CrossProcessLockError(Box::new(error))
482 }
483}
484
485#[cfg(feature = "e2e-encryption")]
486impl From<OlmError> for Error {
487 fn from(error: OlmError) -> Self {
488 Error::OlmError(Box::new(error))
489 }
490}
491
492#[cfg(feature = "e2e-encryption")]
493impl From<MegolmError> for Error {
494 fn from(error: MegolmError) -> Self {
495 Error::MegolmError(Box::new(error))
496 }
497}
498
499impl From<StoreError> for Error {
500 fn from(error: StoreError) -> Self {
501 Error::StateStore(Box::new(error))
502 }
503}
504
505impl From<EventCacheStoreError> for Error {
506 fn from(error: EventCacheStoreError) -> Self {
507 Error::EventCacheStore(Box::new(error))
508 }
509}
510
511impl From<MediaStoreError> for Error {
512 fn from(error: MediaStoreError) -> Self {
513 Error::MediaStore(Box::new(error))
514 }
515}
516
517#[cfg(feature = "qrcode")]
518impl From<ScanError> for Error {
519 fn from(error: ScanError) -> Self {
520 Error::QrCodeScanError(Box::new(error))
521 }
522}
523
524impl From<SlidingSyncError> for Error {
525 fn from(error: SlidingSyncError) -> Self {
526 Error::SlidingSync(Box::new(error))
527 }
528}
529
530impl From<OAuthError> for Error {
531 fn from(error: OAuthError) -> Self {
532 Error::OAuth(Box::new(error))
533 }
534}
535
536impl From<EventCacheError> for Error {
537 fn from(error: EventCacheError) -> Self {
538 Error::EventCache(Box::new(error))
539 }
540}
541
542impl From<QueueWedgeError> for Error {
543 fn from(error: QueueWedgeError) -> Self {
544 Error::SendQueueWedgeError(Box::new(error))
545 }
546}
547
548#[cfg(feature = "e2e-encryption")]
550#[derive(Error, Debug)]
551#[allow(dead_code)]
553pub enum RoomKeyImportError {
554 #[error(transparent)]
556 SerdeJson(#[from] JsonError),
557
558 #[error("The crypto store hasn't been yet opened, can't import yet.")]
561 StoreClosed,
562
563 #[error(transparent)]
565 Io(#[from] IoError),
566
567 #[error(transparent)]
569 CryptoStore(#[from] CryptoStoreError),
570
571 #[error(transparent)]
573 Export(#[from] KeyExportError),
574}
575
576impl From<FromHttpResponseError<ruma::api::client::Error>> for HttpError {
577 fn from(err: FromHttpResponseError<ruma::api::client::Error>) -> Self {
578 Self::Api(Box::new(err.map(RumaApiError::ClientApi)))
579 }
580}
581
582impl From<FromHttpResponseError<UiaaResponse>> for HttpError {
583 fn from(err: FromHttpResponseError<UiaaResponse>) -> Self {
584 Self::Api(Box::new(err.map(|e| match e {
585 UiaaResponse::AuthResponse(i) => RumaApiError::Uiaa(i),
586 UiaaResponse::MatrixError(e) => RumaApiError::ClientApi(e),
587 })))
588 }
589}
590
591impl From<FromHttpResponseError<ruma::api::error::MatrixError>> for HttpError {
592 fn from(err: FromHttpResponseError<ruma::api::error::MatrixError>) -> Self {
593 Self::Api(Box::new(err.map(RumaApiError::Other)))
594 }
595}
596
597impl From<SdkBaseError> for Error {
598 fn from(e: SdkBaseError) -> Self {
599 match e {
600 SdkBaseError::StateStore(e) => Self::StateStore(Box::new(e)),
601 #[cfg(feature = "e2e-encryption")]
602 SdkBaseError::CryptoStore(e) => Self::CryptoStoreError(Box::new(e)),
603 #[cfg(feature = "e2e-encryption")]
604 SdkBaseError::BadCryptoStoreState => Self::BadCryptoStoreState,
605 #[cfg(feature = "e2e-encryption")]
606 SdkBaseError::OlmError(e) => Self::OlmError(Box::new(e)),
607 #[cfg(feature = "eyre")]
608 _ => Self::UnknownError(eyre::eyre!(e).into()),
609 #[cfg(all(not(feature = "eyre"), feature = "anyhow", not(target_family = "wasm")))]
610 _ => Self::UnknownError(anyhow::anyhow!(e).into()),
611 #[cfg(all(not(feature = "eyre"), feature = "anyhow", target_family = "wasm"))]
612 _ => Self::UnknownError(e.into()),
613 #[cfg(all(
614 not(feature = "eyre"),
615 not(feature = "anyhow"),
616 not(target_family = "wasm")
617 ))]
618 _ => {
619 let e: Box<dyn std::error::Error + Send + Sync> = format!("{e:?}").into();
620 Self::UnknownError(e)
621 }
622 #[cfg(all(not(feature = "eyre"), not(feature = "anyhow"), target_family = "wasm"))]
623 _ => {
624 let e: Box<dyn std::error::Error> = format!("{e:?}").into();
625 Self::UnknownError(e)
626 }
627 }
628 }
629}
630
631impl From<ReqwestError> for Error {
632 fn from(e: ReqwestError) -> Self {
633 Error::Http(Box::new(HttpError::Reqwest(e)))
634 }
635}
636
637#[derive(Debug, Error)]
639pub enum BeaconError {
640 #[error("Network error: {0}")]
642 Network(#[from] HttpError),
643
644 #[error("Existing beacon information not found.")]
646 NotFound,
647
648 #[error("Beacon event is redacted and cannot be processed.")]
650 Redacted,
651
652 #[error("Must join the room to access beacon information.")]
654 Stripped,
655
656 #[error("Deserialization error: {0}")]
658 Deserialization(#[from] serde_json::Error),
659
660 #[error("The beacon event has expired.")]
662 NotLive,
663
664 #[error("Other error: {0}")]
666 Other(Box<Error>),
667}
668
669impl From<Error> for BeaconError {
670 fn from(err: Error) -> Self {
671 BeaconError::Other(Box::new(err))
672 }
673}
674
675#[derive(Debug, Error, Clone)]
683pub enum RefreshTokenError {
684 #[error("missing refresh token")]
686 RefreshTokenRequired,
687
688 #[error(transparent)]
690 MatrixAuth(Arc<HttpError>),
691
692 #[error(transparent)]
694 OAuth(#[from] Arc<OAuthError>),
695}
696
697#[derive(Debug, Error, Clone, PartialEq)]
699pub enum NotificationSettingsError {
700 #[error("Invalid parameter `{0}`")]
702 InvalidParameter(String),
703 #[error("Unable to add push rule")]
705 UnableToAddPushRule,
706 #[error("Unable to remove push rule")]
708 UnableToRemovePushRule,
709 #[error("Unable to update push rule")]
711 UnableToUpdatePushRule,
712 #[error("Rule `{0}` not found")]
714 RuleNotFound(String),
715 #[error("Unable to save push rules")]
717 UnableToSavePushRules,
718}
719
720impl NotificationSettingsError {
721 pub fn is_rule_not_found(&self) -> bool {
723 matches!(self, Self::RuleNotFound(_))
724 }
725}
726
727impl From<InsertPushRuleError> for NotificationSettingsError {
728 fn from(_: InsertPushRuleError) -> Self {
729 Self::UnableToAddPushRule
730 }
731}
732
733impl From<RemovePushRuleError> for NotificationSettingsError {
734 fn from(_: RemovePushRuleError) -> Self {
735 Self::UnableToRemovePushRule
736 }
737}
738
739#[derive(Debug, Error)]
740#[error("expected: {expected}, got: {got:?}")]
741pub struct WrongRoomState {
742 expected: &'static str,
743 got: RoomState,
744}
745
746impl WrongRoomState {
747 pub(crate) fn new(expected: &'static str, got: RoomState) -> Self {
748 Self { expected, got }
749 }
750}