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("the queried endpoint is not meant for clients")]
101 NotClientRequest,
102
103 #[error(transparent)]
106 Api(#[from] Box<FromHttpResponseError<RumaApiError>>),
107
108 #[error(transparent)]
111 IntoHttp(IntoHttpError),
112
113 #[error(transparent)]
115 RefreshToken(RefreshTokenError),
116}
117
118#[rustfmt::skip] impl HttpError {
120 pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
126 match self {
127 Self::Api(error) => {
128 as_variant!(error.as_ref(), FromHttpResponseError::Server)
129 },
130 _ => None
131 }
132 }
133
134 pub fn as_client_api_error(&self) -> Option<&ruma::api::client::Error> {
137 self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
138 }
139}
140
141impl HttpError {
143 pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
146 self.as_client_api_error()
147 .and_then(|e| as_variant!(&e.body, ErrorBody::Standard { kind, .. } => kind))
148 }
149
150 pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
162 self.as_ruma_api_error().and_then(as_variant!(RumaApiError::Uiaa))
163 }
164
165 pub(crate) fn retry_kind(&self) -> RetryKind {
168 match self {
169 HttpError::Reqwest(_) => RetryKind::NetworkFailure,
172
173 HttpError::Api(error) => match error.as_ref() {
174 FromHttpResponseError::Server(api_error) => RetryKind::from_api_error(api_error),
175 _ => RetryKind::Permanent,
176 },
177 _ => RetryKind::Permanent,
178 }
179 }
180}
181
182impl From<FromHttpResponseError<RumaApiError>> for HttpError {
183 fn from(value: FromHttpResponseError<RumaApiError>) -> Self {
184 Self::Api(Box::new(value))
185 }
186}
187
188pub(crate) enum RetryKind {
191 NetworkFailure,
193
194 Transient {
198 #[cfg_attr(target_family = "wasm", allow(dead_code))]
200 retry_after: Option<Duration>,
201 },
202
203 Permanent,
206}
207
208impl RetryKind {
209 fn from_api_error(api_error: &RumaApiError) -> Self {
216 use ruma::api::client::Error;
217
218 match api_error {
219 RumaApiError::ClientApi(client_error) => {
220 let Error { status_code, body, .. } = client_error;
221
222 match body {
223 ErrorBody::Standard { kind, .. } => match kind {
224 ErrorKind::LimitExceeded { retry_after } => {
225 RetryKind::from_retry_after(retry_after.as_ref())
226 }
227 ErrorKind::Unrecognized => RetryKind::Permanent,
228 _ => RetryKind::from_status_code(*status_code),
229 },
230 _ => RetryKind::from_status_code(*status_code),
231 }
232 }
233 RumaApiError::Other(e) => RetryKind::from_status_code(e.status_code),
234 RumaApiError::Uiaa(_) => RetryKind::Permanent,
235 }
236 }
237
238 fn from_retry_after(retry_after: Option<&RetryAfter>) -> Self {
244 let retry_after = retry_after
245 .and_then(|retry_after| match retry_after {
246 RetryAfter::Delay(d) => Some(d),
247 RetryAfter::DateTime(_) => None,
248 })
249 .copied();
250
251 Self::Transient { retry_after }
252 }
253
254 fn from_status_code(status_code: StatusCode) -> Self {
261 if status_code.as_u16() == 520 {
262 RetryKind::Permanent
265 } else if status_code == StatusCode::TOO_MANY_REQUESTS || status_code.is_server_error() {
266 RetryKind::Transient { retry_after: None }
270 } else {
271 RetryKind::Permanent
272 }
273 }
274}
275
276#[derive(Error, Debug)]
278#[non_exhaustive]
279pub enum Error {
280 #[error(transparent)]
282 Http(Box<HttpError>),
283
284 #[error("the queried endpoint requires authentication but was called before logging in")]
287 AuthenticationRequired,
288
289 #[error("Local cache doesn't contain all necessary data to perform the action.")]
291 InsufficientData,
292
293 #[cfg(feature = "e2e-encryption")]
296 #[error("The olm machine has already been initialized")]
297 BadCryptoStoreState,
298
299 #[cfg(feature = "e2e-encryption")]
301 #[error("The olm machine isn't yet available")]
302 NoOlmMachine,
303
304 #[error(transparent)]
306 SerdeJson(#[from] JsonError),
307
308 #[error(transparent)]
310 Io(#[from] IoError),
311
312 #[cfg(feature = "e2e-encryption")]
314 #[error(transparent)]
315 CryptoStoreError(Box<CryptoStoreError>),
316
317 #[error(transparent)]
319 CrossProcessLockError(Box<CrossProcessLockError>),
320
321 #[cfg(feature = "e2e-encryption")]
323 #[error(transparent)]
324 OlmError(Box<OlmError>),
325
326 #[cfg(feature = "e2e-encryption")]
328 #[error(transparent)]
329 MegolmError(Box<MegolmError>),
330
331 #[cfg(feature = "e2e-encryption")]
333 #[error(transparent)]
334 DecryptorError(#[from] DecryptorError),
335
336 #[error(transparent)]
338 StateStore(Box<StoreError>),
339
340 #[error(transparent)]
342 EventCacheStore(Box<EventCacheStoreError>),
343
344 #[error(transparent)]
346 MediaStore(Box<MediaStoreError>),
347
348 #[error(transparent)]
350 Identifier(#[from] IdParseError),
351
352 #[error(transparent)]
354 Url(#[from] UrlParseError),
355
356 #[cfg(feature = "qrcode")]
358 #[error(transparent)]
359 QrCodeScanError(Box<ScanError>),
360
361 #[error(transparent)]
363 UserTagName(#[from] InvalidUserTagName),
364
365 #[error(transparent)]
367 SlidingSync(Box<SlidingSyncError>),
368
369 #[error("wrong room state: {0}")]
373 WrongRoomState(Box<WrongRoomState>),
374
375 #[error("session callbacks have been set multiple times")]
377 MultipleSessionCallbacks,
378
379 #[error(transparent)]
381 OAuth(Box<OAuthError>),
382
383 #[error("a concurrent request failed; see logs for details")]
385 ConcurrentRequestFailed,
386
387 #[cfg(not(target_family = "wasm"))]
392 #[error("unknown error: {0}")]
393 UnknownError(Box<dyn std::error::Error + Send + Sync>),
394
395 #[cfg(target_family = "wasm")]
397 #[error("unknown error: {0}")]
398 UnknownError(Box<dyn std::error::Error>),
399
400 #[error(transparent)]
402 EventCache(Box<EventCacheError>),
403
404 #[error(transparent)]
406 SendQueueWedgeError(Box<QueueWedgeError>),
407
408 #[error("backups are not enabled")]
410 BackupNotEnabled,
411
412 #[error("can't ignore the logged-in user")]
414 CantIgnoreLoggedInUser,
415
416 #[error(transparent)]
418 Media(#[from] MediaError),
419
420 #[error(transparent)]
422 ReplyError(#[from] ReplyError),
423
424 #[error("power levels error: {0}")]
426 PowerLevels(#[from] PowerLevelsError),
427}
428
429#[rustfmt::skip] impl Error {
431 pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
437 as_variant!(self, Self::Http).and_then(|e| e.as_ruma_api_error())
438 }
439
440 pub fn as_client_api_error(&self) -> Option<&ruma::api::client::Error> {
443 self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
444 }
445
446 pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
449 self.as_client_api_error().and_then(|e| {
450 as_variant!(&e.body, ErrorBody::Standard { kind, .. } => kind)
451 })
452 }
453
454 pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
466 self.as_ruma_api_error().and_then(as_variant!(RumaApiError::Uiaa))
467 }
468}
469
470impl From<HttpError> for Error {
471 fn from(error: HttpError) -> Self {
472 Error::Http(Box::new(error))
473 }
474}
475
476#[cfg(feature = "e2e-encryption")]
477impl From<CryptoStoreError> for Error {
478 fn from(error: CryptoStoreError) -> Self {
479 Error::CryptoStoreError(Box::new(error))
480 }
481}
482
483impl From<CrossProcessLockError> for Error {
484 fn from(error: CrossProcessLockError) -> Self {
485 Error::CrossProcessLockError(Box::new(error))
486 }
487}
488
489#[cfg(feature = "e2e-encryption")]
490impl From<OlmError> for Error {
491 fn from(error: OlmError) -> Self {
492 Error::OlmError(Box::new(error))
493 }
494}
495
496#[cfg(feature = "e2e-encryption")]
497impl From<MegolmError> for Error {
498 fn from(error: MegolmError) -> Self {
499 Error::MegolmError(Box::new(error))
500 }
501}
502
503impl From<StoreError> for Error {
504 fn from(error: StoreError) -> Self {
505 Error::StateStore(Box::new(error))
506 }
507}
508
509impl From<EventCacheStoreError> for Error {
510 fn from(error: EventCacheStoreError) -> Self {
511 Error::EventCacheStore(Box::new(error))
512 }
513}
514
515impl From<MediaStoreError> for Error {
516 fn from(error: MediaStoreError) -> Self {
517 Error::MediaStore(Box::new(error))
518 }
519}
520
521#[cfg(feature = "qrcode")]
522impl From<ScanError> for Error {
523 fn from(error: ScanError) -> Self {
524 Error::QrCodeScanError(Box::new(error))
525 }
526}
527
528impl From<SlidingSyncError> for Error {
529 fn from(error: SlidingSyncError) -> Self {
530 Error::SlidingSync(Box::new(error))
531 }
532}
533
534impl From<OAuthError> for Error {
535 fn from(error: OAuthError) -> Self {
536 Error::OAuth(Box::new(error))
537 }
538}
539
540impl From<EventCacheError> for Error {
541 fn from(error: EventCacheError) -> Self {
542 Error::EventCache(Box::new(error))
543 }
544}
545
546impl From<QueueWedgeError> for Error {
547 fn from(error: QueueWedgeError) -> Self {
548 Error::SendQueueWedgeError(Box::new(error))
549 }
550}
551
552#[cfg(feature = "e2e-encryption")]
554#[derive(Error, Debug)]
555#[allow(dead_code)]
557pub enum RoomKeyImportError {
558 #[error(transparent)]
560 SerdeJson(#[from] JsonError),
561
562 #[error("The crypto store hasn't been yet opened, can't import yet.")]
565 StoreClosed,
566
567 #[error(transparent)]
569 Io(#[from] IoError),
570
571 #[error(transparent)]
573 CryptoStore(#[from] CryptoStoreError),
574
575 #[error(transparent)]
577 Export(#[from] KeyExportError),
578}
579
580impl From<FromHttpResponseError<ruma::api::client::Error>> for HttpError {
581 fn from(err: FromHttpResponseError<ruma::api::client::Error>) -> Self {
582 Self::Api(Box::new(err.map(RumaApiError::ClientApi)))
583 }
584}
585
586impl From<FromHttpResponseError<UiaaResponse>> for HttpError {
587 fn from(err: FromHttpResponseError<UiaaResponse>) -> Self {
588 Self::Api(Box::new(err.map(|e| match e {
589 UiaaResponse::AuthResponse(i) => RumaApiError::Uiaa(i),
590 UiaaResponse::MatrixError(e) => RumaApiError::ClientApi(e),
591 })))
592 }
593}
594
595impl From<FromHttpResponseError<ruma::api::error::MatrixError>> for HttpError {
596 fn from(err: FromHttpResponseError<ruma::api::error::MatrixError>) -> Self {
597 Self::Api(Box::new(err.map(RumaApiError::Other)))
598 }
599}
600
601impl From<SdkBaseError> for Error {
602 fn from(e: SdkBaseError) -> Self {
603 match e {
604 SdkBaseError::StateStore(e) => Self::StateStore(Box::new(e)),
605 #[cfg(feature = "e2e-encryption")]
606 SdkBaseError::CryptoStore(e) => Self::CryptoStoreError(Box::new(e)),
607 #[cfg(feature = "e2e-encryption")]
608 SdkBaseError::BadCryptoStoreState => Self::BadCryptoStoreState,
609 #[cfg(feature = "e2e-encryption")]
610 SdkBaseError::OlmError(e) => Self::OlmError(Box::new(e)),
611 #[cfg(feature = "eyre")]
612 _ => Self::UnknownError(eyre::eyre!(e).into()),
613 #[cfg(all(not(feature = "eyre"), feature = "anyhow", not(target_family = "wasm")))]
614 _ => Self::UnknownError(anyhow::anyhow!(e).into()),
615 #[cfg(all(not(feature = "eyre"), feature = "anyhow", target_family = "wasm"))]
616 _ => Self::UnknownError(e.into()),
617 #[cfg(all(
618 not(feature = "eyre"),
619 not(feature = "anyhow"),
620 not(target_family = "wasm")
621 ))]
622 _ => {
623 let e: Box<dyn std::error::Error + Send + Sync> = format!("{e:?}").into();
624 Self::UnknownError(e)
625 }
626 #[cfg(all(not(feature = "eyre"), not(feature = "anyhow"), target_family = "wasm"))]
627 _ => {
628 let e: Box<dyn std::error::Error> = format!("{e:?}").into();
629 Self::UnknownError(e)
630 }
631 }
632 }
633}
634
635impl From<ReqwestError> for Error {
636 fn from(e: ReqwestError) -> Self {
637 Error::Http(Box::new(HttpError::Reqwest(e)))
638 }
639}
640
641#[derive(Debug, Error)]
643pub enum BeaconError {
644 #[error("Network error: {0}")]
646 Network(#[from] HttpError),
647
648 #[error("Existing beacon information not found.")]
650 NotFound,
651
652 #[error("Beacon event is redacted and cannot be processed.")]
654 Redacted,
655
656 #[error("Must join the room to access beacon information.")]
658 Stripped,
659
660 #[error("Deserialization error: {0}")]
662 Deserialization(#[from] serde_json::Error),
663
664 #[error("The beacon event has expired.")]
666 NotLive,
667
668 #[error("Other error: {0}")]
670 Other(Box<Error>),
671}
672
673impl From<Error> for BeaconError {
674 fn from(err: Error) -> Self {
675 BeaconError::Other(Box::new(err))
676 }
677}
678
679#[derive(Debug, Error, Clone)]
687pub enum RefreshTokenError {
688 #[error("missing refresh token")]
690 RefreshTokenRequired,
691
692 #[error(transparent)]
694 MatrixAuth(Arc<HttpError>),
695
696 #[error(transparent)]
698 OAuth(#[from] Arc<OAuthError>),
699}
700
701#[derive(Debug, Error, Clone, PartialEq)]
703pub enum NotificationSettingsError {
704 #[error("Invalid parameter `{0}`")]
706 InvalidParameter(String),
707 #[error("Unable to add push rule")]
709 UnableToAddPushRule,
710 #[error("Unable to remove push rule")]
712 UnableToRemovePushRule,
713 #[error("Unable to update push rule")]
715 UnableToUpdatePushRule,
716 #[error("Rule `{0}` not found")]
718 RuleNotFound(String),
719 #[error("Unable to save push rules")]
721 UnableToSavePushRules,
722}
723
724impl NotificationSettingsError {
725 pub fn is_rule_not_found(&self) -> bool {
727 matches!(self, Self::RuleNotFound(_))
728 }
729}
730
731impl From<InsertPushRuleError> for NotificationSettingsError {
732 fn from(_: InsertPushRuleError) -> Self {
733 Self::UnableToAddPushRule
734 }
735}
736
737impl From<RemovePushRuleError> for NotificationSettingsError {
738 fn from(_: RemovePushRuleError) -> Self {
739 Self::UnableToRemovePushRule
740 }
741}
742
743#[derive(Debug, Error)]
744#[error("expected: {expected}, got: {got:?}")]
745pub struct WrongRoomState {
746 expected: &'static str,
747 got: RoomState,
748}
749
750impl WrongRoomState {
751 pub(crate) fn new(expected: &'static str, got: RoomState) -> Self {
752 Self { expected, got }
753 }
754}