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::{event_cache::EventCacheError, media::MediaError, store_locks::LockStoreError};
49
50pub type Result<T, E = Error> = std::result::Result<T, E>;
52
53pub type HttpResult<T> = std::result::Result<T, HttpError>;
55
56#[derive(Error, Debug)]
59pub enum RumaApiError {
60 #[error(transparent)]
62 ClientApi(ruma::api::client::Error),
63
64 #[error("User-Interactive Authentication required.")]
71 Uiaa(UiaaInfo),
72
73 #[error(transparent)]
75 Other(ruma::api::error::MatrixError),
76}
77
78impl RumaApiError {
79 pub fn as_client_api_error(&self) -> Option<&ruma::api::client::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("the queried endpoint is not meant for clients")]
97 NotClientRequest,
98
99 #[error(transparent)]
101 Api(#[from] FromHttpResponseError<RumaApiError>),
102
103 #[error(transparent)]
106 IntoHttp(IntoHttpError),
107
108 #[error(transparent)]
110 RefreshToken(RefreshTokenError),
111}
112
113#[rustfmt::skip] impl HttpError {
115 pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
121 as_variant!(self, Self::Api(FromHttpResponseError::Server(e)) => e)
122 }
123
124 pub fn as_client_api_error(&self) -> Option<&ruma::api::client::Error> {
127 self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
128 }
129}
130
131impl HttpError {
133 pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
136 self.as_client_api_error()
137 .and_then(|e| as_variant!(&e.body, ErrorBody::Standard { kind, .. } => kind))
138 }
139
140 pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
152 self.as_ruma_api_error().and_then(as_variant!(RumaApiError::Uiaa))
153 }
154
155 pub(crate) fn retry_kind(&self) -> RetryKind {
158 match self {
159 HttpError::Reqwest(_) => RetryKind::NetworkFailure,
162
163 HttpError::Api(FromHttpResponseError::Server(api_error)) => {
164 RetryKind::from_api_error(api_error)
165 }
166 _ => RetryKind::Permanent,
167 }
168 }
169}
170
171pub(crate) enum RetryKind {
174 NetworkFailure,
176
177 Transient {
181 #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
183 retry_after: Option<Duration>,
184 },
185
186 Permanent,
189}
190
191impl RetryKind {
192 fn from_api_error(api_error: &RumaApiError) -> Self {
199 use ruma::api::client::Error;
200
201 match api_error {
202 RumaApiError::ClientApi(client_error) => {
203 let Error { status_code, body, .. } = client_error;
204
205 match body {
206 ErrorBody::Standard { kind, .. } => match kind {
207 ErrorKind::LimitExceeded { retry_after } => {
208 RetryKind::from_retry_after(retry_after.as_ref())
209 }
210 ErrorKind::Unrecognized => RetryKind::Permanent,
211 _ => RetryKind::from_status_code(*status_code),
212 },
213 _ => RetryKind::from_status_code(*status_code),
214 }
215 }
216 RumaApiError::Other(e) => RetryKind::from_status_code(e.status_code),
217 RumaApiError::Uiaa(_) => RetryKind::Permanent,
218 }
219 }
220
221 fn from_retry_after(retry_after: Option<&RetryAfter>) -> Self {
227 let retry_after = retry_after
228 .and_then(|retry_after| match retry_after {
229 RetryAfter::Delay(d) => Some(d),
230 RetryAfter::DateTime(_) => None,
231 })
232 .copied();
233
234 Self::Transient { retry_after }
235 }
236
237 fn from_status_code(status_code: StatusCode) -> Self {
244 if status_code == StatusCode::TOO_MANY_REQUESTS || status_code.is_server_error() {
248 RetryKind::Transient { retry_after: None }
249 } else {
250 RetryKind::Permanent
251 }
252 }
253}
254
255#[derive(Error, Debug)]
257#[non_exhaustive]
258pub enum Error {
259 #[error(transparent)]
261 Http(#[from] HttpError),
262
263 #[error("the queried endpoint requires authentication but was called before logging in")]
266 AuthenticationRequired,
267
268 #[error("Local cache doesn't contain all necessary data to perform the action.")]
270 InsufficientData,
271
272 #[cfg(feature = "e2e-encryption")]
275 #[error("The olm machine has already been initialized")]
276 BadCryptoStoreState,
277
278 #[cfg(feature = "e2e-encryption")]
280 #[error("The olm machine isn't yet available")]
281 NoOlmMachine,
282
283 #[error(transparent)]
285 SerdeJson(#[from] JsonError),
286
287 #[error(transparent)]
289 Io(#[from] IoError),
290
291 #[cfg(feature = "e2e-encryption")]
293 #[error(transparent)]
294 CryptoStoreError(#[from] CryptoStoreError),
295
296 #[error(transparent)]
298 CrossProcessLockError(#[from] LockStoreError),
299
300 #[cfg(feature = "e2e-encryption")]
302 #[error(transparent)]
303 OlmError(#[from] OlmError),
304
305 #[cfg(feature = "e2e-encryption")]
307 #[error(transparent)]
308 MegolmError(#[from] MegolmError),
309
310 #[cfg(feature = "e2e-encryption")]
312 #[error(transparent)]
313 DecryptorError(#[from] DecryptorError),
314
315 #[error(transparent)]
317 StateStore(#[from] StoreError),
318
319 #[error(transparent)]
321 EventCacheStore(#[from] EventCacheStoreError),
322
323 #[error(transparent)]
325 Identifier(#[from] IdParseError),
326
327 #[error(transparent)]
329 Url(#[from] UrlParseError),
330
331 #[cfg(feature = "qrcode")]
333 #[error(transparent)]
334 QrCodeScanError(#[from] ScanError),
335
336 #[error(transparent)]
338 UserTagName(#[from] InvalidUserTagName),
339
340 #[error(transparent)]
342 SlidingSync(#[from] crate::sliding_sync::Error),
343
344 #[error("wrong room state: {0}")]
348 WrongRoomState(WrongRoomState),
349
350 #[error("session callbacks have been set multiple times")]
352 MultipleSessionCallbacks,
353
354 #[cfg(feature = "experimental-oidc")]
356 #[error(transparent)]
357 Oidc(#[from] crate::authentication::oidc::OidcError),
358
359 #[error("a concurrent request failed; see logs for details")]
361 ConcurrentRequestFailed,
362
363 #[error("unknown error: {0}")]
368 UnknownError(Box<dyn std::error::Error + Send + Sync>),
369
370 #[error(transparent)]
372 EventCache(#[from] EventCacheError),
373
374 #[error(transparent)]
376 SendQueueWedgeError(#[from] QueueWedgeError),
377
378 #[error("backups are not enabled")]
380 BackupNotEnabled,
381
382 #[error(transparent)]
384 Media(#[from] MediaError),
385}
386
387#[rustfmt::skip] impl Error {
389 pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
395 as_variant!(self, Self::Http).and_then(|e| e.as_ruma_api_error())
396 }
397
398 pub fn as_client_api_error(&self) -> Option<&ruma::api::client::Error> {
401 self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
402 }
403
404 pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
407 self.as_client_api_error().and_then(|e| {
408 as_variant!(&e.body, ErrorBody::Standard { kind, .. } => kind)
409 })
410 }
411
412 pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
424 self.as_ruma_api_error().and_then(as_variant!(RumaApiError::Uiaa))
425 }
426}
427
428#[cfg(feature = "e2e-encryption")]
430#[derive(Error, Debug)]
431#[allow(dead_code)]
433pub enum RoomKeyImportError {
434 #[error(transparent)]
436 SerdeJson(#[from] JsonError),
437
438 #[error("The crypto store hasn't been yet opened, can't import yet.")]
441 StoreClosed,
442
443 #[error(transparent)]
445 Io(#[from] IoError),
446
447 #[error(transparent)]
449 CryptoStore(#[from] CryptoStoreError),
450
451 #[error(transparent)]
453 Export(#[from] KeyExportError),
454}
455
456impl From<FromHttpResponseError<ruma::api::client::Error>> for HttpError {
457 fn from(err: FromHttpResponseError<ruma::api::client::Error>) -> Self {
458 Self::Api(err.map(RumaApiError::ClientApi))
459 }
460}
461
462impl From<FromHttpResponseError<UiaaResponse>> for HttpError {
463 fn from(err: FromHttpResponseError<UiaaResponse>) -> Self {
464 Self::Api(err.map(|e| match e {
465 UiaaResponse::AuthResponse(i) => RumaApiError::Uiaa(i),
466 UiaaResponse::MatrixError(e) => RumaApiError::ClientApi(e),
467 }))
468 }
469}
470
471impl From<FromHttpResponseError<ruma::api::error::MatrixError>> for HttpError {
472 fn from(err: FromHttpResponseError<ruma::api::error::MatrixError>) -> Self {
473 Self::Api(err.map(RumaApiError::Other))
474 }
475}
476
477impl From<SdkBaseError> for Error {
478 fn from(e: SdkBaseError) -> Self {
479 match e {
480 SdkBaseError::StateStore(e) => Self::StateStore(e),
481 #[cfg(feature = "e2e-encryption")]
482 SdkBaseError::CryptoStore(e) => Self::CryptoStoreError(e),
483 #[cfg(feature = "e2e-encryption")]
484 SdkBaseError::BadCryptoStoreState => Self::BadCryptoStoreState,
485 #[cfg(feature = "e2e-encryption")]
486 SdkBaseError::OlmError(e) => Self::OlmError(e),
487 #[cfg(feature = "eyre")]
488 _ => Self::UnknownError(eyre::eyre!(e).into()),
489 #[cfg(all(not(feature = "eyre"), feature = "anyhow"))]
490 _ => Self::UnknownError(anyhow::anyhow!(e).into()),
491 #[cfg(all(not(feature = "eyre"), not(feature = "anyhow")))]
492 _ => {
493 let e: Box<dyn std::error::Error + Sync + Send> = format!("{e:?}").into();
494 Self::UnknownError(e)
495 }
496 }
497 }
498}
499
500impl From<ReqwestError> for Error {
501 fn from(e: ReqwestError) -> Self {
502 Error::Http(HttpError::Reqwest(e))
503 }
504}
505
506#[derive(Debug, Error)]
508pub enum BeaconError {
509 #[error("Network error: {0}")]
511 Network(#[from] HttpError),
512
513 #[error("Existing beacon information not found.")]
515 NotFound,
516
517 #[error("Beacon event is redacted and cannot be processed.")]
519 Redacted,
520
521 #[error("Must join the room to access beacon information.")]
523 Stripped,
524
525 #[error("Deserialization error: {0}")]
527 Deserialization(#[from] serde_json::Error),
528
529 #[error("The beacon event has expired.")]
531 NotLive,
532
533 #[error("Other error: {0}")]
535 Other(Box<Error>),
536}
537
538impl From<Error> for BeaconError {
539 fn from(err: Error) -> Self {
540 BeaconError::Other(Box::new(err))
541 }
542}
543
544#[derive(Debug, Error, Clone)]
552pub enum RefreshTokenError {
553 #[error("missing refresh token")]
555 RefreshTokenRequired,
556
557 #[error(transparent)]
559 MatrixAuth(Arc<HttpError>),
560
561 #[cfg(feature = "experimental-oidc")]
563 #[error(transparent)]
564 Oidc(#[from] Arc<crate::authentication::oidc::OidcError>),
565}
566
567#[derive(Debug, Error, Clone, PartialEq)]
569pub enum NotificationSettingsError {
570 #[error("Invalid parameter `{0}`")]
572 InvalidParameter(String),
573 #[error("Unable to add push rule")]
575 UnableToAddPushRule,
576 #[error("Unable to remove push rule")]
578 UnableToRemovePushRule,
579 #[error("Unable to update push rule")]
581 UnableToUpdatePushRule,
582 #[error("Rule `{0}` not found")]
584 RuleNotFound(String),
585 #[error("Unable to save push rules")]
587 UnableToSavePushRules,
588}
589
590impl From<InsertPushRuleError> for NotificationSettingsError {
591 fn from(_: InsertPushRuleError) -> Self {
592 Self::UnableToAddPushRule
593 }
594}
595
596impl From<RemovePushRuleError> for NotificationSettingsError {
597 fn from(_: RemovePushRuleError) -> Self {
598 Self::UnableToRemovePushRule
599 }
600}
601
602#[derive(Debug, Error)]
603#[error("expected: {expected}, got: {got:?}")]
604pub struct WrongRoomState {
605 expected: &'static str,
606 got: RoomState,
607}
608
609impl WrongRoomState {
610 pub(crate) fn new(expected: &'static str, got: RoomState) -> Self {
611 Self { expected, got }
612 }
613}