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 == StatusCode::TOO_MANY_REQUESTS || status_code.is_server_error() {
240 RetryKind::Transient { retry_after: None }
241 } else {
242 RetryKind::Permanent
243 }
244 }
245}
246
247#[derive(Error, Debug)]
249#[non_exhaustive]
250pub enum Error {
251 #[error(transparent)]
253 Http(Box<HttpError>),
254
255 #[error("the queried endpoint requires authentication but was called before logging in")]
258 AuthenticationRequired,
259
260 #[error("Local cache doesn't contain all necessary data to perform the action.")]
262 InsufficientData,
263
264 #[cfg(feature = "e2e-encryption")]
267 #[error("The olm machine has already been initialized")]
268 BadCryptoStoreState,
269
270 #[cfg(feature = "e2e-encryption")]
272 #[error("The olm machine isn't yet available")]
273 NoOlmMachine,
274
275 #[error(transparent)]
277 SerdeJson(#[from] JsonError),
278
279 #[error(transparent)]
281 Io(#[from] IoError),
282
283 #[cfg(feature = "e2e-encryption")]
285 #[error(transparent)]
286 CryptoStoreError(Box<CryptoStoreError>),
287
288 #[error(transparent)]
290 CrossProcessLockError(Box<CrossProcessLockError>),
291
292 #[cfg(feature = "e2e-encryption")]
294 #[error(transparent)]
295 OlmError(Box<OlmError>),
296
297 #[cfg(feature = "e2e-encryption")]
299 #[error(transparent)]
300 MegolmError(Box<MegolmError>),
301
302 #[cfg(feature = "e2e-encryption")]
304 #[error(transparent)]
305 DecryptorError(#[from] DecryptorError),
306
307 #[cfg(feature = "e2e-encryption")]
309 #[error(transparent)]
310 SignatureError(#[from] SignatureError),
311
312 #[error(transparent)]
314 StateStore(Box<StoreError>),
315
316 #[error(transparent)]
318 EventCacheStore(Box<EventCacheStoreError>),
319
320 #[error(transparent)]
322 MediaStore(Box<MediaStoreError>),
323
324 #[error(transparent)]
326 Identifier(#[from] IdParseError),
327
328 #[error(transparent)]
330 Url(#[from] UrlParseError),
331
332 #[cfg(feature = "qrcode")]
334 #[error(transparent)]
335 QrCodeScanError(Box<ScanError>),
336
337 #[error(transparent)]
339 UserTagName(#[from] InvalidUserTagName),
340
341 #[error(transparent)]
343 SlidingSync(Box<SlidingSyncError>),
344
345 #[error("wrong room state: {0}")]
349 WrongRoomState(Box<WrongRoomState>),
350
351 #[error("session callbacks have been set multiple times")]
353 MultipleSessionCallbacks,
354
355 #[error(transparent)]
357 OAuth(Box<OAuthError>),
358
359 #[error("a concurrent request failed; see logs for details")]
361 ConcurrentRequestFailed,
362
363 #[cfg(not(target_family = "wasm"))]
368 #[error("unknown error: {0}")]
369 UnknownError(Box<dyn std::error::Error + Send + Sync>),
370
371 #[cfg(target_family = "wasm")]
373 #[error("unknown error: {0}")]
374 UnknownError(Box<dyn std::error::Error>),
375
376 #[error(transparent)]
378 EventCache(Box<EventCacheError>),
379
380 #[error(transparent)]
382 SendQueueWedgeError(Box<QueueWedgeError>),
383
384 #[error("backups are not enabled")]
386 BackupNotEnabled,
387
388 #[error("can't ignore the logged-in user")]
390 CantIgnoreLoggedInUser,
391
392 #[error(transparent)]
394 Media(#[from] MediaError),
395
396 #[error(transparent)]
398 ReplyError(#[from] ReplyError),
399
400 #[error("power levels error: {0}")]
402 PowerLevels(#[from] PowerLevelsError),
403
404 #[error("timed out")]
406 Timeout,
407}
408
409#[rustfmt::skip] impl Error {
411 pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
417 as_variant!(self, Self::Http).and_then(|e| e.as_ruma_api_error())
418 }
419
420 pub fn as_client_api_error(&self) -> Option<&ruma::api::error::Error> {
423 self.as_ruma_api_error().and_then(as_variant!(UiaaResponse::MatrixError))
424 }
425
426 pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
429 self.as_client_api_error().and_then(ruma::api::error::Error::error_kind)
430 }
431
432 pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
444 self.as_ruma_api_error().and_then(as_variant!(UiaaResponse::AuthResponse))
445 }
446}
447
448impl From<HttpError> for Error {
449 fn from(error: HttpError) -> Self {
450 Error::Http(Box::new(error))
451 }
452}
453
454#[cfg(feature = "e2e-encryption")]
455impl From<CryptoStoreError> for Error {
456 fn from(error: CryptoStoreError) -> Self {
457 Error::CryptoStoreError(Box::new(error))
458 }
459}
460
461impl From<CrossProcessLockError> for Error {
462 fn from(error: CrossProcessLockError) -> Self {
463 Error::CrossProcessLockError(Box::new(error))
464 }
465}
466
467impl From<CrossProcessLockUnobtained> for Error {
468 fn from(error: CrossProcessLockUnobtained) -> Self {
469 CrossProcessLockError::from(error).into()
470 }
471}
472
473#[cfg(feature = "e2e-encryption")]
474impl From<OlmError> for Error {
475 fn from(error: OlmError) -> Self {
476 Error::OlmError(Box::new(error))
477 }
478}
479
480#[cfg(feature = "e2e-encryption")]
481impl From<MegolmError> for Error {
482 fn from(error: MegolmError) -> Self {
483 Error::MegolmError(Box::new(error))
484 }
485}
486
487impl From<StoreError> for Error {
488 fn from(error: StoreError) -> Self {
489 Error::StateStore(Box::new(error))
490 }
491}
492
493impl From<EventCacheStoreError> for Error {
494 fn from(error: EventCacheStoreError) -> Self {
495 Error::EventCacheStore(Box::new(error))
496 }
497}
498
499impl From<MediaStoreError> for Error {
500 fn from(error: MediaStoreError) -> Self {
501 Error::MediaStore(Box::new(error))
502 }
503}
504
505#[cfg(feature = "qrcode")]
506impl From<ScanError> for Error {
507 fn from(error: ScanError) -> Self {
508 Error::QrCodeScanError(Box::new(error))
509 }
510}
511
512impl From<SlidingSyncError> for Error {
513 fn from(error: SlidingSyncError) -> Self {
514 Error::SlidingSync(Box::new(error))
515 }
516}
517
518impl From<OAuthError> for Error {
519 fn from(error: OAuthError) -> Self {
520 Error::OAuth(Box::new(error))
521 }
522}
523
524impl From<EventCacheError> for Error {
525 fn from(error: EventCacheError) -> Self {
526 Error::EventCache(Box::new(error))
527 }
528}
529
530impl From<QueueWedgeError> for Error {
531 fn from(error: QueueWedgeError) -> Self {
532 Error::SendQueueWedgeError(Box::new(error))
533 }
534}
535
536#[cfg(feature = "e2e-encryption")]
537impl From<BootstrapCrossSigningError> for Error {
538 fn from(error: BootstrapCrossSigningError) -> Self {
539 match error {
540 BootstrapCrossSigningError::CryptoStore(e) => e.into(),
541 BootstrapCrossSigningError::Signature(e) => e.into(),
542 }
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(Into::into)))
577 }
578}
579
580impl From<SdkBaseError> for Error {
581 fn from(e: SdkBaseError) -> Self {
582 match e {
583 SdkBaseError::StateStore(e) => Self::StateStore(Box::new(e)),
584 #[cfg(feature = "e2e-encryption")]
585 SdkBaseError::CryptoStore(e) => Self::CryptoStoreError(Box::new(e)),
586 #[cfg(feature = "e2e-encryption")]
587 SdkBaseError::BadCryptoStoreState => Self::BadCryptoStoreState,
588 #[cfg(feature = "e2e-encryption")]
589 SdkBaseError::OlmError(e) => Self::OlmError(Box::new(e)),
590 #[cfg(feature = "eyre")]
591 _ => Self::UnknownError(eyre::eyre!(e).into()),
592 #[cfg(all(not(feature = "eyre"), feature = "anyhow", not(target_family = "wasm")))]
593 _ => Self::UnknownError(anyhow::anyhow!(e).into()),
594 #[cfg(all(not(feature = "eyre"), feature = "anyhow", target_family = "wasm"))]
595 _ => Self::UnknownError(e.into()),
596 #[cfg(all(
597 not(feature = "eyre"),
598 not(feature = "anyhow"),
599 not(target_family = "wasm")
600 ))]
601 _ => {
602 let e: Box<dyn std::error::Error + Send + Sync> = format!("{e:?}").into();
603 Self::UnknownError(e)
604 }
605 #[cfg(all(not(feature = "eyre"), not(feature = "anyhow"), target_family = "wasm"))]
606 _ => {
607 let e: Box<dyn std::error::Error> = format!("{e:?}").into();
608 Self::UnknownError(e)
609 }
610 }
611 }
612}
613
614impl From<ReqwestError> for Error {
615 fn from(e: ReqwestError) -> Self {
616 Error::Http(Box::new(HttpError::Reqwest(e)))
617 }
618}
619
620#[derive(Debug, Error)]
622pub enum BeaconError {
623 #[error("Network error: {0}")]
625 Network(#[from] HttpError),
626
627 #[error("Existing beacon information not found.")]
629 NotFound,
630
631 #[error("Beacon event is redacted and cannot be processed.")]
633 Redacted,
634
635 #[error("Must join the room to access beacon information.")]
637 Stripped,
638
639 #[error("Deserialization error: {0}")]
641 Deserialization(#[from] serde_json::Error),
642
643 #[error("The beacon event has expired.")]
645 NotLive,
646
647 #[error("Other error: {0}")]
649 Other(Box<Error>),
650}
651
652impl From<Error> for BeaconError {
653 fn from(err: Error) -> Self {
654 BeaconError::Other(Box::new(err))
655 }
656}
657
658#[derive(Debug, Error, Clone)]
666pub enum RefreshTokenError {
667 #[error("missing refresh token")]
669 RefreshTokenRequired,
670
671 #[error(transparent)]
673 MatrixAuth(Arc<HttpError>),
674
675 #[error(transparent)]
677 OAuth(#[from] Arc<OAuthError>),
678}
679
680#[derive(Debug, Error, Clone, PartialEq)]
682pub enum NotificationSettingsError {
683 #[error("Invalid parameter `{0}`")]
685 InvalidParameter(String),
686 #[error("Unable to add push rule")]
688 UnableToAddPushRule,
689 #[error("Unable to remove push rule")]
691 UnableToRemovePushRule,
692 #[error("Unable to update push rule")]
694 UnableToUpdatePushRule,
695 #[error("Rule `{0}` not found")]
697 RuleNotFound(String),
698 #[error("Unable to save push rules")]
700 UnableToSavePushRules,
701}
702
703impl NotificationSettingsError {
704 pub fn is_rule_not_found(&self) -> bool {
706 matches!(self, Self::RuleNotFound(_))
707 }
708}
709
710impl From<InsertPushRuleError> for NotificationSettingsError {
711 fn from(_: InsertPushRuleError) -> Self {
712 Self::UnableToAddPushRule
713 }
714}
715
716impl From<RemovePushRuleError> for NotificationSettingsError {
717 fn from(_: RemovePushRuleError) -> Self {
718 Self::UnableToRemovePushRule
719 }
720}
721
722#[derive(Debug, Error)]
723#[error("expected: {expected}, got: {got:?}")]
724pub struct WrongRoomState {
725 expected: &'static str,
726 got: RoomState,
727}
728
729impl WrongRoomState {
730 pub(crate) fn new(expected: &'static str, got: RoomState) -> Self {
731 Self { expected, got }
732 }
733}