use std::{io::Error as IoError, sync::Arc, time::Duration};
use as_variant::as_variant;
use http::StatusCode;
#[cfg(feature = "qrcode")]
use matrix_sdk_base::crypto::ScanError;
#[cfg(feature = "e2e-encryption")]
use matrix_sdk_base::crypto::{
CryptoStoreError, DecryptorError, KeyExportError, MegolmError, OlmError,
};
use matrix_sdk_base::{
Error as SdkBaseError, QueueWedgeError, RoomState, StoreError,
event_cache::store::EventCacheStoreError, media::store::MediaStoreError,
};
use reqwest::Error as ReqwestError;
use ruma::{
IdParseError,
api::{
client::{
error::{ErrorKind, RetryAfter},
uiaa::{UiaaInfo, UiaaResponse},
},
error::{FromHttpResponseError, IntoHttpError},
},
events::{room::power_levels::PowerLevelsError, tag::InvalidUserTagName},
push::{InsertPushRuleError, RemovePushRuleError},
};
use serde_json::Error as JsonError;
use thiserror::Error;
use url::ParseError as UrlParseError;
use crate::{
authentication::oauth::OAuthError, cross_process_lock::CrossProcessLockError,
event_cache::EventCacheError, media::MediaError, room::reply::ReplyError,
sliding_sync::Error as SlidingSyncError,
};
pub type Result<T, E = Error> = std::result::Result<T, E>;
pub type HttpResult<T> = std::result::Result<T, HttpError>;
#[derive(Error, Debug)]
pub enum RumaApiError {
#[error(transparent)]
ClientApi(ruma::api::client::Error),
#[error("User-Interactive Authentication required.")]
Uiaa(UiaaInfo),
#[error(transparent)]
Other(ruma::api::error::MatrixError),
}
impl RumaApiError {
pub fn as_client_api_error(&self) -> Option<&ruma::api::client::Error> {
as_variant!(self, Self::ClientApi)
}
}
#[derive(Error, Debug)]
pub enum HttpError {
#[error(transparent)]
Reqwest(#[from] ReqwestError),
#[error(transparent)]
Api(#[from] Box<FromHttpResponseError<RumaApiError>>),
#[error(transparent)]
IntoHttp(IntoHttpError),
#[error(transparent)]
RefreshToken(RefreshTokenError),
}
#[rustfmt::skip] impl HttpError {
pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
match self {
Self::Api(error) => {
as_variant!(error.as_ref(), FromHttpResponseError::Server)
},
_ => None
}
}
pub fn as_client_api_error(&self) -> Option<&ruma::api::client::Error> {
self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
}
}
impl HttpError {
pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
self.as_client_api_error().and_then(ruma::api::client::Error::error_kind)
}
pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
self.as_ruma_api_error().and_then(as_variant!(RumaApiError::Uiaa))
}
pub(crate) fn retry_kind(&self) -> RetryKind {
match self {
HttpError::Reqwest(_) => RetryKind::NetworkFailure,
HttpError::Api(error) => match error.as_ref() {
FromHttpResponseError::Server(api_error) => RetryKind::from_api_error(api_error),
_ => RetryKind::Permanent,
},
_ => RetryKind::Permanent,
}
}
}
impl From<FromHttpResponseError<RumaApiError>> for HttpError {
fn from(value: FromHttpResponseError<RumaApiError>) -> Self {
Self::Api(Box::new(value))
}
}
pub(crate) enum RetryKind {
NetworkFailure,
Transient {
#[cfg_attr(target_family = "wasm", allow(dead_code))]
retry_after: Option<Duration>,
},
Permanent,
}
impl RetryKind {
fn from_api_error(api_error: &RumaApiError) -> Self {
match api_error {
RumaApiError::ClientApi(client_error) => match client_error.error_kind() {
Some(ErrorKind::LimitExceeded { retry_after }) => {
RetryKind::from_retry_after(retry_after.as_ref())
}
Some(ErrorKind::Unrecognized) => RetryKind::Permanent,
_ => RetryKind::from_status_code(client_error.status_code),
},
RumaApiError::Other(e) => RetryKind::from_status_code(e.status_code),
RumaApiError::Uiaa(_) => RetryKind::Permanent,
}
}
fn from_retry_after(retry_after: Option<&RetryAfter>) -> Self {
let retry_after = retry_after
.and_then(|retry_after| match retry_after {
RetryAfter::Delay(d) => Some(d),
RetryAfter::DateTime(_) => None,
})
.copied();
Self::Transient { retry_after }
}
fn from_status_code(status_code: StatusCode) -> Self {
if status_code.as_u16() == 520 {
RetryKind::Permanent
} else if status_code == StatusCode::TOO_MANY_REQUESTS || status_code.is_server_error() {
RetryKind::Transient { retry_after: None }
} else {
RetryKind::Permanent
}
}
}
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum Error {
#[error(transparent)]
Http(Box<HttpError>),
#[error("the queried endpoint requires authentication but was called before logging in")]
AuthenticationRequired,
#[error("Local cache doesn't contain all necessary data to perform the action.")]
InsufficientData,
#[cfg(feature = "e2e-encryption")]
#[error("The olm machine has already been initialized")]
BadCryptoStoreState,
#[cfg(feature = "e2e-encryption")]
#[error("The olm machine isn't yet available")]
NoOlmMachine,
#[error(transparent)]
SerdeJson(#[from] JsonError),
#[error(transparent)]
Io(#[from] IoError),
#[cfg(feature = "e2e-encryption")]
#[error(transparent)]
CryptoStoreError(Box<CryptoStoreError>),
#[error(transparent)]
CrossProcessLockError(Box<CrossProcessLockError>),
#[cfg(feature = "e2e-encryption")]
#[error(transparent)]
OlmError(Box<OlmError>),
#[cfg(feature = "e2e-encryption")]
#[error(transparent)]
MegolmError(Box<MegolmError>),
#[cfg(feature = "e2e-encryption")]
#[error(transparent)]
DecryptorError(#[from] DecryptorError),
#[error(transparent)]
StateStore(Box<StoreError>),
#[error(transparent)]
EventCacheStore(Box<EventCacheStoreError>),
#[error(transparent)]
MediaStore(Box<MediaStoreError>),
#[error(transparent)]
Identifier(#[from] IdParseError),
#[error(transparent)]
Url(#[from] UrlParseError),
#[cfg(feature = "qrcode")]
#[error(transparent)]
QrCodeScanError(Box<ScanError>),
#[error(transparent)]
UserTagName(#[from] InvalidUserTagName),
#[error(transparent)]
SlidingSync(Box<SlidingSyncError>),
#[error("wrong room state: {0}")]
WrongRoomState(Box<WrongRoomState>),
#[error("session callbacks have been set multiple times")]
MultipleSessionCallbacks,
#[error(transparent)]
OAuth(Box<OAuthError>),
#[error("a concurrent request failed; see logs for details")]
ConcurrentRequestFailed,
#[cfg(not(target_family = "wasm"))]
#[error("unknown error: {0}")]
UnknownError(Box<dyn std::error::Error + Send + Sync>),
#[cfg(target_family = "wasm")]
#[error("unknown error: {0}")]
UnknownError(Box<dyn std::error::Error>),
#[error(transparent)]
EventCache(Box<EventCacheError>),
#[error(transparent)]
SendQueueWedgeError(Box<QueueWedgeError>),
#[error("backups are not enabled")]
BackupNotEnabled,
#[error("can't ignore the logged-in user")]
CantIgnoreLoggedInUser,
#[error(transparent)]
Media(#[from] MediaError),
#[error(transparent)]
ReplyError(#[from] ReplyError),
#[error("power levels error: {0}")]
PowerLevels(#[from] PowerLevelsError),
}
#[rustfmt::skip] impl Error {
pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
as_variant!(self, Self::Http).and_then(|e| e.as_ruma_api_error())
}
pub fn as_client_api_error(&self) -> Option<&ruma::api::client::Error> {
self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
}
pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
self.as_client_api_error().and_then(ruma::api::client::Error::error_kind)
}
pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
self.as_ruma_api_error().and_then(as_variant!(RumaApiError::Uiaa))
}
}
impl From<HttpError> for Error {
fn from(error: HttpError) -> Self {
Error::Http(Box::new(error))
}
}
#[cfg(feature = "e2e-encryption")]
impl From<CryptoStoreError> for Error {
fn from(error: CryptoStoreError) -> Self {
Error::CryptoStoreError(Box::new(error))
}
}
impl From<CrossProcessLockError> for Error {
fn from(error: CrossProcessLockError) -> Self {
Error::CrossProcessLockError(Box::new(error))
}
}
#[cfg(feature = "e2e-encryption")]
impl From<OlmError> for Error {
fn from(error: OlmError) -> Self {
Error::OlmError(Box::new(error))
}
}
#[cfg(feature = "e2e-encryption")]
impl From<MegolmError> for Error {
fn from(error: MegolmError) -> Self {
Error::MegolmError(Box::new(error))
}
}
impl From<StoreError> for Error {
fn from(error: StoreError) -> Self {
Error::StateStore(Box::new(error))
}
}
impl From<EventCacheStoreError> for Error {
fn from(error: EventCacheStoreError) -> Self {
Error::EventCacheStore(Box::new(error))
}
}
impl From<MediaStoreError> for Error {
fn from(error: MediaStoreError) -> Self {
Error::MediaStore(Box::new(error))
}
}
#[cfg(feature = "qrcode")]
impl From<ScanError> for Error {
fn from(error: ScanError) -> Self {
Error::QrCodeScanError(Box::new(error))
}
}
impl From<SlidingSyncError> for Error {
fn from(error: SlidingSyncError) -> Self {
Error::SlidingSync(Box::new(error))
}
}
impl From<OAuthError> for Error {
fn from(error: OAuthError) -> Self {
Error::OAuth(Box::new(error))
}
}
impl From<EventCacheError> for Error {
fn from(error: EventCacheError) -> Self {
Error::EventCache(Box::new(error))
}
}
impl From<QueueWedgeError> for Error {
fn from(error: QueueWedgeError) -> Self {
Error::SendQueueWedgeError(Box::new(error))
}
}
#[cfg(feature = "e2e-encryption")]
#[derive(Error, Debug)]
#[allow(dead_code)]
pub enum RoomKeyImportError {
#[error(transparent)]
SerdeJson(#[from] JsonError),
#[error("The crypto store hasn't been yet opened, can't import yet.")]
StoreClosed,
#[error(transparent)]
Io(#[from] IoError),
#[error(transparent)]
CryptoStore(#[from] CryptoStoreError),
#[error(transparent)]
Export(#[from] KeyExportError),
}
impl From<FromHttpResponseError<ruma::api::client::Error>> for HttpError {
fn from(err: FromHttpResponseError<ruma::api::client::Error>) -> Self {
Self::Api(Box::new(err.map(RumaApiError::ClientApi)))
}
}
impl From<FromHttpResponseError<UiaaResponse>> for HttpError {
fn from(err: FromHttpResponseError<UiaaResponse>) -> Self {
Self::Api(Box::new(err.map(|e| match e {
UiaaResponse::AuthResponse(i) => RumaApiError::Uiaa(i),
UiaaResponse::MatrixError(e) => RumaApiError::ClientApi(e),
})))
}
}
impl From<FromHttpResponseError<ruma::api::error::MatrixError>> for HttpError {
fn from(err: FromHttpResponseError<ruma::api::error::MatrixError>) -> Self {
Self::Api(Box::new(err.map(RumaApiError::Other)))
}
}
impl From<SdkBaseError> for Error {
fn from(e: SdkBaseError) -> Self {
match e {
SdkBaseError::StateStore(e) => Self::StateStore(Box::new(e)),
#[cfg(feature = "e2e-encryption")]
SdkBaseError::CryptoStore(e) => Self::CryptoStoreError(Box::new(e)),
#[cfg(feature = "e2e-encryption")]
SdkBaseError::BadCryptoStoreState => Self::BadCryptoStoreState,
#[cfg(feature = "e2e-encryption")]
SdkBaseError::OlmError(e) => Self::OlmError(Box::new(e)),
#[cfg(feature = "eyre")]
_ => Self::UnknownError(eyre::eyre!(e).into()),
#[cfg(all(not(feature = "eyre"), feature = "anyhow", not(target_family = "wasm")))]
_ => Self::UnknownError(anyhow::anyhow!(e).into()),
#[cfg(all(not(feature = "eyre"), feature = "anyhow", target_family = "wasm"))]
_ => Self::UnknownError(e.into()),
#[cfg(all(
not(feature = "eyre"),
not(feature = "anyhow"),
not(target_family = "wasm")
))]
_ => {
let e: Box<dyn std::error::Error + Send + Sync> = format!("{e:?}").into();
Self::UnknownError(e)
}
#[cfg(all(not(feature = "eyre"), not(feature = "anyhow"), target_family = "wasm"))]
_ => {
let e: Box<dyn std::error::Error> = format!("{e:?}").into();
Self::UnknownError(e)
}
}
}
}
impl From<ReqwestError> for Error {
fn from(e: ReqwestError) -> Self {
Error::Http(Box::new(HttpError::Reqwest(e)))
}
}
#[derive(Debug, Error)]
pub enum BeaconError {
#[error("Network error: {0}")]
Network(#[from] HttpError),
#[error("Existing beacon information not found.")]
NotFound,
#[error("Beacon event is redacted and cannot be processed.")]
Redacted,
#[error("Must join the room to access beacon information.")]
Stripped,
#[error("Deserialization error: {0}")]
Deserialization(#[from] serde_json::Error),
#[error("The beacon event has expired.")]
NotLive,
#[error("Other error: {0}")]
Other(Box<Error>),
}
impl From<Error> for BeaconError {
fn from(err: Error) -> Self {
BeaconError::Other(Box::new(err))
}
}
#[derive(Debug, Error, Clone)]
pub enum RefreshTokenError {
#[error("missing refresh token")]
RefreshTokenRequired,
#[error(transparent)]
MatrixAuth(Arc<HttpError>),
#[error(transparent)]
OAuth(#[from] Arc<OAuthError>),
}
#[derive(Debug, Error, Clone, PartialEq)]
pub enum NotificationSettingsError {
#[error("Invalid parameter `{0}`")]
InvalidParameter(String),
#[error("Unable to add push rule")]
UnableToAddPushRule,
#[error("Unable to remove push rule")]
UnableToRemovePushRule,
#[error("Unable to update push rule")]
UnableToUpdatePushRule,
#[error("Rule `{0}` not found")]
RuleNotFound(String),
#[error("Unable to save push rules")]
UnableToSavePushRules,
}
impl NotificationSettingsError {
pub fn is_rule_not_found(&self) -> bool {
matches!(self, Self::RuleNotFound(_))
}
}
impl From<InsertPushRuleError> for NotificationSettingsError {
fn from(_: InsertPushRuleError) -> Self {
Self::UnableToAddPushRule
}
}
impl From<RemovePushRuleError> for NotificationSettingsError {
fn from(_: RemovePushRuleError) -> Self {
Self::UnableToRemovePushRule
}
}
#[derive(Debug, Error)]
#[error("expected: {expected}, got: {got:?}")]
pub struct WrongRoomState {
expected: &'static str,
got: RoomState,
}
impl WrongRoomState {
pub(crate) fn new(expected: &'static str, got: RoomState) -> Self {
Self { expected, got }
}
}