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
60#[derive(Error, Debug)]
63pub enum RumaApiError {
64 #[error(transparent)]
66 ClientApi(ruma::api::error::Error),
67
68 #[error("User-Interactive Authentication required.")]
75 Uiaa(UiaaInfo),
76}
77
78impl RumaApiError {
79 pub fn as_client_api_error(&self) -> Option<&ruma::api::error::Error> {
83 as_variant!(self, Self::ClientApi)
84 }
85}
86
87#[derive(Error, Debug)]
90pub enum HttpError {
91 #[error(transparent)]
93 Reqwest(#[from] ReqwestError),
94
95 #[error(transparent)]
98 Api(#[from] Box<FromHttpResponseError<RumaApiError>>),
99
100 #[error(transparent)]
103 IntoHttp(IntoHttpError),
104
105 #[error(transparent)]
107 RefreshToken(RefreshTokenError),
108
109 #[error(transparent)]
114 Cached(Arc<HttpError>),
115
116 #[cfg(target_os = "android")]
118 #[error(transparent)]
119 VerifierBuilder(#[from] VerifierBuilderError),
120}
121
122#[rustfmt::skip] impl HttpError {
124 pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
130 match self {
131 Self::Api(error) => {
132 as_variant!(error.as_ref(), FromHttpResponseError::Server)
133 },
134 _ => None
135 }
136 }
137
138 pub fn as_client_api_error(&self) -> Option<&ruma::api::error::Error> {
141 self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
142 }
143}
144
145impl HttpError {
147 pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
150 self.as_client_api_error().and_then(ruma::api::error::Error::error_kind)
151 }
152
153 pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
165 self.as_ruma_api_error().and_then(as_variant!(RumaApiError::Uiaa))
166 }
167
168 pub(crate) fn retry_kind(&self) -> RetryKind {
171 match self {
172 HttpError::Reqwest(_) => RetryKind::NetworkFailure,
175
176 HttpError::Api(error) => match error.as_ref() {
177 FromHttpResponseError::Server(api_error) => RetryKind::from_api_error(api_error),
178 _ => RetryKind::Permanent,
179 },
180 _ => RetryKind::Permanent,
181 }
182 }
183}
184
185impl From<FromHttpResponseError<RumaApiError>> for HttpError {
186 fn from(value: FromHttpResponseError<RumaApiError>) -> Self {
187 Self::Api(Box::new(value))
188 }
189}
190
191pub(crate) enum RetryKind {
194 NetworkFailure,
196
197 Transient {
201 #[cfg_attr(target_family = "wasm", allow(dead_code))]
203 retry_after: Option<Duration>,
204 },
205
206 Permanent,
209}
210
211impl RetryKind {
212 fn from_api_error(api_error: &RumaApiError) -> Self {
219 match api_error {
220 RumaApiError::ClientApi(client_error) => match client_error.error_kind() {
221 Some(ErrorKind::LimitExceeded(limit_exceeded)) => {
222 RetryKind::from_retry_after(limit_exceeded.retry_after.as_ref())
223 }
224 Some(ErrorKind::Unrecognized) => RetryKind::Permanent,
225 _ => RetryKind::from_status_code(client_error.status_code),
226 },
227 RumaApiError::Uiaa(_) => RetryKind::Permanent,
228 }
229 }
230
231 fn from_retry_after(retry_after: Option<&RetryAfter>) -> Self {
237 let retry_after = retry_after
238 .and_then(|retry_after| match retry_after {
239 RetryAfter::Delay(d) => Some(d),
240 RetryAfter::DateTime(_) => None,
241 })
242 .copied();
243
244 Self::Transient { retry_after }
245 }
246
247 fn from_status_code(status_code: StatusCode) -> Self {
254 if status_code.as_u16() == 520 {
255 RetryKind::Permanent
258 } else if status_code == StatusCode::TOO_MANY_REQUESTS || status_code.is_server_error() {
259 RetryKind::Transient { retry_after: None }
263 } else {
264 RetryKind::Permanent
265 }
266 }
267}
268
269#[derive(Error, Debug)]
271#[non_exhaustive]
272pub enum Error {
273 #[error(transparent)]
275 Http(Box<HttpError>),
276
277 #[error("the queried endpoint requires authentication but was called before logging in")]
280 AuthenticationRequired,
281
282 #[error("Local cache doesn't contain all necessary data to perform the action.")]
284 InsufficientData,
285
286 #[cfg(feature = "e2e-encryption")]
289 #[error("The olm machine has already been initialized")]
290 BadCryptoStoreState,
291
292 #[cfg(feature = "e2e-encryption")]
294 #[error("The olm machine isn't yet available")]
295 NoOlmMachine,
296
297 #[error(transparent)]
299 SerdeJson(#[from] JsonError),
300
301 #[error(transparent)]
303 Io(#[from] IoError),
304
305 #[cfg(feature = "e2e-encryption")]
307 #[error(transparent)]
308 CryptoStoreError(Box<CryptoStoreError>),
309
310 #[error(transparent)]
312 CrossProcessLockError(Box<CrossProcessLockError>),
313
314 #[cfg(feature = "e2e-encryption")]
316 #[error(transparent)]
317 OlmError(Box<OlmError>),
318
319 #[cfg(feature = "e2e-encryption")]
321 #[error(transparent)]
322 MegolmError(Box<MegolmError>),
323
324 #[cfg(feature = "e2e-encryption")]
326 #[error(transparent)]
327 DecryptorError(#[from] DecryptorError),
328
329 #[error(transparent)]
331 StateStore(Box<StoreError>),
332
333 #[error(transparent)]
335 EventCacheStore(Box<EventCacheStoreError>),
336
337 #[error(transparent)]
339 MediaStore(Box<MediaStoreError>),
340
341 #[error(transparent)]
343 Identifier(#[from] IdParseError),
344
345 #[error(transparent)]
347 Url(#[from] UrlParseError),
348
349 #[cfg(feature = "qrcode")]
351 #[error(transparent)]
352 QrCodeScanError(Box<ScanError>),
353
354 #[error(transparent)]
356 UserTagName(#[from] InvalidUserTagName),
357
358 #[error(transparent)]
360 SlidingSync(Box<SlidingSyncError>),
361
362 #[error("wrong room state: {0}")]
366 WrongRoomState(Box<WrongRoomState>),
367
368 #[error("session callbacks have been set multiple times")]
370 MultipleSessionCallbacks,
371
372 #[error(transparent)]
374 OAuth(Box<OAuthError>),
375
376 #[error("a concurrent request failed; see logs for details")]
378 ConcurrentRequestFailed,
379
380 #[cfg(not(target_family = "wasm"))]
385 #[error("unknown error: {0}")]
386 UnknownError(Box<dyn std::error::Error + Send + Sync>),
387
388 #[cfg(target_family = "wasm")]
390 #[error("unknown error: {0}")]
391 UnknownError(Box<dyn std::error::Error>),
392
393 #[error(transparent)]
395 EventCache(Box<EventCacheError>),
396
397 #[error(transparent)]
399 SendQueueWedgeError(Box<QueueWedgeError>),
400
401 #[error("backups are not enabled")]
403 BackupNotEnabled,
404
405 #[error("can't ignore the logged-in user")]
407 CantIgnoreLoggedInUser,
408
409 #[error(transparent)]
411 Media(#[from] MediaError),
412
413 #[error(transparent)]
415 ReplyError(#[from] ReplyError),
416
417 #[error("power levels error: {0}")]
419 PowerLevels(#[from] PowerLevelsError),
420
421 #[error("timed out")]
423 Timeout,
424}
425
426#[rustfmt::skip] impl Error {
428 pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
434 as_variant!(self, Self::Http).and_then(|e| e.as_ruma_api_error())
435 }
436
437 pub fn as_client_api_error(&self) -> Option<&ruma::api::error::Error> {
440 self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
441 }
442
443 pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
446 self.as_client_api_error().and_then(ruma::api::error::Error::error_kind)
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<CrossProcessLockError> for Error {
479 fn from(error: CrossProcessLockError) -> Self {
480 Error::CrossProcessLockError(Box::new(error))
481 }
482}
483
484impl From<CrossProcessLockUnobtained> for Error {
485 fn from(error: CrossProcessLockUnobtained) -> Self {
486 CrossProcessLockError::from(error).into()
487 }
488}
489
490#[cfg(feature = "e2e-encryption")]
491impl From<OlmError> for Error {
492 fn from(error: OlmError) -> Self {
493 Error::OlmError(Box::new(error))
494 }
495}
496
497#[cfg(feature = "e2e-encryption")]
498impl From<MegolmError> for Error {
499 fn from(error: MegolmError) -> Self {
500 Error::MegolmError(Box::new(error))
501 }
502}
503
504impl From<StoreError> for Error {
505 fn from(error: StoreError) -> Self {
506 Error::StateStore(Box::new(error))
507 }
508}
509
510impl From<EventCacheStoreError> for Error {
511 fn from(error: EventCacheStoreError) -> Self {
512 Error::EventCacheStore(Box::new(error))
513 }
514}
515
516impl From<MediaStoreError> for Error {
517 fn from(error: MediaStoreError) -> Self {
518 Error::MediaStore(Box::new(error))
519 }
520}
521
522#[cfg(feature = "qrcode")]
523impl From<ScanError> for Error {
524 fn from(error: ScanError) -> Self {
525 Error::QrCodeScanError(Box::new(error))
526 }
527}
528
529impl From<SlidingSyncError> for Error {
530 fn from(error: SlidingSyncError) -> Self {
531 Error::SlidingSync(Box::new(error))
532 }
533}
534
535impl From<OAuthError> for Error {
536 fn from(error: OAuthError) -> Self {
537 Error::OAuth(Box::new(error))
538 }
539}
540
541impl From<EventCacheError> for Error {
542 fn from(error: EventCacheError) -> Self {
543 Error::EventCache(Box::new(error))
544 }
545}
546
547impl From<QueueWedgeError> for Error {
548 fn from(error: QueueWedgeError) -> Self {
549 Error::SendQueueWedgeError(Box::new(error))
550 }
551}
552
553#[cfg(feature = "e2e-encryption")]
555#[derive(Error, Debug)]
556#[allow(dead_code)]
558pub enum RoomKeyImportError {
559 #[error(transparent)]
561 SerdeJson(#[from] JsonError),
562
563 #[error("The crypto store hasn't been yet opened, can't import yet.")]
566 StoreClosed,
567
568 #[error(transparent)]
570 Io(#[from] IoError),
571
572 #[error(transparent)]
574 CryptoStore(#[from] CryptoStoreError),
575
576 #[error(transparent)]
578 Export(#[from] KeyExportError),
579}
580
581impl From<FromHttpResponseError<ruma::api::error::Error>> for HttpError {
582 fn from(err: FromHttpResponseError<ruma::api::error::Error>) -> Self {
583 Self::Api(Box::new(err.map(RumaApiError::ClientApi)))
584 }
585}
586
587impl From<FromHttpResponseError<UiaaResponse>> for HttpError {
588 fn from(err: FromHttpResponseError<UiaaResponse>) -> Self {
589 Self::Api(Box::new(err.map(|e| match e {
590 UiaaResponse::AuthResponse(i) => RumaApiError::Uiaa(i),
591 UiaaResponse::MatrixError(e) => RumaApiError::ClientApi(e),
592 })))
593 }
594}
595
596impl From<SdkBaseError> for Error {
597 fn from(e: SdkBaseError) -> Self {
598 match e {
599 SdkBaseError::StateStore(e) => Self::StateStore(Box::new(e)),
600 #[cfg(feature = "e2e-encryption")]
601 SdkBaseError::CryptoStore(e) => Self::CryptoStoreError(Box::new(e)),
602 #[cfg(feature = "e2e-encryption")]
603 SdkBaseError::BadCryptoStoreState => Self::BadCryptoStoreState,
604 #[cfg(feature = "e2e-encryption")]
605 SdkBaseError::OlmError(e) => Self::OlmError(Box::new(e)),
606 #[cfg(feature = "eyre")]
607 _ => Self::UnknownError(eyre::eyre!(e).into()),
608 #[cfg(all(not(feature = "eyre"), feature = "anyhow", not(target_family = "wasm")))]
609 _ => Self::UnknownError(anyhow::anyhow!(e).into()),
610 #[cfg(all(not(feature = "eyre"), feature = "anyhow", target_family = "wasm"))]
611 _ => Self::UnknownError(e.into()),
612 #[cfg(all(
613 not(feature = "eyre"),
614 not(feature = "anyhow"),
615 not(target_family = "wasm")
616 ))]
617 _ => {
618 let e: Box<dyn std::error::Error + Send + Sync> = format!("{e:?}").into();
619 Self::UnknownError(e)
620 }
621 #[cfg(all(not(feature = "eyre"), not(feature = "anyhow"), target_family = "wasm"))]
622 _ => {
623 let e: Box<dyn std::error::Error> = format!("{e:?}").into();
624 Self::UnknownError(e)
625 }
626 }
627 }
628}
629
630impl From<ReqwestError> for Error {
631 fn from(e: ReqwestError) -> Self {
632 Error::Http(Box::new(HttpError::Reqwest(e)))
633 }
634}
635
636#[derive(Debug, Error)]
638pub enum BeaconError {
639 #[error("Network error: {0}")]
641 Network(#[from] HttpError),
642
643 #[error("Existing beacon information not found.")]
645 NotFound,
646
647 #[error("Beacon event is redacted and cannot be processed.")]
649 Redacted,
650
651 #[error("Must join the room to access beacon information.")]
653 Stripped,
654
655 #[error("Deserialization error: {0}")]
657 Deserialization(#[from] serde_json::Error),
658
659 #[error("The beacon event has expired.")]
661 NotLive,
662
663 #[error("Other error: {0}")]
665 Other(Box<Error>),
666}
667
668impl From<Error> for BeaconError {
669 fn from(err: Error) -> Self {
670 BeaconError::Other(Box::new(err))
671 }
672}
673
674#[derive(Debug, Error, Clone)]
682pub enum RefreshTokenError {
683 #[error("missing refresh token")]
685 RefreshTokenRequired,
686
687 #[error(transparent)]
689 MatrixAuth(Arc<HttpError>),
690
691 #[error(transparent)]
693 OAuth(#[from] Arc<OAuthError>),
694}
695
696#[derive(Debug, Error, Clone, PartialEq)]
698pub enum NotificationSettingsError {
699 #[error("Invalid parameter `{0}`")]
701 InvalidParameter(String),
702 #[error("Unable to add push rule")]
704 UnableToAddPushRule,
705 #[error("Unable to remove push rule")]
707 UnableToRemovePushRule,
708 #[error("Unable to update push rule")]
710 UnableToUpdatePushRule,
711 #[error("Rule `{0}` not found")]
713 RuleNotFound(String),
714 #[error("Unable to save push rules")]
716 UnableToSavePushRules,
717}
718
719impl NotificationSettingsError {
720 pub fn is_rule_not_found(&self) -> bool {
722 matches!(self, Self::RuleNotFound(_))
723 }
724}
725
726impl From<InsertPushRuleError> for NotificationSettingsError {
727 fn from(_: InsertPushRuleError) -> Self {
728 Self::UnableToAddPushRule
729 }
730}
731
732impl From<RemovePushRuleError> for NotificationSettingsError {
733 fn from(_: RemovePushRuleError) -> Self {
734 Self::UnableToRemovePushRule
735 }
736}
737
738#[derive(Debug, Error)]
739#[error("expected: {expected}, got: {got:?}")]
740pub struct WrongRoomState {
741 expected: &'static str,
742 got: RoomState,
743}
744
745impl WrongRoomState {
746 pub(crate) fn new(expected: &'static str, got: RoomState) -> Self {
747 Self { expected, got }
748 }
749}