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 event_cache::store::EventCacheStoreError, Error as SdkBaseError, QueueWedgeError, RoomState,
29 StoreError,
30};
31use reqwest::Error as ReqwestError;
32use ruma::{
33 api::{
34 client::{
35 error::{ErrorBody, ErrorKind, RetryAfter},
36 uiaa::{UiaaInfo, UiaaResponse},
37 },
38 error::{FromHttpResponseError, IntoHttpError},
39 },
40 events::tag::InvalidUserTagName,
41 push::{InsertPushRuleError, RemovePushRuleError},
42 IdParseError,
43};
44use serde_json::Error as JsonError;
45use thiserror::Error;
46use url::ParseError as UrlParseError;
47
48use crate::{
49 authentication::oauth::OAuthError, event_cache::EventCacheError, media::MediaError,
50 room::reply::ReplyError, sliding_sync::Error as SlidingSyncError, store_locks::LockStoreError,
51};
52
53pub type Result<T, E = Error> = std::result::Result<T, E>;
55
56pub type HttpResult<T> = std::result::Result<T, HttpError>;
58
59#[derive(Error, Debug)]
62pub enum RumaApiError {
63 #[error(transparent)]
65 ClientApi(ruma::api::client::Error),
66
67 #[error("User-Interactive Authentication required.")]
74 Uiaa(UiaaInfo),
75
76 #[error(transparent)]
78 Other(ruma::api::error::MatrixError),
79}
80
81impl RumaApiError {
82 pub fn as_client_api_error(&self) -> Option<&ruma::api::client::Error> {
86 as_variant!(self, Self::ClientApi)
87 }
88}
89
90#[derive(Error, Debug)]
93pub enum HttpError {
94 #[error(transparent)]
96 Reqwest(#[from] ReqwestError),
97
98 #[error("the queried endpoint is not meant for clients")]
100 NotClientRequest,
101
102 #[error(transparent)]
105 Api(#[from] Box<FromHttpResponseError<RumaApiError>>),
106
107 #[error(transparent)]
110 IntoHttp(IntoHttpError),
111
112 #[error(transparent)]
114 RefreshToken(RefreshTokenError),
115}
116
117#[rustfmt::skip] impl HttpError {
119 pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
125 match self {
126 Self::Api(error) => {
127 as_variant!(error.as_ref(), FromHttpResponseError::Server)
128 },
129 _ => None
130 }
131 }
132
133 pub fn as_client_api_error(&self) -> Option<&ruma::api::client::Error> {
136 self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
137 }
138}
139
140impl HttpError {
142 pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
145 self.as_client_api_error()
146 .and_then(|e| as_variant!(&e.body, ErrorBody::Standard { kind, .. } => kind))
147 }
148
149 pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
161 self.as_ruma_api_error().and_then(as_variant!(RumaApiError::Uiaa))
162 }
163
164 pub(crate) fn retry_kind(&self) -> RetryKind {
167 match self {
168 HttpError::Reqwest(_) => RetryKind::NetworkFailure,
171
172 HttpError::Api(error) => match error.as_ref() {
173 FromHttpResponseError::Server(api_error) => RetryKind::from_api_error(api_error),
174 _ => RetryKind::Permanent,
175 },
176 _ => RetryKind::Permanent,
177 }
178 }
179}
180
181impl From<FromHttpResponseError<RumaApiError>> for HttpError {
182 fn from(value: FromHttpResponseError<RumaApiError>) -> Self {
183 Self::Api(Box::new(value))
184 }
185}
186
187pub(crate) enum RetryKind {
190 NetworkFailure,
192
193 Transient {
197 #[cfg_attr(target_family = "wasm", allow(dead_code))]
199 retry_after: Option<Duration>,
200 },
201
202 Permanent,
205}
206
207impl RetryKind {
208 fn from_api_error(api_error: &RumaApiError) -> Self {
215 use ruma::api::client::Error;
216
217 match api_error {
218 RumaApiError::ClientApi(client_error) => {
219 let Error { status_code, body, .. } = client_error;
220
221 match body {
222 ErrorBody::Standard { kind, .. } => match kind {
223 ErrorKind::LimitExceeded { retry_after } => {
224 RetryKind::from_retry_after(retry_after.as_ref())
225 }
226 ErrorKind::Unrecognized => RetryKind::Permanent,
227 _ => RetryKind::from_status_code(*status_code),
228 },
229 _ => RetryKind::from_status_code(*status_code),
230 }
231 }
232 RumaApiError::Other(e) => RetryKind::from_status_code(e.status_code),
233 RumaApiError::Uiaa(_) => RetryKind::Permanent,
234 }
235 }
236
237 fn from_retry_after(retry_after: Option<&RetryAfter>) -> Self {
243 let retry_after = retry_after
244 .and_then(|retry_after| match retry_after {
245 RetryAfter::Delay(d) => Some(d),
246 RetryAfter::DateTime(_) => None,
247 })
248 .copied();
249
250 Self::Transient { retry_after }
251 }
252
253 fn from_status_code(status_code: StatusCode) -> Self {
260 if status_code.as_u16() == 520 {
261 RetryKind::Permanent
264 } else if status_code == StatusCode::TOO_MANY_REQUESTS || status_code.is_server_error() {
265 RetryKind::Transient { retry_after: None }
269 } else {
270 RetryKind::Permanent
271 }
272 }
273}
274
275#[derive(Error, Debug)]
277#[non_exhaustive]
278pub enum Error {
279 #[error(transparent)]
281 Http(Box<HttpError>),
282
283 #[error("the queried endpoint requires authentication but was called before logging in")]
286 AuthenticationRequired,
287
288 #[error("Local cache doesn't contain all necessary data to perform the action.")]
290 InsufficientData,
291
292 #[cfg(feature = "e2e-encryption")]
295 #[error("The olm machine has already been initialized")]
296 BadCryptoStoreState,
297
298 #[cfg(feature = "e2e-encryption")]
300 #[error("The olm machine isn't yet available")]
301 NoOlmMachine,
302
303 #[error(transparent)]
305 SerdeJson(#[from] JsonError),
306
307 #[error(transparent)]
309 Io(#[from] IoError),
310
311 #[cfg(feature = "e2e-encryption")]
313 #[error(transparent)]
314 CryptoStoreError(Box<CryptoStoreError>),
315
316 #[error(transparent)]
318 CrossProcessLockError(Box<LockStoreError>),
319
320 #[cfg(feature = "e2e-encryption")]
322 #[error(transparent)]
323 OlmError(Box<OlmError>),
324
325 #[cfg(feature = "e2e-encryption")]
327 #[error(transparent)]
328 MegolmError(Box<MegolmError>),
329
330 #[cfg(feature = "e2e-encryption")]
332 #[error(transparent)]
333 DecryptorError(#[from] DecryptorError),
334
335 #[error(transparent)]
337 StateStore(Box<StoreError>),
338
339 #[error(transparent)]
341 EventCacheStore(Box<EventCacheStoreError>),
342
343 #[error(transparent)]
345 Identifier(#[from] IdParseError),
346
347 #[error(transparent)]
349 Url(#[from] UrlParseError),
350
351 #[cfg(feature = "qrcode")]
353 #[error(transparent)]
354 QrCodeScanError(Box<ScanError>),
355
356 #[error(transparent)]
358 UserTagName(#[from] InvalidUserTagName),
359
360 #[error(transparent)]
362 SlidingSync(Box<SlidingSyncError>),
363
364 #[error("wrong room state: {0}")]
368 WrongRoomState(Box<WrongRoomState>),
369
370 #[error("session callbacks have been set multiple times")]
372 MultipleSessionCallbacks,
373
374 #[error(transparent)]
376 OAuth(Box<OAuthError>),
377
378 #[error("a concurrent request failed; see logs for details")]
380 ConcurrentRequestFailed,
381
382 #[cfg(not(target_family = "wasm"))]
387 #[error("unknown error: {0}")]
388 UnknownError(Box<dyn std::error::Error + Send + Sync>),
389
390 #[cfg(target_family = "wasm")]
392 #[error("unknown error: {0}")]
393 UnknownError(Box<dyn std::error::Error>),
394
395 #[error(transparent)]
397 EventCache(Box<EventCacheError>),
398
399 #[error(transparent)]
401 SendQueueWedgeError(Box<QueueWedgeError>),
402
403 #[error("backups are not enabled")]
405 BackupNotEnabled,
406
407 #[error("can't ignore the logged-in user")]
409 CantIgnoreLoggedInUser,
410
411 #[error(transparent)]
413 Media(#[from] MediaError),
414
415 #[error(transparent)]
417 ReplyError(#[from] ReplyError),
418}
419
420#[rustfmt::skip] impl Error {
422 pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
428 as_variant!(self, Self::Http).and_then(|e| e.as_ruma_api_error())
429 }
430
431 pub fn as_client_api_error(&self) -> Option<&ruma::api::client::Error> {
434 self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
435 }
436
437 pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
440 self.as_client_api_error().and_then(|e| {
441 as_variant!(&e.body, ErrorBody::Standard { kind, .. } => kind)
442 })
443 }
444
445 pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
457 self.as_ruma_api_error().and_then(as_variant!(RumaApiError::Uiaa))
458 }
459}
460
461impl From<HttpError> for Error {
462 fn from(error: HttpError) -> Self {
463 Error::Http(Box::new(error))
464 }
465}
466
467#[cfg(feature = "e2e-encryption")]
468impl From<CryptoStoreError> for Error {
469 fn from(error: CryptoStoreError) -> Self {
470 Error::CryptoStoreError(Box::new(error))
471 }
472}
473
474impl From<LockStoreError> for Error {
475 fn from(error: LockStoreError) -> Self {
476 Error::CrossProcessLockError(Box::new(error))
477 }
478}
479
480#[cfg(feature = "e2e-encryption")]
481impl From<OlmError> for Error {
482 fn from(error: OlmError) -> Self {
483 Error::OlmError(Box::new(error))
484 }
485}
486
487#[cfg(feature = "e2e-encryption")]
488impl From<MegolmError> for Error {
489 fn from(error: MegolmError) -> Self {
490 Error::MegolmError(Box::new(error))
491 }
492}
493
494impl From<StoreError> for Error {
495 fn from(error: StoreError) -> Self {
496 Error::StateStore(Box::new(error))
497 }
498}
499
500impl From<EventCacheStoreError> for Error {
501 fn from(error: EventCacheStoreError) -> Self {
502 Error::EventCacheStore(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")]
539#[derive(Error, Debug)]
540#[allow(dead_code)]
542pub enum RoomKeyImportError {
543 #[error(transparent)]
545 SerdeJson(#[from] JsonError),
546
547 #[error("The crypto store hasn't been yet opened, can't import yet.")]
550 StoreClosed,
551
552 #[error(transparent)]
554 Io(#[from] IoError),
555
556 #[error(transparent)]
558 CryptoStore(#[from] CryptoStoreError),
559
560 #[error(transparent)]
562 Export(#[from] KeyExportError),
563}
564
565impl From<FromHttpResponseError<ruma::api::client::Error>> for HttpError {
566 fn from(err: FromHttpResponseError<ruma::api::client::Error>) -> Self {
567 Self::Api(Box::new(err.map(RumaApiError::ClientApi)))
568 }
569}
570
571impl From<FromHttpResponseError<UiaaResponse>> for HttpError {
572 fn from(err: FromHttpResponseError<UiaaResponse>) -> Self {
573 Self::Api(Box::new(err.map(|e| match e {
574 UiaaResponse::AuthResponse(i) => RumaApiError::Uiaa(i),
575 UiaaResponse::MatrixError(e) => RumaApiError::ClientApi(e),
576 })))
577 }
578}
579
580impl From<FromHttpResponseError<ruma::api::error::MatrixError>> for HttpError {
581 fn from(err: FromHttpResponseError<ruma::api::error::MatrixError>) -> Self {
582 Self::Api(Box::new(err.map(RumaApiError::Other)))
583 }
584}
585
586impl From<SdkBaseError> for Error {
587 fn from(e: SdkBaseError) -> Self {
588 match e {
589 SdkBaseError::StateStore(e) => Self::StateStore(Box::new(e)),
590 #[cfg(feature = "e2e-encryption")]
591 SdkBaseError::CryptoStore(e) => Self::CryptoStoreError(Box::new(e)),
592 #[cfg(feature = "e2e-encryption")]
593 SdkBaseError::BadCryptoStoreState => Self::BadCryptoStoreState,
594 #[cfg(feature = "e2e-encryption")]
595 SdkBaseError::OlmError(e) => Self::OlmError(Box::new(e)),
596 #[cfg(feature = "eyre")]
597 _ => Self::UnknownError(eyre::eyre!(e).into()),
598 #[cfg(all(not(feature = "eyre"), feature = "anyhow", not(target_family = "wasm")))]
599 _ => Self::UnknownError(anyhow::anyhow!(e).into()),
600 #[cfg(all(not(feature = "eyre"), feature = "anyhow", target_family = "wasm"))]
601 _ => Self::UnknownError(e.into()),
602 #[cfg(all(
603 not(feature = "eyre"),
604 not(feature = "anyhow"),
605 not(target_family = "wasm")
606 ))]
607 _ => {
608 let e: Box<dyn std::error::Error + Send + Sync> = format!("{e:?}").into();
609 Self::UnknownError(e)
610 }
611 #[cfg(all(not(feature = "eyre"), not(feature = "anyhow"), target_family = "wasm"))]
612 _ => {
613 let e: Box<dyn std::error::Error> = format!("{e:?}").into();
614 Self::UnknownError(e)
615 }
616 }
617 }
618}
619
620impl From<ReqwestError> for Error {
621 fn from(e: ReqwestError) -> Self {
622 Error::Http(Box::new(HttpError::Reqwest(e)))
623 }
624}
625
626#[derive(Debug, Error)]
628pub enum BeaconError {
629 #[error("Network error: {0}")]
631 Network(#[from] HttpError),
632
633 #[error("Existing beacon information not found.")]
635 NotFound,
636
637 #[error("Beacon event is redacted and cannot be processed.")]
639 Redacted,
640
641 #[error("Must join the room to access beacon information.")]
643 Stripped,
644
645 #[error("Deserialization error: {0}")]
647 Deserialization(#[from] serde_json::Error),
648
649 #[error("The beacon event has expired.")]
651 NotLive,
652
653 #[error("Other error: {0}")]
655 Other(Box<Error>),
656}
657
658impl From<Error> for BeaconError {
659 fn from(err: Error) -> Self {
660 BeaconError::Other(Box::new(err))
661 }
662}
663
664#[derive(Debug, Error, Clone)]
672pub enum RefreshTokenError {
673 #[error("missing refresh token")]
675 RefreshTokenRequired,
676
677 #[error(transparent)]
679 MatrixAuth(Arc<HttpError>),
680
681 #[error(transparent)]
683 OAuth(#[from] Arc<OAuthError>),
684}
685
686#[derive(Debug, Error, Clone, PartialEq)]
688pub enum NotificationSettingsError {
689 #[error("Invalid parameter `{0}`")]
691 InvalidParameter(String),
692 #[error("Unable to add push rule")]
694 UnableToAddPushRule,
695 #[error("Unable to remove push rule")]
697 UnableToRemovePushRule,
698 #[error("Unable to update push rule")]
700 UnableToUpdatePushRule,
701 #[error("Rule `{0}` not found")]
703 RuleNotFound(String),
704 #[error("Unable to save push rules")]
706 UnableToSavePushRules,
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}