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::{room::power_levels::PowerLevelsError, 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 #[error("power levels error: {0}")]
421 PowerLevels(#[from] PowerLevelsError),
422}
423
424#[rustfmt::skip] impl Error {
426 pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
432 as_variant!(self, Self::Http).and_then(|e| e.as_ruma_api_error())
433 }
434
435 pub fn as_client_api_error(&self) -> Option<&ruma::api::client::Error> {
438 self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
439 }
440
441 pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
444 self.as_client_api_error().and_then(|e| {
445 as_variant!(&e.body, ErrorBody::Standard { kind, .. } => kind)
446 })
447 }
448
449 pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
461 self.as_ruma_api_error().and_then(as_variant!(RumaApiError::Uiaa))
462 }
463}
464
465impl From<HttpError> for Error {
466 fn from(error: HttpError) -> Self {
467 Error::Http(Box::new(error))
468 }
469}
470
471#[cfg(feature = "e2e-encryption")]
472impl From<CryptoStoreError> for Error {
473 fn from(error: CryptoStoreError) -> Self {
474 Error::CryptoStoreError(Box::new(error))
475 }
476}
477
478impl From<LockStoreError> for Error {
479 fn from(error: LockStoreError) -> Self {
480 Error::CrossProcessLockError(Box::new(error))
481 }
482}
483
484#[cfg(feature = "e2e-encryption")]
485impl From<OlmError> for Error {
486 fn from(error: OlmError) -> Self {
487 Error::OlmError(Box::new(error))
488 }
489}
490
491#[cfg(feature = "e2e-encryption")]
492impl From<MegolmError> for Error {
493 fn from(error: MegolmError) -> Self {
494 Error::MegolmError(Box::new(error))
495 }
496}
497
498impl From<StoreError> for Error {
499 fn from(error: StoreError) -> Self {
500 Error::StateStore(Box::new(error))
501 }
502}
503
504impl From<EventCacheStoreError> for Error {
505 fn from(error: EventCacheStoreError) -> Self {
506 Error::EventCacheStore(Box::new(error))
507 }
508}
509
510#[cfg(feature = "qrcode")]
511impl From<ScanError> for Error {
512 fn from(error: ScanError) -> Self {
513 Error::QrCodeScanError(Box::new(error))
514 }
515}
516
517impl From<SlidingSyncError> for Error {
518 fn from(error: SlidingSyncError) -> Self {
519 Error::SlidingSync(Box::new(error))
520 }
521}
522
523impl From<OAuthError> for Error {
524 fn from(error: OAuthError) -> Self {
525 Error::OAuth(Box::new(error))
526 }
527}
528
529impl From<EventCacheError> for Error {
530 fn from(error: EventCacheError) -> Self {
531 Error::EventCache(Box::new(error))
532 }
533}
534
535impl From<QueueWedgeError> for Error {
536 fn from(error: QueueWedgeError) -> Self {
537 Error::SendQueueWedgeError(Box::new(error))
538 }
539}
540
541#[cfg(feature = "e2e-encryption")]
543#[derive(Error, Debug)]
544#[allow(dead_code)]
546pub enum RoomKeyImportError {
547 #[error(transparent)]
549 SerdeJson(#[from] JsonError),
550
551 #[error("The crypto store hasn't been yet opened, can't import yet.")]
554 StoreClosed,
555
556 #[error(transparent)]
558 Io(#[from] IoError),
559
560 #[error(transparent)]
562 CryptoStore(#[from] CryptoStoreError),
563
564 #[error(transparent)]
566 Export(#[from] KeyExportError),
567}
568
569impl From<FromHttpResponseError<ruma::api::client::Error>> for HttpError {
570 fn from(err: FromHttpResponseError<ruma::api::client::Error>) -> Self {
571 Self::Api(Box::new(err.map(RumaApiError::ClientApi)))
572 }
573}
574
575impl From<FromHttpResponseError<UiaaResponse>> for HttpError {
576 fn from(err: FromHttpResponseError<UiaaResponse>) -> Self {
577 Self::Api(Box::new(err.map(|e| match e {
578 UiaaResponse::AuthResponse(i) => RumaApiError::Uiaa(i),
579 UiaaResponse::MatrixError(e) => RumaApiError::ClientApi(e),
580 })))
581 }
582}
583
584impl From<FromHttpResponseError<ruma::api::error::MatrixError>> for HttpError {
585 fn from(err: FromHttpResponseError<ruma::api::error::MatrixError>) -> Self {
586 Self::Api(Box::new(err.map(RumaApiError::Other)))
587 }
588}
589
590impl From<SdkBaseError> for Error {
591 fn from(e: SdkBaseError) -> Self {
592 match e {
593 SdkBaseError::StateStore(e) => Self::StateStore(Box::new(e)),
594 #[cfg(feature = "e2e-encryption")]
595 SdkBaseError::CryptoStore(e) => Self::CryptoStoreError(Box::new(e)),
596 #[cfg(feature = "e2e-encryption")]
597 SdkBaseError::BadCryptoStoreState => Self::BadCryptoStoreState,
598 #[cfg(feature = "e2e-encryption")]
599 SdkBaseError::OlmError(e) => Self::OlmError(Box::new(e)),
600 #[cfg(feature = "eyre")]
601 _ => Self::UnknownError(eyre::eyre!(e).into()),
602 #[cfg(all(not(feature = "eyre"), feature = "anyhow", not(target_family = "wasm")))]
603 _ => Self::UnknownError(anyhow::anyhow!(e).into()),
604 #[cfg(all(not(feature = "eyre"), feature = "anyhow", target_family = "wasm"))]
605 _ => Self::UnknownError(e.into()),
606 #[cfg(all(
607 not(feature = "eyre"),
608 not(feature = "anyhow"),
609 not(target_family = "wasm")
610 ))]
611 _ => {
612 let e: Box<dyn std::error::Error + Send + Sync> = format!("{e:?}").into();
613 Self::UnknownError(e)
614 }
615 #[cfg(all(not(feature = "eyre"), not(feature = "anyhow"), target_family = "wasm"))]
616 _ => {
617 let e: Box<dyn std::error::Error> = format!("{e:?}").into();
618 Self::UnknownError(e)
619 }
620 }
621 }
622}
623
624impl From<ReqwestError> for Error {
625 fn from(e: ReqwestError) -> Self {
626 Error::Http(Box::new(HttpError::Reqwest(e)))
627 }
628}
629
630#[derive(Debug, Error)]
632pub enum BeaconError {
633 #[error("Network error: {0}")]
635 Network(#[from] HttpError),
636
637 #[error("Existing beacon information not found.")]
639 NotFound,
640
641 #[error("Beacon event is redacted and cannot be processed.")]
643 Redacted,
644
645 #[error("Must join the room to access beacon information.")]
647 Stripped,
648
649 #[error("Deserialization error: {0}")]
651 Deserialization(#[from] serde_json::Error),
652
653 #[error("The beacon event has expired.")]
655 NotLive,
656
657 #[error("Other error: {0}")]
659 Other(Box<Error>),
660}
661
662impl From<Error> for BeaconError {
663 fn from(err: Error) -> Self {
664 BeaconError::Other(Box::new(err))
665 }
666}
667
668#[derive(Debug, Error, Clone)]
676pub enum RefreshTokenError {
677 #[error("missing refresh token")]
679 RefreshTokenRequired,
680
681 #[error(transparent)]
683 MatrixAuth(Arc<HttpError>),
684
685 #[error(transparent)]
687 OAuth(#[from] Arc<OAuthError>),
688}
689
690#[derive(Debug, Error, Clone, PartialEq)]
692pub enum NotificationSettingsError {
693 #[error("Invalid parameter `{0}`")]
695 InvalidParameter(String),
696 #[error("Unable to add push rule")]
698 UnableToAddPushRule,
699 #[error("Unable to remove push rule")]
701 UnableToRemovePushRule,
702 #[error("Unable to update push rule")]
704 UnableToUpdatePushRule,
705 #[error("Rule `{0}` not found")]
707 RuleNotFound(String),
708 #[error("Unable to save push rules")]
710 UnableToSavePushRules,
711}
712
713impl NotificationSettingsError {
714 pub fn is_rule_not_found(&self) -> bool {
716 matches!(self, Self::RuleNotFound(_))
717 }
718}
719
720impl From<InsertPushRuleError> for NotificationSettingsError {
721 fn from(_: InsertPushRuleError) -> Self {
722 Self::UnableToAddPushRule
723 }
724}
725
726impl From<RemovePushRuleError> for NotificationSettingsError {
727 fn from(_: RemovePushRuleError) -> Self {
728 Self::UnableToRemovePushRule
729 }
730}
731
732#[derive(Debug, Error)]
733#[error("expected: {expected}, got: {got:?}")]
734pub struct WrongRoomState {
735 expected: &'static str,
736 got: RoomState,
737}
738
739impl WrongRoomState {
740 pub(crate) fn new(expected: &'static str, got: RoomState) -> Self {
741 Self { expected, got }
742 }
743}