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 Error as SdkBaseError, QueueWedgeError, RoomState, StoreError,
29 cross_process_lock::CrossProcessLockUnobtained, event_cache::store::EventCacheStoreError,
30 media::store::MediaStoreError,
31};
32use reqwest::Error as ReqwestError;
33use ruma::{
34 IdParseError,
35 api::{
36 client::uiaa::{UiaaInfo, UiaaResponse},
37 error::{ErrorKind, FromHttpResponseError, IntoHttpError, RetryAfter},
38 },
39 events::{room::power_levels::PowerLevelsError, tag::InvalidUserTagName},
40 push::{InsertPushRuleError, RemovePushRuleError},
41};
42#[cfg(target_os = "android")]
43use rustls::client::VerifierBuilderError;
44use serde_json::Error as JsonError;
45use thiserror::Error;
46use url::ParseError as UrlParseError;
47
48use crate::{
49 authentication::oauth::OAuthError, cross_process_lock::CrossProcessLockError,
50 event_cache::EventCacheError, media::MediaError, room::reply::ReplyError,
51 sliding_sync::Error as SlidingSyncError,
52};
53
54pub type Result<T, E = Error> = std::result::Result<T, E>;
56
57pub type HttpResult<T> = std::result::Result<T, HttpError>;
59
60pub type RumaApiError = UiaaResponse;
63
64#[derive(Error, Debug)]
67pub enum HttpError {
68 #[error(transparent)]
70 Reqwest(#[from] ReqwestError),
71
72 #[error(transparent)]
75 Api(#[from] Box<FromHttpResponseError<RumaApiError>>),
76
77 #[error(transparent)]
80 IntoHttp(IntoHttpError),
81
82 #[error(transparent)]
84 RefreshToken(RefreshTokenError),
85
86 #[error(transparent)]
91 Cached(Arc<HttpError>),
92
93 #[cfg(target_os = "android")]
95 #[error(transparent)]
96 VerifierBuilder(#[from] VerifierBuilderError),
97}
98
99#[rustfmt::skip] impl HttpError {
101 pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
107 match self {
108 Self::Api(error) => {
109 as_variant!(error.as_ref(), FromHttpResponseError::Server)
110 },
111 _ => None
112 }
113 }
114}
115
116impl HttpError {
118 pub fn as_client_api_error(&self) -> Option<&ruma::api::error::Error> {
121 self.as_ruma_api_error().and_then(as_variant!(UiaaResponse::MatrixError))
122 }
123
124 pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
127 self.as_client_api_error().and_then(ruma::api::error::Error::error_kind)
128 }
129
130 pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
142 self.as_ruma_api_error().and_then(as_variant!(UiaaResponse::AuthResponse))
143 }
144
145 pub(crate) fn retry_kind(&self) -> RetryKind {
148 match self {
149 HttpError::Reqwest(_) => RetryKind::NetworkFailure,
152
153 HttpError::Api(error) => match error.as_ref() {
154 FromHttpResponseError::Server(api_error) => RetryKind::from_api_error(api_error),
155 _ => RetryKind::Permanent,
156 },
157 _ => RetryKind::Permanent,
158 }
159 }
160
161 pub fn is_endpoint_not_implemented(&self) -> bool {
164 self.as_client_api_error().is_some_and(|error| error.is_endpoint_not_implemented())
165 }
166}
167
168impl From<FromHttpResponseError<RumaApiError>> for HttpError {
169 fn from(value: FromHttpResponseError<RumaApiError>) -> Self {
170 Self::Api(Box::new(value))
171 }
172}
173
174pub(crate) enum RetryKind {
177 NetworkFailure,
179
180 Transient {
184 #[cfg_attr(target_family = "wasm", allow(dead_code))]
186 retry_after: Option<Duration>,
187 },
188
189 Permanent,
192}
193
194impl RetryKind {
195 fn from_api_error(api_error: &UiaaResponse) -> Self {
202 match api_error {
203 UiaaResponse::MatrixError(client_error) => match client_error.error_kind() {
204 Some(ErrorKind::LimitExceeded(limit_exceeded)) => {
205 RetryKind::from_retry_after(limit_exceeded.retry_after.as_ref())
206 }
207 Some(ErrorKind::Unrecognized) => RetryKind::Permanent,
208 _ => RetryKind::from_status_code(client_error.status_code),
209 },
210 UiaaResponse::AuthResponse(_) => RetryKind::Permanent,
211 }
212 }
213
214 fn from_retry_after(retry_after: Option<&RetryAfter>) -> Self {
220 let retry_after = retry_after
221 .and_then(|retry_after| match retry_after {
222 RetryAfter::Delay(d) => Some(d),
223 RetryAfter::DateTime(_) => None,
224 })
225 .copied();
226
227 Self::Transient { retry_after }
228 }
229
230 fn from_status_code(status_code: StatusCode) -> Self {
237 if status_code.as_u16() == 520 {
238 RetryKind::Permanent
241 } else if status_code == StatusCode::TOO_MANY_REQUESTS || status_code.is_server_error() {
242 RetryKind::Transient { retry_after: None }
246 } else {
247 RetryKind::Permanent
248 }
249 }
250}
251
252#[derive(Error, Debug)]
254#[non_exhaustive]
255pub enum Error {
256 #[error(transparent)]
258 Http(Box<HttpError>),
259
260 #[error("the queried endpoint requires authentication but was called before logging in")]
263 AuthenticationRequired,
264
265 #[error("Local cache doesn't contain all necessary data to perform the action.")]
267 InsufficientData,
268
269 #[cfg(feature = "e2e-encryption")]
272 #[error("The olm machine has already been initialized")]
273 BadCryptoStoreState,
274
275 #[cfg(feature = "e2e-encryption")]
277 #[error("The olm machine isn't yet available")]
278 NoOlmMachine,
279
280 #[error(transparent)]
282 SerdeJson(#[from] JsonError),
283
284 #[error(transparent)]
286 Io(#[from] IoError),
287
288 #[cfg(feature = "e2e-encryption")]
290 #[error(transparent)]
291 CryptoStoreError(Box<CryptoStoreError>),
292
293 #[error(transparent)]
295 CrossProcessLockError(Box<CrossProcessLockError>),
296
297 #[cfg(feature = "e2e-encryption")]
299 #[error(transparent)]
300 OlmError(Box<OlmError>),
301
302 #[cfg(feature = "e2e-encryption")]
304 #[error(transparent)]
305 MegolmError(Box<MegolmError>),
306
307 #[cfg(feature = "e2e-encryption")]
309 #[error(transparent)]
310 DecryptorError(#[from] DecryptorError),
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")]
538#[derive(Error, Debug)]
539#[allow(dead_code)]
541pub enum RoomKeyImportError {
542 #[error(transparent)]
544 SerdeJson(#[from] JsonError),
545
546 #[error("The crypto store hasn't been yet opened, can't import yet.")]
549 StoreClosed,
550
551 #[error(transparent)]
553 Io(#[from] IoError),
554
555 #[error(transparent)]
557 CryptoStore(#[from] CryptoStoreError),
558
559 #[error(transparent)]
561 Export(#[from] KeyExportError),
562}
563
564impl From<FromHttpResponseError<ruma::api::error::Error>> for HttpError {
565 fn from(err: FromHttpResponseError<ruma::api::error::Error>) -> Self {
566 Self::Api(Box::new(err.map(Into::into)))
567 }
568}
569
570impl From<SdkBaseError> for Error {
571 fn from(e: SdkBaseError) -> Self {
572 match e {
573 SdkBaseError::StateStore(e) => Self::StateStore(Box::new(e)),
574 #[cfg(feature = "e2e-encryption")]
575 SdkBaseError::CryptoStore(e) => Self::CryptoStoreError(Box::new(e)),
576 #[cfg(feature = "e2e-encryption")]
577 SdkBaseError::BadCryptoStoreState => Self::BadCryptoStoreState,
578 #[cfg(feature = "e2e-encryption")]
579 SdkBaseError::OlmError(e) => Self::OlmError(Box::new(e)),
580 #[cfg(feature = "eyre")]
581 _ => Self::UnknownError(eyre::eyre!(e).into()),
582 #[cfg(all(not(feature = "eyre"), feature = "anyhow", not(target_family = "wasm")))]
583 _ => Self::UnknownError(anyhow::anyhow!(e).into()),
584 #[cfg(all(not(feature = "eyre"), feature = "anyhow", target_family = "wasm"))]
585 _ => Self::UnknownError(e.into()),
586 #[cfg(all(
587 not(feature = "eyre"),
588 not(feature = "anyhow"),
589 not(target_family = "wasm")
590 ))]
591 _ => {
592 let e: Box<dyn std::error::Error + Send + Sync> = format!("{e:?}").into();
593 Self::UnknownError(e)
594 }
595 #[cfg(all(not(feature = "eyre"), not(feature = "anyhow"), target_family = "wasm"))]
596 _ => {
597 let e: Box<dyn std::error::Error> = format!("{e:?}").into();
598 Self::UnknownError(e)
599 }
600 }
601 }
602}
603
604impl From<ReqwestError> for Error {
605 fn from(e: ReqwestError) -> Self {
606 Error::Http(Box::new(HttpError::Reqwest(e)))
607 }
608}
609
610#[derive(Debug, Error)]
612pub enum BeaconError {
613 #[error("Network error: {0}")]
615 Network(#[from] HttpError),
616
617 #[error("Existing beacon information not found.")]
619 NotFound,
620
621 #[error("Beacon event is redacted and cannot be processed.")]
623 Redacted,
624
625 #[error("Must join the room to access beacon information.")]
627 Stripped,
628
629 #[error("Deserialization error: {0}")]
631 Deserialization(#[from] serde_json::Error),
632
633 #[error("The beacon event has expired.")]
635 NotLive,
636
637 #[error("Other error: {0}")]
639 Other(Box<Error>),
640}
641
642impl From<Error> for BeaconError {
643 fn from(err: Error) -> Self {
644 BeaconError::Other(Box::new(err))
645 }
646}
647
648#[derive(Debug, Error, Clone)]
656pub enum RefreshTokenError {
657 #[error("missing refresh token")]
659 RefreshTokenRequired,
660
661 #[error(transparent)]
663 MatrixAuth(Arc<HttpError>),
664
665 #[error(transparent)]
667 OAuth(#[from] Arc<OAuthError>),
668}
669
670#[derive(Debug, Error, Clone, PartialEq)]
672pub enum NotificationSettingsError {
673 #[error("Invalid parameter `{0}`")]
675 InvalidParameter(String),
676 #[error("Unable to add push rule")]
678 UnableToAddPushRule,
679 #[error("Unable to remove push rule")]
681 UnableToRemovePushRule,
682 #[error("Unable to update push rule")]
684 UnableToUpdatePushRule,
685 #[error("Rule `{0}` not found")]
687 RuleNotFound(String),
688 #[error("Unable to save push rules")]
690 UnableToSavePushRules,
691}
692
693impl NotificationSettingsError {
694 pub fn is_rule_not_found(&self) -> bool {
696 matches!(self, Self::RuleNotFound(_))
697 }
698}
699
700impl From<InsertPushRuleError> for NotificationSettingsError {
701 fn from(_: InsertPushRuleError) -> Self {
702 Self::UnableToAddPushRule
703 }
704}
705
706impl From<RemovePushRuleError> for NotificationSettingsError {
707 fn from(_: RemovePushRuleError) -> Self {
708 Self::UnableToRemovePushRule
709 }
710}
711
712#[derive(Debug, Error)]
713#[error("expected: {expected}, got: {got:?}")]
714pub struct WrongRoomState {
715 expected: &'static str,
716 got: RoomState,
717}
718
719impl WrongRoomState {
720 pub(crate) fn new(expected: &'static str, got: RoomState) -> Self {
721 Self { expected, got }
722 }
723}