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 BootstrapCrossSigningError, CryptoStoreError, DecryptorError, KeyExportError, MegolmError,
26 OlmError, SignatureError,
27};
28use matrix_sdk_base::{
29 Error as SdkBaseError, QueueWedgeError, RoomState, StoreError,
30 cross_process_lock::CrossProcessLockUnobtained, event_cache::store::EventCacheStoreError,
31 media::store::MediaStoreError,
32};
33use reqwest::Error as ReqwestError;
34use ruma::{
35 IdParseError,
36 api::{
37 client::uiaa::{UiaaInfo, UiaaResponse},
38 error::{ErrorKind, FromHttpResponseError, IntoHttpError, RetryAfter},
39 },
40 events::{room::power_levels::PowerLevelsError, tag::InvalidUserTagName},
41 push::{InsertPushRuleError, RemovePushRuleError},
42};
43use serde_json::Error as JsonError;
44use thiserror::Error;
45use url::ParseError as UrlParseError;
46
47use crate::{
48 authentication::oauth::OAuthError, cross_process_lock::CrossProcessLockError,
49 event_cache::EventCacheError, media::MediaError, room::reply::ReplyError,
50 sliding_sync::Error as SlidingSyncError,
51};
52
53pub type Result<T, E = Error> = std::result::Result<T, E>;
55
56pub type HttpResult<T> = std::result::Result<T, HttpError>;
58
59pub type RumaApiError = UiaaResponse;
62
63#[derive(Error, Debug)]
66pub enum HttpError {
67 #[error(transparent)]
69 Reqwest(#[from] ReqwestError),
70
71 #[error(transparent)]
74 Api(#[from] Box<FromHttpResponseError<RumaApiError>>),
75
76 #[error(transparent)]
79 IntoHttp(IntoHttpError),
80
81 #[error(transparent)]
83 RefreshToken(RefreshTokenError),
84
85 #[error(transparent)]
90 Cached(Arc<HttpError>),
91}
92
93#[rustfmt::skip] impl HttpError {
95 pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
101 match self {
102 Self::Api(error) => {
103 as_variant!(error.as_ref(), FromHttpResponseError::Server)
104 },
105 _ => None
106 }
107 }
108}
109
110impl HttpError {
112 pub fn as_client_api_error(&self) -> Option<&ruma::api::error::Error> {
115 self.as_ruma_api_error().and_then(as_variant!(UiaaResponse::MatrixError))
116 }
117
118 pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
121 self.as_client_api_error().and_then(ruma::api::error::Error::error_kind)
122 }
123
124 pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
136 self.as_ruma_api_error().and_then(as_variant!(UiaaResponse::AuthResponse))
137 }
138
139 pub(crate) fn retry_kind(&self) -> RetryKind {
142 match self {
143 HttpError::Reqwest(_) => RetryKind::NetworkFailure,
146
147 HttpError::Api(error) => match error.as_ref() {
148 FromHttpResponseError::Server(api_error) => RetryKind::from_api_error(api_error),
149 _ => RetryKind::Permanent,
150 },
151 _ => RetryKind::Permanent,
152 }
153 }
154
155 pub fn is_endpoint_not_implemented(&self) -> bool {
158 self.as_client_api_error().is_some_and(|error| error.is_endpoint_not_implemented())
159 }
160}
161
162impl From<FromHttpResponseError<RumaApiError>> for HttpError {
163 fn from(value: FromHttpResponseError<RumaApiError>) -> Self {
164 Self::Api(Box::new(value))
165 }
166}
167
168pub(crate) enum RetryKind {
171 NetworkFailure,
173
174 Transient {
178 #[cfg_attr(target_family = "wasm", allow(dead_code))]
180 retry_after: Option<Duration>,
181 },
182
183 Permanent,
186}
187
188impl RetryKind {
189 fn from_api_error(api_error: &UiaaResponse) -> Self {
196 match api_error {
197 UiaaResponse::MatrixError(client_error) => match client_error.error_kind() {
198 Some(ErrorKind::LimitExceeded(limit_exceeded)) => {
199 RetryKind::from_retry_after(limit_exceeded.retry_after.as_ref())
200 }
201 Some(ErrorKind::Unrecognized) => RetryKind::Permanent,
202 _ => RetryKind::from_status_code(client_error.status_code),
203 },
204 UiaaResponse::AuthResponse(_) => RetryKind::Permanent,
205 }
206 }
207
208 fn from_retry_after(retry_after: Option<&RetryAfter>) -> Self {
214 let retry_after = retry_after
215 .and_then(|retry_after| match retry_after {
216 RetryAfter::Delay(d) => Some(d),
217 RetryAfter::DateTime(_) => None,
218 })
219 .copied();
220
221 Self::Transient { retry_after }
222 }
223
224 fn from_status_code(status_code: StatusCode) -> Self {
231 if status_code.as_u16() == 520 {
232 RetryKind::Permanent
235 } else if status_code == StatusCode::TOO_MANY_REQUESTS || status_code.is_server_error() {
236 RetryKind::Transient { retry_after: None }
240 } else {
241 RetryKind::Permanent
242 }
243 }
244}
245
246#[derive(Error, Debug)]
248#[non_exhaustive]
249pub enum Error {
250 #[error(transparent)]
252 Http(Box<HttpError>),
253
254 #[error("the queried endpoint requires authentication but was called before logging in")]
257 AuthenticationRequired,
258
259 #[error("Local cache doesn't contain all necessary data to perform the action.")]
261 InsufficientData,
262
263 #[cfg(feature = "e2e-encryption")]
266 #[error("The olm machine has already been initialized")]
267 BadCryptoStoreState,
268
269 #[cfg(feature = "e2e-encryption")]
271 #[error("The olm machine isn't yet available")]
272 NoOlmMachine,
273
274 #[error(transparent)]
276 SerdeJson(#[from] JsonError),
277
278 #[error(transparent)]
280 Io(#[from] IoError),
281
282 #[cfg(feature = "e2e-encryption")]
284 #[error(transparent)]
285 CryptoStoreError(Box<CryptoStoreError>),
286
287 #[error(transparent)]
289 CrossProcessLockError(Box<CrossProcessLockError>),
290
291 #[cfg(feature = "e2e-encryption")]
293 #[error(transparent)]
294 OlmError(Box<OlmError>),
295
296 #[cfg(feature = "e2e-encryption")]
298 #[error(transparent)]
299 MegolmError(Box<MegolmError>),
300
301 #[cfg(feature = "e2e-encryption")]
303 #[error(transparent)]
304 DecryptorError(#[from] DecryptorError),
305
306 #[cfg(feature = "e2e-encryption")]
308 #[error(transparent)]
309 SignatureError(#[from] SignatureError),
310
311 #[error(transparent)]
313 StateStore(Box<StoreError>),
314
315 #[error(transparent)]
317 EventCacheStore(Box<EventCacheStoreError>),
318
319 #[error(transparent)]
321 MediaStore(Box<MediaStoreError>),
322
323 #[error(transparent)]
325 Identifier(#[from] IdParseError),
326
327 #[error(transparent)]
329 Url(#[from] UrlParseError),
330
331 #[cfg(feature = "qrcode")]
333 #[error(transparent)]
334 QrCodeScanError(Box<ScanError>),
335
336 #[error(transparent)]
338 UserTagName(#[from] InvalidUserTagName),
339
340 #[error(transparent)]
342 SlidingSync(Box<SlidingSyncError>),
343
344 #[error("wrong room state: {0}")]
348 WrongRoomState(Box<WrongRoomState>),
349
350 #[error("session callbacks have been set multiple times")]
352 MultipleSessionCallbacks,
353
354 #[error(transparent)]
356 OAuth(Box<OAuthError>),
357
358 #[error("a concurrent request failed; see logs for details")]
360 ConcurrentRequestFailed,
361
362 #[cfg(not(target_family = "wasm"))]
367 #[error("unknown error: {0}")]
368 UnknownError(Box<dyn std::error::Error + Send + Sync>),
369
370 #[cfg(target_family = "wasm")]
372 #[error("unknown error: {0}")]
373 UnknownError(Box<dyn std::error::Error>),
374
375 #[error(transparent)]
377 EventCache(Box<EventCacheError>),
378
379 #[error(transparent)]
381 SendQueueWedgeError(Box<QueueWedgeError>),
382
383 #[error("backups are not enabled")]
385 BackupNotEnabled,
386
387 #[error("can't ignore the logged-in user")]
389 CantIgnoreLoggedInUser,
390
391 #[error(transparent)]
393 Media(#[from] MediaError),
394
395 #[error(transparent)]
397 ReplyError(#[from] ReplyError),
398
399 #[error("power levels error: {0}")]
401 PowerLevels(#[from] PowerLevelsError),
402
403 #[error("timed out")]
405 Timeout,
406}
407
408#[rustfmt::skip] impl Error {
410 pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
416 as_variant!(self, Self::Http).and_then(|e| e.as_ruma_api_error())
417 }
418
419 pub fn as_client_api_error(&self) -> Option<&ruma::api::error::Error> {
422 self.as_ruma_api_error().and_then(as_variant!(UiaaResponse::MatrixError))
423 }
424
425 pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
428 self.as_client_api_error().and_then(ruma::api::error::Error::error_kind)
429 }
430
431 pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
443 self.as_ruma_api_error().and_then(as_variant!(UiaaResponse::AuthResponse))
444 }
445}
446
447impl From<HttpError> for Error {
448 fn from(error: HttpError) -> Self {
449 Error::Http(Box::new(error))
450 }
451}
452
453#[cfg(feature = "e2e-encryption")]
454impl From<CryptoStoreError> for Error {
455 fn from(error: CryptoStoreError) -> Self {
456 Error::CryptoStoreError(Box::new(error))
457 }
458}
459
460impl From<CrossProcessLockError> for Error {
461 fn from(error: CrossProcessLockError) -> Self {
462 Error::CrossProcessLockError(Box::new(error))
463 }
464}
465
466impl From<CrossProcessLockUnobtained> for Error {
467 fn from(error: CrossProcessLockUnobtained) -> Self {
468 CrossProcessLockError::from(error).into()
469 }
470}
471
472#[cfg(feature = "e2e-encryption")]
473impl From<OlmError> for Error {
474 fn from(error: OlmError) -> Self {
475 Error::OlmError(Box::new(error))
476 }
477}
478
479#[cfg(feature = "e2e-encryption")]
480impl From<MegolmError> for Error {
481 fn from(error: MegolmError) -> Self {
482 Error::MegolmError(Box::new(error))
483 }
484}
485
486impl From<StoreError> for Error {
487 fn from(error: StoreError) -> Self {
488 Error::StateStore(Box::new(error))
489 }
490}
491
492impl From<EventCacheStoreError> for Error {
493 fn from(error: EventCacheStoreError) -> Self {
494 Error::EventCacheStore(Box::new(error))
495 }
496}
497
498impl From<MediaStoreError> for Error {
499 fn from(error: MediaStoreError) -> Self {
500 Error::MediaStore(Box::new(error))
501 }
502}
503
504#[cfg(feature = "qrcode")]
505impl From<ScanError> for Error {
506 fn from(error: ScanError) -> Self {
507 Error::QrCodeScanError(Box::new(error))
508 }
509}
510
511impl From<SlidingSyncError> for Error {
512 fn from(error: SlidingSyncError) -> Self {
513 Error::SlidingSync(Box::new(error))
514 }
515}
516
517impl From<OAuthError> for Error {
518 fn from(error: OAuthError) -> Self {
519 Error::OAuth(Box::new(error))
520 }
521}
522
523impl From<EventCacheError> for Error {
524 fn from(error: EventCacheError) -> Self {
525 Error::EventCache(Box::new(error))
526 }
527}
528
529impl From<QueueWedgeError> for Error {
530 fn from(error: QueueWedgeError) -> Self {
531 Error::SendQueueWedgeError(Box::new(error))
532 }
533}
534
535#[cfg(feature = "e2e-encryption")]
536impl From<BootstrapCrossSigningError> for Error {
537 fn from(error: BootstrapCrossSigningError) -> Self {
538 match error {
539 BootstrapCrossSigningError::CryptoStore(e) => e.into(),
540 BootstrapCrossSigningError::Signature(e) => e.into(),
541 }
542 }
543}
544
545#[cfg(feature = "e2e-encryption")]
547#[derive(Error, Debug)]
548#[allow(dead_code)]
550pub enum RoomKeyImportError {
551 #[error(transparent)]
553 SerdeJson(#[from] JsonError),
554
555 #[error("The crypto store hasn't been yet opened, can't import yet.")]
558 StoreClosed,
559
560 #[error(transparent)]
562 Io(#[from] IoError),
563
564 #[error(transparent)]
566 CryptoStore(#[from] CryptoStoreError),
567
568 #[error(transparent)]
570 Export(#[from] KeyExportError),
571}
572
573impl From<FromHttpResponseError<ruma::api::error::Error>> for HttpError {
574 fn from(err: FromHttpResponseError<ruma::api::error::Error>) -> Self {
575 Self::Api(Box::new(err.map(Into::into)))
576 }
577}
578
579impl From<SdkBaseError> for Error {
580 fn from(e: SdkBaseError) -> Self {
581 match e {
582 SdkBaseError::StateStore(e) => Self::StateStore(Box::new(e)),
583 #[cfg(feature = "e2e-encryption")]
584 SdkBaseError::CryptoStore(e) => Self::CryptoStoreError(Box::new(e)),
585 #[cfg(feature = "e2e-encryption")]
586 SdkBaseError::BadCryptoStoreState => Self::BadCryptoStoreState,
587 #[cfg(feature = "e2e-encryption")]
588 SdkBaseError::OlmError(e) => Self::OlmError(Box::new(e)),
589 #[cfg(feature = "eyre")]
590 _ => Self::UnknownError(eyre::eyre!(e).into()),
591 #[cfg(all(not(feature = "eyre"), feature = "anyhow", not(target_family = "wasm")))]
592 _ => Self::UnknownError(anyhow::anyhow!(e).into()),
593 #[cfg(all(not(feature = "eyre"), feature = "anyhow", target_family = "wasm"))]
594 _ => Self::UnknownError(e.into()),
595 #[cfg(all(
596 not(feature = "eyre"),
597 not(feature = "anyhow"),
598 not(target_family = "wasm")
599 ))]
600 _ => {
601 let e: Box<dyn std::error::Error + Send + Sync> = format!("{e:?}").into();
602 Self::UnknownError(e)
603 }
604 #[cfg(all(not(feature = "eyre"), not(feature = "anyhow"), target_family = "wasm"))]
605 _ => {
606 let e: Box<dyn std::error::Error> = format!("{e:?}").into();
607 Self::UnknownError(e)
608 }
609 }
610 }
611}
612
613impl From<ReqwestError> for Error {
614 fn from(e: ReqwestError) -> Self {
615 Error::Http(Box::new(HttpError::Reqwest(e)))
616 }
617}
618
619#[derive(Debug, Error)]
621pub enum BeaconError {
622 #[error("Network error: {0}")]
624 Network(#[from] HttpError),
625
626 #[error("Existing beacon information not found.")]
628 NotFound,
629
630 #[error("Beacon event is redacted and cannot be processed.")]
632 Redacted,
633
634 #[error("Must join the room to access beacon information.")]
636 Stripped,
637
638 #[error("Deserialization error: {0}")]
640 Deserialization(#[from] serde_json::Error),
641
642 #[error("The beacon event has expired.")]
644 NotLive,
645
646 #[error("Other error: {0}")]
648 Other(Box<Error>),
649}
650
651impl From<Error> for BeaconError {
652 fn from(err: Error) -> Self {
653 BeaconError::Other(Box::new(err))
654 }
655}
656
657#[derive(Debug, Error, Clone)]
665pub enum RefreshTokenError {
666 #[error("missing refresh token")]
668 RefreshTokenRequired,
669
670 #[error(transparent)]
672 MatrixAuth(Arc<HttpError>),
673
674 #[error(transparent)]
676 OAuth(#[from] Arc<OAuthError>),
677}
678
679#[derive(Debug, Error, Clone, PartialEq)]
681pub enum NotificationSettingsError {
682 #[error("Invalid parameter `{0}`")]
684 InvalidParameter(String),
685 #[error("Unable to add push rule")]
687 UnableToAddPushRule,
688 #[error("Unable to remove push rule")]
690 UnableToRemovePushRule,
691 #[error("Unable to update push rule")]
693 UnableToUpdatePushRule,
694 #[error("Rule `{0}` not found")]
696 RuleNotFound(String),
697 #[error("Unable to save push rules")]
699 UnableToSavePushRules,
700}
701
702impl NotificationSettingsError {
703 pub fn is_rule_not_found(&self) -> bool {
705 matches!(self, Self::RuleNotFound(_))
706 }
707}
708
709impl From<InsertPushRuleError> for NotificationSettingsError {
710 fn from(_: InsertPushRuleError) -> Self {
711 Self::UnableToAddPushRule
712 }
713}
714
715impl From<RemovePushRuleError> for NotificationSettingsError {
716 fn from(_: RemovePushRuleError) -> Self {
717 Self::UnableToRemovePushRule
718 }
719}
720
721#[derive(Debug, Error)]
722#[error("expected: {expected}, got: {got:?}")]
723pub struct WrongRoomState {
724 expected: &'static str,
725 got: RoomState,
726}
727
728impl WrongRoomState {
729 pub(crate) fn new(expected: &'static str, got: RoomState) -> Self {
730 Self { expected, got }
731 }
732}