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 cross_process_lock::CrossProcessLockUnobtained, event_cache::store::EventCacheStoreError,
30 media::store::MediaStoreError,
31};
32use reqwest::Error as ReqwestError;
33use ruma::{
34 IdParseError,
35 api::{
36 client::uiaa::{UiaaInfo, UiaaResponse},
37 error::{ErrorKind, FromHttpResponseError, IntoHttpError, RetryAfter},
38 },
39 events::{room::power_levels::PowerLevelsError, tag::InvalidUserTagName},
40 push::{InsertPushRuleError, RemovePushRuleError},
41};
42#[cfg(target_os = "android")]
43use rustls::client::VerifierBuilderError;
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::error::Error),
67
68 #[error("User-Interactive Authentication required.")]
75 Uiaa(UiaaInfo),
76}
77
78impl RumaApiError {
79 pub fn as_client_api_error(&self) -> Option<&ruma::api::error::Error> {
83 as_variant!(self, Self::ClientApi)
84 }
85}
86
87#[derive(Error, Debug)]
90pub enum HttpError {
91 #[error(transparent)]
93 Reqwest(#[from] ReqwestError),
94
95 #[error(transparent)]
98 Api(#[from] Box<FromHttpResponseError<RumaApiError>>),
99
100 #[error(transparent)]
103 IntoHttp(IntoHttpError),
104
105 #[error(transparent)]
107 RefreshToken(RefreshTokenError),
108
109 #[cfg(target_os = "android")]
111 #[error(transparent)]
112 VerifierBuilder(#[from] VerifierBuilderError),
113}
114
115#[rustfmt::skip] impl HttpError {
117 pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
123 match self {
124 Self::Api(error) => {
125 as_variant!(error.as_ref(), FromHttpResponseError::Server)
126 },
127 _ => None
128 }
129 }
130
131 pub fn as_client_api_error(&self) -> Option<&ruma::api::error::Error> {
134 self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
135 }
136}
137
138impl HttpError {
140 pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
143 self.as_client_api_error().and_then(ruma::api::error::Error::error_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 match api_error {
213 RumaApiError::ClientApi(client_error) => match client_error.error_kind() {
214 Some(ErrorKind::LimitExceeded(limit_exceeded)) => {
215 RetryKind::from_retry_after(limit_exceeded.retry_after.as_ref())
216 }
217 Some(ErrorKind::Unrecognized) => RetryKind::Permanent,
218 _ => RetryKind::from_status_code(client_error.status_code),
219 },
220 RumaApiError::Uiaa(_) => RetryKind::Permanent,
221 }
222 }
223
224 fn from_retry_after(retry_after: Option<&RetryAfter>) -> Self {
230 let retry_after = retry_after
231 .and_then(|retry_after| match retry_after {
232 RetryAfter::Delay(d) => Some(d),
233 RetryAfter::DateTime(_) => None,
234 })
235 .copied();
236
237 Self::Transient { retry_after }
238 }
239
240 fn from_status_code(status_code: StatusCode) -> Self {
247 if status_code.as_u16() == 520 {
248 RetryKind::Permanent
251 } else if status_code == StatusCode::TOO_MANY_REQUESTS || status_code.is_server_error() {
252 RetryKind::Transient { retry_after: None }
256 } else {
257 RetryKind::Permanent
258 }
259 }
260}
261
262#[derive(Error, Debug)]
264#[non_exhaustive]
265pub enum Error {
266 #[error(transparent)]
268 Http(Box<HttpError>),
269
270 #[error("the queried endpoint requires authentication but was called before logging in")]
273 AuthenticationRequired,
274
275 #[error("Local cache doesn't contain all necessary data to perform the action.")]
277 InsufficientData,
278
279 #[cfg(feature = "e2e-encryption")]
282 #[error("The olm machine has already been initialized")]
283 BadCryptoStoreState,
284
285 #[cfg(feature = "e2e-encryption")]
287 #[error("The olm machine isn't yet available")]
288 NoOlmMachine,
289
290 #[error(transparent)]
292 SerdeJson(#[from] JsonError),
293
294 #[error(transparent)]
296 Io(#[from] IoError),
297
298 #[cfg(feature = "e2e-encryption")]
300 #[error(transparent)]
301 CryptoStoreError(Box<CryptoStoreError>),
302
303 #[error(transparent)]
305 CrossProcessLockError(Box<CrossProcessLockError>),
306
307 #[cfg(feature = "e2e-encryption")]
309 #[error(transparent)]
310 OlmError(Box<OlmError>),
311
312 #[cfg(feature = "e2e-encryption")]
314 #[error(transparent)]
315 MegolmError(Box<MegolmError>),
316
317 #[cfg(feature = "e2e-encryption")]
319 #[error(transparent)]
320 DecryptorError(#[from] DecryptorError),
321
322 #[error(transparent)]
324 StateStore(Box<StoreError>),
325
326 #[error(transparent)]
328 EventCacheStore(Box<EventCacheStoreError>),
329
330 #[error(transparent)]
332 MediaStore(Box<MediaStoreError>),
333
334 #[error(transparent)]
336 Identifier(#[from] IdParseError),
337
338 #[error(transparent)]
340 Url(#[from] UrlParseError),
341
342 #[cfg(feature = "qrcode")]
344 #[error(transparent)]
345 QrCodeScanError(Box<ScanError>),
346
347 #[error(transparent)]
349 UserTagName(#[from] InvalidUserTagName),
350
351 #[error(transparent)]
353 SlidingSync(Box<SlidingSyncError>),
354
355 #[error("wrong room state: {0}")]
359 WrongRoomState(Box<WrongRoomState>),
360
361 #[error("session callbacks have been set multiple times")]
363 MultipleSessionCallbacks,
364
365 #[error(transparent)]
367 OAuth(Box<OAuthError>),
368
369 #[error("a concurrent request failed; see logs for details")]
371 ConcurrentRequestFailed,
372
373 #[cfg(not(target_family = "wasm"))]
378 #[error("unknown error: {0}")]
379 UnknownError(Box<dyn std::error::Error + Send + Sync>),
380
381 #[cfg(target_family = "wasm")]
383 #[error("unknown error: {0}")]
384 UnknownError(Box<dyn std::error::Error>),
385
386 #[error(transparent)]
388 EventCache(Box<EventCacheError>),
389
390 #[error(transparent)]
392 SendQueueWedgeError(Box<QueueWedgeError>),
393
394 #[error("backups are not enabled")]
396 BackupNotEnabled,
397
398 #[error("can't ignore the logged-in user")]
400 CantIgnoreLoggedInUser,
401
402 #[error(transparent)]
404 Media(#[from] MediaError),
405
406 #[error(transparent)]
408 ReplyError(#[from] ReplyError),
409
410 #[error("power levels error: {0}")]
412 PowerLevels(#[from] PowerLevelsError),
413
414 #[error("timed out")]
416 Timeout,
417}
418
419#[rustfmt::skip] impl Error {
421 pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
427 as_variant!(self, Self::Http).and_then(|e| e.as_ruma_api_error())
428 }
429
430 pub fn as_client_api_error(&self) -> Option<&ruma::api::error::Error> {
433 self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
434 }
435
436 pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
439 self.as_client_api_error().and_then(ruma::api::error::Error::error_kind)
440 }
441
442 pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
454 self.as_ruma_api_error().and_then(as_variant!(RumaApiError::Uiaa))
455 }
456}
457
458impl From<HttpError> for Error {
459 fn from(error: HttpError) -> Self {
460 Error::Http(Box::new(error))
461 }
462}
463
464#[cfg(feature = "e2e-encryption")]
465impl From<CryptoStoreError> for Error {
466 fn from(error: CryptoStoreError) -> Self {
467 Error::CryptoStoreError(Box::new(error))
468 }
469}
470
471impl From<CrossProcessLockError> for Error {
472 fn from(error: CrossProcessLockError) -> Self {
473 Error::CrossProcessLockError(Box::new(error))
474 }
475}
476
477impl From<CrossProcessLockUnobtained> for Error {
478 fn from(error: CrossProcessLockUnobtained) -> Self {
479 CrossProcessLockError::from(error).into()
480 }
481}
482
483#[cfg(feature = "e2e-encryption")]
484impl From<OlmError> for Error {
485 fn from(error: OlmError) -> Self {
486 Error::OlmError(Box::new(error))
487 }
488}
489
490#[cfg(feature = "e2e-encryption")]
491impl From<MegolmError> for Error {
492 fn from(error: MegolmError) -> Self {
493 Error::MegolmError(Box::new(error))
494 }
495}
496
497impl From<StoreError> for Error {
498 fn from(error: StoreError) -> Self {
499 Error::StateStore(Box::new(error))
500 }
501}
502
503impl From<EventCacheStoreError> for Error {
504 fn from(error: EventCacheStoreError) -> Self {
505 Error::EventCacheStore(Box::new(error))
506 }
507}
508
509impl From<MediaStoreError> for Error {
510 fn from(error: MediaStoreError) -> Self {
511 Error::MediaStore(Box::new(error))
512 }
513}
514
515#[cfg(feature = "qrcode")]
516impl From<ScanError> for Error {
517 fn from(error: ScanError) -> Self {
518 Error::QrCodeScanError(Box::new(error))
519 }
520}
521
522impl From<SlidingSyncError> for Error {
523 fn from(error: SlidingSyncError) -> Self {
524 Error::SlidingSync(Box::new(error))
525 }
526}
527
528impl From<OAuthError> for Error {
529 fn from(error: OAuthError) -> Self {
530 Error::OAuth(Box::new(error))
531 }
532}
533
534impl From<EventCacheError> for Error {
535 fn from(error: EventCacheError) -> Self {
536 Error::EventCache(Box::new(error))
537 }
538}
539
540impl From<QueueWedgeError> for Error {
541 fn from(error: QueueWedgeError) -> Self {
542 Error::SendQueueWedgeError(Box::new(error))
543 }
544}
545
546#[cfg(feature = "e2e-encryption")]
548#[derive(Error, Debug)]
549#[allow(dead_code)]
551pub enum RoomKeyImportError {
552 #[error(transparent)]
554 SerdeJson(#[from] JsonError),
555
556 #[error("The crypto store hasn't been yet opened, can't import yet.")]
559 StoreClosed,
560
561 #[error(transparent)]
563 Io(#[from] IoError),
564
565 #[error(transparent)]
567 CryptoStore(#[from] CryptoStoreError),
568
569 #[error(transparent)]
571 Export(#[from] KeyExportError),
572}
573
574impl From<FromHttpResponseError<ruma::api::error::Error>> for HttpError {
575 fn from(err: FromHttpResponseError<ruma::api::error::Error>) -> Self {
576 Self::Api(Box::new(err.map(RumaApiError::ClientApi)))
577 }
578}
579
580impl From<FromHttpResponseError<UiaaResponse>> for HttpError {
581 fn from(err: FromHttpResponseError<UiaaResponse>) -> Self {
582 Self::Api(Box::new(err.map(|e| match e {
583 UiaaResponse::AuthResponse(i) => RumaApiError::Uiaa(i),
584 UiaaResponse::MatrixError(e) => RumaApiError::ClientApi(e),
585 })))
586 }
587}
588
589impl From<SdkBaseError> for Error {
590 fn from(e: SdkBaseError) -> Self {
591 match e {
592 SdkBaseError::StateStore(e) => Self::StateStore(Box::new(e)),
593 #[cfg(feature = "e2e-encryption")]
594 SdkBaseError::CryptoStore(e) => Self::CryptoStoreError(Box::new(e)),
595 #[cfg(feature = "e2e-encryption")]
596 SdkBaseError::BadCryptoStoreState => Self::BadCryptoStoreState,
597 #[cfg(feature = "e2e-encryption")]
598 SdkBaseError::OlmError(e) => Self::OlmError(Box::new(e)),
599 #[cfg(feature = "eyre")]
600 _ => Self::UnknownError(eyre::eyre!(e).into()),
601 #[cfg(all(not(feature = "eyre"), feature = "anyhow", not(target_family = "wasm")))]
602 _ => Self::UnknownError(anyhow::anyhow!(e).into()),
603 #[cfg(all(not(feature = "eyre"), feature = "anyhow", target_family = "wasm"))]
604 _ => Self::UnknownError(e.into()),
605 #[cfg(all(
606 not(feature = "eyre"),
607 not(feature = "anyhow"),
608 not(target_family = "wasm")
609 ))]
610 _ => {
611 let e: Box<dyn std::error::Error + Send + Sync> = format!("{e:?}").into();
612 Self::UnknownError(e)
613 }
614 #[cfg(all(not(feature = "eyre"), not(feature = "anyhow"), target_family = "wasm"))]
615 _ => {
616 let e: Box<dyn std::error::Error> = format!("{e:?}").into();
617 Self::UnknownError(e)
618 }
619 }
620 }
621}
622
623impl From<ReqwestError> for Error {
624 fn from(e: ReqwestError) -> Self {
625 Error::Http(Box::new(HttpError::Reqwest(e)))
626 }
627}
628
629#[derive(Debug, Error)]
631pub enum BeaconError {
632 #[error("Network error: {0}")]
634 Network(#[from] HttpError),
635
636 #[error("Existing beacon information not found.")]
638 NotFound,
639
640 #[error("Beacon event is redacted and cannot be processed.")]
642 Redacted,
643
644 #[error("Must join the room to access beacon information.")]
646 Stripped,
647
648 #[error("Deserialization error: {0}")]
650 Deserialization(#[from] serde_json::Error),
651
652 #[error("The beacon event has expired.")]
654 NotLive,
655
656 #[error("Other error: {0}")]
658 Other(Box<Error>),
659}
660
661impl From<Error> for BeaconError {
662 fn from(err: Error) -> Self {
663 BeaconError::Other(Box::new(err))
664 }
665}
666
667#[derive(Debug, Error, Clone)]
675pub enum RefreshTokenError {
676 #[error("missing refresh token")]
678 RefreshTokenRequired,
679
680 #[error(transparent)]
682 MatrixAuth(Arc<HttpError>),
683
684 #[error(transparent)]
686 OAuth(#[from] Arc<OAuthError>),
687}
688
689#[derive(Debug, Error, Clone, PartialEq)]
691pub enum NotificationSettingsError {
692 #[error("Invalid parameter `{0}`")]
694 InvalidParameter(String),
695 #[error("Unable to add push rule")]
697 UnableToAddPushRule,
698 #[error("Unable to remove push rule")]
700 UnableToRemovePushRule,
701 #[error("Unable to update push rule")]
703 UnableToUpdatePushRule,
704 #[error("Rule `{0}` not found")]
706 RuleNotFound(String),
707 #[error("Unable to save push rules")]
709 UnableToSavePushRules,
710}
711
712impl NotificationSettingsError {
713 pub fn is_rule_not_found(&self) -> bool {
715 matches!(self, Self::RuleNotFound(_))
716 }
717}
718
719impl From<InsertPushRuleError> for NotificationSettingsError {
720 fn from(_: InsertPushRuleError) -> Self {
721 Self::UnableToAddPushRule
722 }
723}
724
725impl From<RemovePushRuleError> for NotificationSettingsError {
726 fn from(_: RemovePushRuleError) -> Self {
727 Self::UnableToRemovePushRule
728 }
729}
730
731#[derive(Debug, Error)]
732#[error("expected: {expected}, got: {got:?}")]
733pub struct WrongRoomState {
734 expected: &'static str,
735 got: RoomState,
736}
737
738impl WrongRoomState {
739 pub(crate) fn new(expected: &'static str, got: RoomState) -> Self {
740 Self { expected, got }
741 }
742}