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)]
104 Api(#[from] FromHttpResponseError<RumaApiError>),
105
106 #[error(transparent)]
109 IntoHttp(IntoHttpError),
110
111 #[error(transparent)]
113 RefreshToken(RefreshTokenError),
114}
115
116#[rustfmt::skip] impl HttpError {
118 pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
124 as_variant!(self, Self::Api(FromHttpResponseError::Server(e)) => e)
125 }
126
127 pub fn as_client_api_error(&self) -> Option<&ruma::api::client::Error> {
130 self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
131 }
132}
133
134impl HttpError {
136 pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
139 self.as_client_api_error()
140 .and_then(|e| as_variant!(&e.body, ErrorBody::Standard { kind, .. } => kind))
141 }
142
143 pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
155 self.as_ruma_api_error().and_then(as_variant!(RumaApiError::Uiaa))
156 }
157
158 pub(crate) fn retry_kind(&self) -> RetryKind {
161 match self {
162 HttpError::Reqwest(_) => RetryKind::NetworkFailure,
165
166 HttpError::Api(FromHttpResponseError::Server(api_error)) => {
167 RetryKind::from_api_error(api_error)
168 }
169 _ => RetryKind::Permanent,
170 }
171 }
172}
173
174pub(crate) enum RetryKind {
177 NetworkFailure,
179
180 Transient {
184 #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
186 retry_after: Option<Duration>,
187 },
188
189 Permanent,
192}
193
194impl RetryKind {
195 fn from_api_error(api_error: &RumaApiError) -> Self {
202 use ruma::api::client::Error;
203
204 match api_error {
205 RumaApiError::ClientApi(client_error) => {
206 let Error { status_code, body, .. } = client_error;
207
208 match body {
209 ErrorBody::Standard { kind, .. } => match kind {
210 ErrorKind::LimitExceeded { retry_after } => {
211 RetryKind::from_retry_after(retry_after.as_ref())
212 }
213 ErrorKind::Unrecognized => RetryKind::Permanent,
214 _ => RetryKind::from_status_code(*status_code),
215 },
216 _ => RetryKind::from_status_code(*status_code),
217 }
218 }
219 RumaApiError::Other(e) => RetryKind::from_status_code(e.status_code),
220 RumaApiError::Uiaa(_) => RetryKind::Permanent,
221 }
222 }
223
224 fn from_retry_after(retry_after: Option<&RetryAfter>) -> Self {
230 let retry_after = retry_after
231 .and_then(|retry_after| match retry_after {
232 RetryAfter::Delay(d) => Some(d),
233 RetryAfter::DateTime(_) => None,
234 })
235 .copied();
236
237 Self::Transient { retry_after }
238 }
239
240 fn from_status_code(status_code: StatusCode) -> Self {
247 if status_code == StatusCode::TOO_MANY_REQUESTS || status_code.is_server_error() {
251 RetryKind::Transient { retry_after: None }
252 } else {
253 RetryKind::Permanent
254 }
255 }
256}
257
258#[derive(Error, Debug)]
260#[non_exhaustive]
261pub enum Error {
262 #[error(transparent)]
264 Http(Box<HttpError>),
265
266 #[error("the queried endpoint requires authentication but was called before logging in")]
269 AuthenticationRequired,
270
271 #[error("Local cache doesn't contain all necessary data to perform the action.")]
273 InsufficientData,
274
275 #[cfg(feature = "e2e-encryption")]
278 #[error("The olm machine has already been initialized")]
279 BadCryptoStoreState,
280
281 #[cfg(feature = "e2e-encryption")]
283 #[error("The olm machine isn't yet available")]
284 NoOlmMachine,
285
286 #[error(transparent)]
288 SerdeJson(#[from] JsonError),
289
290 #[error(transparent)]
292 Io(#[from] IoError),
293
294 #[cfg(feature = "e2e-encryption")]
296 #[error(transparent)]
297 CryptoStoreError(Box<CryptoStoreError>),
298
299 #[error(transparent)]
301 CrossProcessLockError(Box<LockStoreError>),
302
303 #[cfg(feature = "e2e-encryption")]
305 #[error(transparent)]
306 OlmError(Box<OlmError>),
307
308 #[cfg(feature = "e2e-encryption")]
310 #[error(transparent)]
311 MegolmError(Box<MegolmError>),
312
313 #[cfg(feature = "e2e-encryption")]
315 #[error(transparent)]
316 DecryptorError(#[from] DecryptorError),
317
318 #[error(transparent)]
320 StateStore(Box<StoreError>),
321
322 #[error(transparent)]
324 EventCacheStore(Box<EventCacheStoreError>),
325
326 #[error(transparent)]
328 Identifier(#[from] IdParseError),
329
330 #[error(transparent)]
332 Url(#[from] UrlParseError),
333
334 #[cfg(feature = "qrcode")]
336 #[error(transparent)]
337 QrCodeScanError(Box<ScanError>),
338
339 #[error(transparent)]
341 UserTagName(#[from] InvalidUserTagName),
342
343 #[error(transparent)]
345 SlidingSync(Box<SlidingSyncError>),
346
347 #[error("wrong room state: {0}")]
351 WrongRoomState(Box<WrongRoomState>),
352
353 #[error("session callbacks have been set multiple times")]
355 MultipleSessionCallbacks,
356
357 #[error(transparent)]
359 OAuth(Box<OAuthError>),
360
361 #[error("a concurrent request failed; see logs for details")]
363 ConcurrentRequestFailed,
364
365 #[error("unknown error: {0}")]
370 UnknownError(Box<dyn std::error::Error + Send + Sync>),
371
372 #[error(transparent)]
374 EventCache(Box<EventCacheError>),
375
376 #[error(transparent)]
378 SendQueueWedgeError(Box<QueueWedgeError>),
379
380 #[error("backups are not enabled")]
382 BackupNotEnabled,
383
384 #[error(transparent)]
386 Media(#[from] MediaError),
387
388 #[error(transparent)]
390 ReplyError(#[from] ReplyError),
391}
392
393#[rustfmt::skip] impl Error {
395 pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
401 as_variant!(self, Self::Http).and_then(|e| e.as_ruma_api_error())
402 }
403
404 pub fn as_client_api_error(&self) -> Option<&ruma::api::client::Error> {
407 self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
408 }
409
410 pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
413 self.as_client_api_error().and_then(|e| {
414 as_variant!(&e.body, ErrorBody::Standard { kind, .. } => kind)
415 })
416 }
417
418 pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
430 self.as_ruma_api_error().and_then(as_variant!(RumaApiError::Uiaa))
431 }
432}
433
434impl From<HttpError> for Error {
435 fn from(error: HttpError) -> Self {
436 Error::Http(Box::new(error))
437 }
438}
439
440#[cfg(feature = "e2e-encryption")]
441impl From<CryptoStoreError> for Error {
442 fn from(error: CryptoStoreError) -> Self {
443 Error::CryptoStoreError(Box::new(error))
444 }
445}
446
447impl From<LockStoreError> for Error {
448 fn from(error: LockStoreError) -> Self {
449 Error::CrossProcessLockError(Box::new(error))
450 }
451}
452
453#[cfg(feature = "e2e-encryption")]
454impl From<OlmError> for Error {
455 fn from(error: OlmError) -> Self {
456 Error::OlmError(Box::new(error))
457 }
458}
459
460#[cfg(feature = "e2e-encryption")]
461impl From<MegolmError> for Error {
462 fn from(error: MegolmError) -> Self {
463 Error::MegolmError(Box::new(error))
464 }
465}
466
467impl From<StoreError> for Error {
468 fn from(error: StoreError) -> Self {
469 Error::StateStore(Box::new(error))
470 }
471}
472
473impl From<EventCacheStoreError> for Error {
474 fn from(error: EventCacheStoreError) -> Self {
475 Error::EventCacheStore(Box::new(error))
476 }
477}
478
479#[cfg(feature = "qrcode")]
480impl From<ScanError> for Error {
481 fn from(error: ScanError) -> Self {
482 Error::QrCodeScanError(Box::new(error))
483 }
484}
485
486impl From<SlidingSyncError> for Error {
487 fn from(error: SlidingSyncError) -> Self {
488 Error::SlidingSync(Box::new(error))
489 }
490}
491
492impl From<OAuthError> for Error {
493 fn from(error: OAuthError) -> Self {
494 Error::OAuth(Box::new(error))
495 }
496}
497
498impl From<EventCacheError> for Error {
499 fn from(error: EventCacheError) -> Self {
500 Error::EventCache(Box::new(error))
501 }
502}
503
504impl From<QueueWedgeError> for Error {
505 fn from(error: QueueWedgeError) -> Self {
506 Error::SendQueueWedgeError(Box::new(error))
507 }
508}
509
510#[cfg(feature = "e2e-encryption")]
512#[derive(Error, Debug)]
513#[allow(dead_code)]
515pub enum RoomKeyImportError {
516 #[error(transparent)]
518 SerdeJson(#[from] JsonError),
519
520 #[error("The crypto store hasn't been yet opened, can't import yet.")]
523 StoreClosed,
524
525 #[error(transparent)]
527 Io(#[from] IoError),
528
529 #[error(transparent)]
531 CryptoStore(#[from] CryptoStoreError),
532
533 #[error(transparent)]
535 Export(#[from] KeyExportError),
536}
537
538impl From<FromHttpResponseError<ruma::api::client::Error>> for HttpError {
539 fn from(err: FromHttpResponseError<ruma::api::client::Error>) -> Self {
540 Self::Api(err.map(RumaApiError::ClientApi))
541 }
542}
543
544impl From<FromHttpResponseError<UiaaResponse>> for HttpError {
545 fn from(err: FromHttpResponseError<UiaaResponse>) -> Self {
546 Self::Api(err.map(|e| match e {
547 UiaaResponse::AuthResponse(i) => RumaApiError::Uiaa(i),
548 UiaaResponse::MatrixError(e) => RumaApiError::ClientApi(e),
549 }))
550 }
551}
552
553impl From<FromHttpResponseError<ruma::api::error::MatrixError>> for HttpError {
554 fn from(err: FromHttpResponseError<ruma::api::error::MatrixError>) -> Self {
555 Self::Api(err.map(RumaApiError::Other))
556 }
557}
558
559impl From<SdkBaseError> for Error {
560 fn from(e: SdkBaseError) -> Self {
561 match e {
562 SdkBaseError::StateStore(e) => Self::StateStore(Box::new(e)),
563 #[cfg(feature = "e2e-encryption")]
564 SdkBaseError::CryptoStore(e) => Self::CryptoStoreError(Box::new(e)),
565 #[cfg(feature = "e2e-encryption")]
566 SdkBaseError::BadCryptoStoreState => Self::BadCryptoStoreState,
567 #[cfg(feature = "e2e-encryption")]
568 SdkBaseError::OlmError(e) => Self::OlmError(Box::new(e)),
569 #[cfg(feature = "eyre")]
570 _ => Self::UnknownError(eyre::eyre!(e).into()),
571 #[cfg(all(not(feature = "eyre"), feature = "anyhow"))]
572 _ => Self::UnknownError(anyhow::anyhow!(e).into()),
573 #[cfg(all(not(feature = "eyre"), not(feature = "anyhow")))]
574 _ => {
575 let e: Box<dyn std::error::Error + Sync + Send> = format!("{e:?}").into();
576 Self::UnknownError(e)
577 }
578 }
579 }
580}
581
582impl From<ReqwestError> for Error {
583 fn from(e: ReqwestError) -> Self {
584 Error::Http(Box::new(HttpError::Reqwest(e)))
585 }
586}
587
588#[derive(Debug, Error)]
590pub enum BeaconError {
591 #[error("Network error: {0}")]
593 Network(#[from] HttpError),
594
595 #[error("Existing beacon information not found.")]
597 NotFound,
598
599 #[error("Beacon event is redacted and cannot be processed.")]
601 Redacted,
602
603 #[error("Must join the room to access beacon information.")]
605 Stripped,
606
607 #[error("Deserialization error: {0}")]
609 Deserialization(#[from] serde_json::Error),
610
611 #[error("The beacon event has expired.")]
613 NotLive,
614
615 #[error("Other error: {0}")]
617 Other(Box<Error>),
618}
619
620impl From<Error> for BeaconError {
621 fn from(err: Error) -> Self {
622 BeaconError::Other(Box::new(err))
623 }
624}
625
626#[derive(Debug, Error, Clone)]
634pub enum RefreshTokenError {
635 #[error("missing refresh token")]
637 RefreshTokenRequired,
638
639 #[error(transparent)]
641 MatrixAuth(Arc<HttpError>),
642
643 #[error(transparent)]
645 OAuth(#[from] Arc<OAuthError>),
646}
647
648#[derive(Debug, Error, Clone, PartialEq)]
650pub enum NotificationSettingsError {
651 #[error("Invalid parameter `{0}`")]
653 InvalidParameter(String),
654 #[error("Unable to add push rule")]
656 UnableToAddPushRule,
657 #[error("Unable to remove push rule")]
659 UnableToRemovePushRule,
660 #[error("Unable to update push rule")]
662 UnableToUpdatePushRule,
663 #[error("Rule `{0}` not found")]
665 RuleNotFound(String),
666 #[error("Unable to save push rules")]
668 UnableToSavePushRules,
669}
670
671impl From<InsertPushRuleError> for NotificationSettingsError {
672 fn from(_: InsertPushRuleError) -> Self {
673 Self::UnableToAddPushRule
674 }
675}
676
677impl From<RemovePushRuleError> for NotificationSettingsError {
678 fn from(_: RemovePushRuleError) -> Self {
679 Self::UnableToRemovePushRule
680 }
681}
682
683#[derive(Debug, Error)]
684#[error("expected: {expected}, got: {got:?}")]
685pub struct WrongRoomState {
686 expected: &'static str,
687 got: RoomState,
688}
689
690impl WrongRoomState {
691 pub(crate) fn new(expected: &'static str, got: RoomState) -> Self {
692 Self { expected, got }
693 }
694}