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,
147
148 HttpError::Api(error) => match error.as_ref() {
149 FromHttpResponseError::Server(api_error) => RetryKind::from_api_error(api_error),
150 _ => RetryKind::Permanent,
151 },
152 _ => RetryKind::Permanent,
153 }
154 }
155
156 pub fn is_endpoint_not_implemented(&self) -> bool {
159 self.as_client_api_error().is_some_and(|error| error.is_endpoint_not_implemented())
160 }
161}
162
163impl From<FromHttpResponseError<RumaApiError>> for HttpError {
164 fn from(value: FromHttpResponseError<RumaApiError>) -> Self {
165 Self::Api(Box::new(value))
166 }
167}
168
169pub(crate) enum RetryKind {
172 NetworkFailure,
174
175 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 == StatusCode::TOO_MANY_REQUESTS || status_code.is_server_error() {
241 RetryKind::Transient { retry_after: None }
242 } else {
243 RetryKind::Permanent
244 }
245 }
246}
247
248#[derive(Error, Debug)]
250#[non_exhaustive]
251pub enum Error {
252 #[error(transparent)]
254 Http(Box<HttpError>),
255
256 #[error("the queried endpoint requires authentication but was called before logging in")]
259 AuthenticationRequired,
260
261 #[error("Local cache doesn't contain all necessary data to perform the action.")]
263 InsufficientData,
264
265 #[cfg(feature = "e2e-encryption")]
268 #[error("The olm machine has already been initialized")]
269 BadCryptoStoreState,
270
271 #[cfg(feature = "e2e-encryption")]
273 #[error("The olm machine isn't yet available")]
274 NoOlmMachine,
275
276 #[error(transparent)]
278 SerdeJson(#[from] JsonError),
279
280 #[error(transparent)]
282 Io(#[from] IoError),
283
284 #[cfg(feature = "e2e-encryption")]
286 #[error(transparent)]
287 CryptoStoreError(Box<CryptoStoreError>),
288
289 #[error(transparent)]
291 CrossProcessLockError(Box<CrossProcessLockError>),
292
293 #[cfg(feature = "e2e-encryption")]
295 #[error(transparent)]
296 OlmError(Box<OlmError>),
297
298 #[cfg(feature = "e2e-encryption")]
300 #[error(transparent)]
301 MegolmError(Box<MegolmError>),
302
303 #[cfg(feature = "e2e-encryption")]
305 #[error(transparent)]
306 DecryptorError(#[from] DecryptorError),
307
308 #[cfg(feature = "e2e-encryption")]
310 #[error(transparent)]
311 SignatureError(#[from] SignatureError),
312
313 #[error(transparent)]
315 StateStore(Box<StoreError>),
316
317 #[error(transparent)]
319 EventCacheStore(Box<EventCacheStoreError>),
320
321 #[error(transparent)]
323 MediaStore(Box<MediaStoreError>),
324
325 #[error(transparent)]
327 Identifier(#[from] IdParseError),
328
329 #[error(transparent)]
331 Url(#[from] UrlParseError),
332
333 #[cfg(feature = "qrcode")]
335 #[error(transparent)]
336 QrCodeScanError(Box<ScanError>),
337
338 #[error(transparent)]
340 UserTagName(#[from] InvalidUserTagName),
341
342 #[error(transparent)]
344 SlidingSync(Box<SlidingSyncError>),
345
346 #[error("wrong room state: {0}")]
350 WrongRoomState(Box<WrongRoomState>),
351
352 #[error("session callbacks have been set multiple times")]
354 MultipleSessionCallbacks,
355
356 #[error(transparent)]
358 OAuth(Box<OAuthError>),
359
360 #[error("a concurrent request failed; see logs for details")]
362 ConcurrentRequestFailed,
363
364 #[cfg(not(target_family = "wasm"))]
369 #[error("unknown error: {0}")]
370 UnknownError(Box<dyn std::error::Error + Send + Sync>),
371
372 #[cfg(target_family = "wasm")]
374 #[error("unknown error: {0}")]
375 UnknownError(Box<dyn std::error::Error>),
376
377 #[error(transparent)]
379 EventCache(Box<EventCacheError>),
380
381 #[error(transparent)]
383 SendQueueWedgeError(Box<QueueWedgeError>),
384
385 #[error("backups are not enabled")]
387 BackupNotEnabled,
388
389 #[error("can't ignore the logged-in user")]
391 CantIgnoreLoggedInUser,
392
393 #[error(transparent)]
395 Media(#[from] MediaError),
396
397 #[error(transparent)]
399 ReplyError(#[from] ReplyError),
400
401 #[error("power levels error: {0}")]
403 PowerLevels(#[from] PowerLevelsError),
404
405 #[error("timed out")]
407 Timeout,
408}
409
410#[rustfmt::skip] impl Error {
412 pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
418 as_variant!(self, Self::Http).and_then(|e| e.as_ruma_api_error())
419 }
420
421 pub fn as_client_api_error(&self) -> Option<&ruma::api::error::Error> {
424 self.as_ruma_api_error().and_then(as_variant!(UiaaResponse::MatrixError))
425 }
426
427 pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
430 self.as_client_api_error().and_then(ruma::api::error::Error::error_kind)
431 }
432
433 pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
445 self.as_ruma_api_error().and_then(as_variant!(UiaaResponse::AuthResponse))
446 }
447}
448
449impl From<HttpError> for Error {
450 fn from(error: HttpError) -> Self {
451 Error::Http(Box::new(error))
452 }
453}
454
455#[cfg(feature = "e2e-encryption")]
456impl From<CryptoStoreError> for Error {
457 fn from(error: CryptoStoreError) -> Self {
458 Error::CryptoStoreError(Box::new(error))
459 }
460}
461
462impl From<CrossProcessLockError> for Error {
463 fn from(error: CrossProcessLockError) -> Self {
464 Error::CrossProcessLockError(Box::new(error))
465 }
466}
467
468impl From<CrossProcessLockUnobtained> for Error {
469 fn from(error: CrossProcessLockUnobtained) -> Self {
470 CrossProcessLockError::from(error).into()
471 }
472}
473
474#[cfg(feature = "e2e-encryption")]
475impl From<OlmError> for Error {
476 fn from(error: OlmError) -> Self {
477 Error::OlmError(Box::new(error))
478 }
479}
480
481#[cfg(feature = "e2e-encryption")]
482impl From<MegolmError> for Error {
483 fn from(error: MegolmError) -> Self {
484 Error::MegolmError(Box::new(error))
485 }
486}
487
488impl From<StoreError> for Error {
489 fn from(error: StoreError) -> Self {
490 Error::StateStore(Box::new(error))
491 }
492}
493
494impl From<EventCacheStoreError> for Error {
495 fn from(error: EventCacheStoreError) -> Self {
496 Error::EventCacheStore(Box::new(error))
497 }
498}
499
500impl From<MediaStoreError> for Error {
501 fn from(error: MediaStoreError) -> Self {
502 Error::MediaStore(Box::new(error))
503 }
504}
505
506#[cfg(feature = "qrcode")]
507impl From<ScanError> for Error {
508 fn from(error: ScanError) -> Self {
509 Error::QrCodeScanError(Box::new(error))
510 }
511}
512
513impl From<SlidingSyncError> for Error {
514 fn from(error: SlidingSyncError) -> Self {
515 Error::SlidingSync(Box::new(error))
516 }
517}
518
519impl From<OAuthError> for Error {
520 fn from(error: OAuthError) -> Self {
521 Error::OAuth(Box::new(error))
522 }
523}
524
525impl From<EventCacheError> for Error {
526 fn from(error: EventCacheError) -> Self {
527 Error::EventCache(Box::new(error))
528 }
529}
530
531impl From<QueueWedgeError> for Error {
532 fn from(error: QueueWedgeError) -> Self {
533 Error::SendQueueWedgeError(Box::new(error))
534 }
535}
536
537#[cfg(feature = "e2e-encryption")]
538impl From<BootstrapCrossSigningError> for Error {
539 fn from(error: BootstrapCrossSigningError) -> Self {
540 match error {
541 BootstrapCrossSigningError::CryptoStore(e) => e.into(),
542 BootstrapCrossSigningError::Signature(e) => e.into(),
543 }
544 }
545}
546
547#[cfg(feature = "e2e-encryption")]
549#[derive(Error, Debug)]
550#[allow(dead_code)]
552pub enum RoomKeyImportError {
553 #[error(transparent)]
555 SerdeJson(#[from] JsonError),
556
557 #[error("The crypto store hasn't been yet opened, can't import yet.")]
560 StoreClosed,
561
562 #[error(transparent)]
564 Io(#[from] IoError),
565
566 #[error(transparent)]
568 CryptoStore(#[from] CryptoStoreError),
569
570 #[error(transparent)]
572 Export(#[from] KeyExportError),
573}
574
575impl From<FromHttpResponseError<ruma::api::error::Error>> for HttpError {
576 fn from(err: FromHttpResponseError<ruma::api::error::Error>) -> Self {
577 Self::Api(Box::new(err.map(Into::into)))
578 }
579}
580
581impl From<SdkBaseError> for Error {
582 fn from(e: SdkBaseError) -> Self {
583 match e {
584 SdkBaseError::StateStore(e) => Self::StateStore(Box::new(e)),
585 #[cfg(feature = "e2e-encryption")]
586 SdkBaseError::CryptoStore(e) => Self::CryptoStoreError(Box::new(e)),
587 #[cfg(feature = "e2e-encryption")]
588 SdkBaseError::BadCryptoStoreState => Self::BadCryptoStoreState,
589 #[cfg(feature = "e2e-encryption")]
590 SdkBaseError::OlmError(e) => Self::OlmError(Box::new(e)),
591 #[cfg(feature = "eyre")]
592 _ => Self::UnknownError(eyre::eyre!(e).into()),
593 #[cfg(all(not(feature = "eyre"), feature = "anyhow", not(target_family = "wasm")))]
594 _ => Self::UnknownError(anyhow::anyhow!(e).into()),
595 #[cfg(all(not(feature = "eyre"), feature = "anyhow", target_family = "wasm"))]
596 _ => Self::UnknownError(e.into()),
597 #[cfg(all(
598 not(feature = "eyre"),
599 not(feature = "anyhow"),
600 not(target_family = "wasm")
601 ))]
602 _ => {
603 let e: Box<dyn std::error::Error + Send + Sync> = format!("{e:?}").into();
604 Self::UnknownError(e)
605 }
606 #[cfg(all(not(feature = "eyre"), not(feature = "anyhow"), target_family = "wasm"))]
607 _ => {
608 let e: Box<dyn std::error::Error> = format!("{e:?}").into();
609 Self::UnknownError(e)
610 }
611 }
612 }
613}
614
615impl From<ReqwestError> for Error {
616 fn from(e: ReqwestError) -> Self {
617 Error::Http(Box::new(HttpError::Reqwest(e)))
618 }
619}
620
621#[derive(Debug, Error)]
623pub enum BeaconError {
624 #[error("Network error: {0}")]
626 Network(#[from] HttpError),
627
628 #[error("Existing beacon information not found.")]
630 NotFound,
631
632 #[error("Beacon event is redacted and cannot be processed.")]
634 Redacted,
635
636 #[error("Must join the room to access beacon information.")]
638 Stripped,
639
640 #[error("Deserialization error: {0}")]
642 Deserialization(#[from] serde_json::Error),
643
644 #[error("The beacon event has expired.")]
646 NotLive,
647
648 #[error("Other error: {0}")]
650 Other(Box<Error>),
651}
652
653impl From<Error> for BeaconError {
654 fn from(err: Error) -> Self {
655 BeaconError::Other(Box::new(err))
656 }
657}
658
659#[derive(Debug, Error, Clone)]
667pub enum RefreshTokenError {
668 #[error("missing refresh token")]
670 RefreshTokenRequired,
671
672 #[error(transparent)]
674 MatrixAuth(Arc<HttpError>),
675
676 #[error(transparent)]
678 OAuth(#[from] Arc<OAuthError>),
679}
680
681#[derive(Debug, Error, Clone, PartialEq)]
683pub enum NotificationSettingsError {
684 #[error("Invalid parameter `{0}`")]
686 InvalidParameter(String),
687 #[error("Unable to add push rule")]
689 UnableToAddPushRule,
690 #[error("Unable to remove push rule")]
692 UnableToRemovePushRule,
693 #[error("Unable to update push rule")]
695 UnableToUpdatePushRule,
696 #[error("Rule `{0}` not found")]
698 RuleNotFound(String),
699 #[error("Unable to save push rules")]
701 UnableToSavePushRules,
702}
703
704impl NotificationSettingsError {
705 pub fn is_rule_not_found(&self) -> bool {
707 matches!(self, Self::RuleNotFound(_))
708 }
709}
710
711impl From<InsertPushRuleError> for NotificationSettingsError {
712 fn from(_: InsertPushRuleError) -> Self {
713 Self::UnableToAddPushRule
714 }
715}
716
717impl From<RemovePushRuleError> for NotificationSettingsError {
718 fn from(_: RemovePushRuleError) -> Self {
719 Self::UnableToRemovePushRule
720 }
721}
722
723#[derive(Debug, Error)]
724#[error("expected: {expected}, got: {got:?}")]
725pub struct WrongRoomState {
726 expected: &'static str,
727 got: RoomState,
728}
729
730impl WrongRoomState {
731 pub(crate) fn new(expected: &'static str, got: RoomState) -> Self {
732 Self { expected, got }
733 }
734}