matrix_sdk/
error.rs

1// Copyright 2020 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Error conditions.
16
17use 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    event_cache::store::EventCacheStoreError, media::store::MediaStoreError,
30};
31use reqwest::Error as ReqwestError;
32use ruma::{
33    IdParseError,
34    api::{
35        client::{
36            error::{ErrorKind, RetryAfter},
37            uiaa::{UiaaInfo, UiaaResponse},
38        },
39        error::{FromHttpResponseError, IntoHttpError},
40    },
41    events::{room::power_levels::PowerLevelsError, tag::InvalidUserTagName},
42    push::{InsertPushRuleError, RemovePushRuleError},
43};
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
54/// Result type of the matrix-sdk.
55pub type Result<T, E = Error> = std::result::Result<T, E>;
56
57/// Result type of a pure HTTP request.
58pub type HttpResult<T> = std::result::Result<T, HttpError>;
59
60/// An error response from a Matrix API call, using a client API specific
61/// representation if the endpoint is from that.
62#[derive(Error, Debug)]
63pub enum RumaApiError {
64    /// A client API response error.
65    #[error(transparent)]
66    ClientApi(ruma::api::client::Error),
67
68    /// A user-interactive authentication API error.
69    ///
70    /// When registering or authenticating, the Matrix server can send a
71    /// `UiaaInfo` as the error type, this is a User-Interactive Authentication
72    /// API response. This represents an error with information about how to
73    /// authenticate the user.
74    #[error("User-Interactive Authentication required.")]
75    Uiaa(UiaaInfo),
76
77    /// Another API response error.
78    #[error(transparent)]
79    Other(ruma::api::error::MatrixError),
80}
81
82impl RumaApiError {
83    /// If `self` is `ClientApi(e)`, returns `Some(e)`.
84    ///
85    /// Otherwise, returns `None`.
86    pub fn as_client_api_error(&self) -> Option<&ruma::api::client::Error> {
87        as_variant!(self, Self::ClientApi)
88    }
89}
90
91/// An HTTP error, representing either a connection error or an error while
92/// converting the raw HTTP response into a Matrix response.
93#[derive(Error, Debug)]
94pub enum HttpError {
95    /// Error at the HTTP layer.
96    #[error(transparent)]
97    Reqwest(#[from] ReqwestError),
98
99    /// API response error (deserialization, or a Matrix-specific error).
100    // `Box` its inner value to reduce the enum size.
101    #[error(transparent)]
102    Api(#[from] Box<FromHttpResponseError<RumaApiError>>),
103
104    /// Error when creating an API request (e.g. serialization of
105    /// body/headers/query parameters).
106    #[error(transparent)]
107    IntoHttp(IntoHttpError),
108
109    /// Error while refreshing the access token.
110    #[error(transparent)]
111    RefreshToken(RefreshTokenError),
112}
113
114#[rustfmt::skip] // stop rustfmt breaking the `<code>` in docs across multiple lines
115impl HttpError {
116    /// If `self` is
117    /// <code>[Api](Self::Api)([Server](FromHttpResponseError::Server)(e))</code>,
118    /// returns `Some(e)`.
119    ///
120    /// Otherwise, returns `None`.
121    pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
122        match self {
123            Self::Api(error) => {
124                as_variant!(error.as_ref(), FromHttpResponseError::Server)
125            },
126            _ => None
127        }
128    }
129
130    /// Shorthand for
131    /// <code>.[as_ruma_api_error](Self::as_ruma_api_error)().[and_then](Option::and_then)([RumaApiError::as_client_api_error])</code>.
132    pub fn as_client_api_error(&self) -> Option<&ruma::api::client::Error> {
133        self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
134    }
135}
136
137// Another impl block that's formatted with rustfmt.
138impl HttpError {
139    /// If `self` is a server error in the `errcode` + `error` format expected
140    /// for client-API endpoints, returns the error kind (`errcode`).
141    pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
142        self.as_client_api_error().and_then(ruma::api::client::Error::error_kind)
143    }
144
145    /// Try to destructure the error into an universal interactive auth info.
146    ///
147    /// Some requests require universal interactive auth, doing such a request
148    /// will always fail the first time with a 401 status code, the response
149    /// body will contain info how the client can authenticate.
150    ///
151    /// The request will need to be retried, this time containing additional
152    /// authentication data.
153    ///
154    /// This method is an convenience method to get to the info the server
155    /// returned on the first, failed request.
156    pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
157        self.as_ruma_api_error().and_then(as_variant!(RumaApiError::Uiaa))
158    }
159
160    /// Returns whether an HTTP error response should be qualified as transient
161    /// or permanent.
162    pub(crate) fn retry_kind(&self) -> RetryKind {
163        match self {
164            // If it was a plain network error, it's either that we're disconnected from the
165            // internet, or that the remote is, so retry a few times.
166            HttpError::Reqwest(_) => RetryKind::NetworkFailure,
167
168            HttpError::Api(error) => match error.as_ref() {
169                FromHttpResponseError::Server(api_error) => RetryKind::from_api_error(api_error),
170                _ => RetryKind::Permanent,
171            },
172            _ => RetryKind::Permanent,
173        }
174    }
175}
176
177impl From<FromHttpResponseError<RumaApiError>> for HttpError {
178    fn from(value: FromHttpResponseError<RumaApiError>) -> Self {
179        Self::Api(Box::new(value))
180    }
181}
182
183/// How should we behave with respect to retry behavior after an [`HttpError`]
184/// happened?
185pub(crate) enum RetryKind {
186    /// The request failed because of an error at the network layer.
187    NetworkFailure,
188
189    /// The request failed with a "transient" error, meaning it could be retried
190    /// either soon, or after a given amount of time expressed in
191    /// `retry_after`.
192    Transient {
193        // This is used only for attempts to retry, so on non-wasm32 code (in the `native` module).
194        #[cfg_attr(target_family = "wasm", allow(dead_code))]
195        retry_after: Option<Duration>,
196    },
197
198    /// The request failed with a non-transient error, and retrying it would
199    /// likely cause the same error again, so it's not worth retrying.
200    Permanent,
201}
202
203impl RetryKind {
204    /// Construct a [`RetryKind`] from a Ruma API error.
205    ///
206    /// The Ruma API error is for errors which have the standard error response
207    /// format defined in the [spec].
208    ///
209    /// [spec]: https://spec.matrix.org/v1.11/client-server-api/#standard-error-response
210    fn from_api_error(api_error: &RumaApiError) -> Self {
211        match api_error {
212            RumaApiError::ClientApi(client_error) => match client_error.error_kind() {
213                Some(ErrorKind::LimitExceeded { retry_after }) => {
214                    RetryKind::from_retry_after(retry_after.as_ref())
215                }
216                Some(ErrorKind::Unrecognized) => RetryKind::Permanent,
217                _ => RetryKind::from_status_code(client_error.status_code),
218            },
219            RumaApiError::Other(e) => RetryKind::from_status_code(e.status_code),
220            RumaApiError::Uiaa(_) => RetryKind::Permanent,
221        }
222    }
223
224    /// Create a [`RetryKind`] if we have found a [`RetryAfter`] defined in an
225    /// error.
226    ///
227    /// This method should be used for errors where the server explicitly tells
228    /// us how long we must wait before we retry the request again.
229    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    /// Construct a [`RetryKind`] from a HTTP [`StatusCode`].
241    ///
242    /// This should be used if we don't have a more specific Matrix style error
243    /// which gives us more information about the nature of the error, i.e.
244    /// if we received an error from a reverse proxy while the Matrix
245    /// homeserver is down.
246    fn from_status_code(status_code: StatusCode) -> Self {
247        if status_code.as_u16() == 520 {
248            // Cloudflare or some other proxy server sent this, meaning the actual
249            // homeserver sent some unknown error back
250            RetryKind::Permanent
251        } else if status_code == StatusCode::TOO_MANY_REQUESTS || status_code.is_server_error() {
252            // If the status code is 429, this is requesting a retry in HTTP, without the
253            // custom `errcode`. Treat that as a retriable request with no specified
254            // retry_after delay.
255            RetryKind::Transient { retry_after: None }
256        } else {
257            RetryKind::Permanent
258        }
259    }
260}
261
262/// Internal representation of errors.
263#[derive(Error, Debug)]
264#[non_exhaustive]
265pub enum Error {
266    /// Error doing an HTTP request.
267    #[error(transparent)]
268    Http(Box<HttpError>),
269
270    /// Queried endpoint requires authentication but was called on an anonymous
271    /// client.
272    #[error("the queried endpoint requires authentication but was called before logging in")]
273    AuthenticationRequired,
274
275    /// This request failed because the local data wasn't sufficient.
276    #[error("Local cache doesn't contain all necessary data to perform the action.")]
277    InsufficientData,
278
279    /// Attempting to restore a session after the olm-machine has already been
280    /// set up fails
281    #[cfg(feature = "e2e-encryption")]
282    #[error("The olm machine has already been initialized")]
283    BadCryptoStoreState,
284
285    /// Attempting to access the olm-machine but it is not yet available.
286    #[cfg(feature = "e2e-encryption")]
287    #[error("The olm machine isn't yet available")]
288    NoOlmMachine,
289
290    /// An error de/serializing type for the `StateStore`
291    #[error(transparent)]
292    SerdeJson(#[from] JsonError),
293
294    /// An IO error happened.
295    #[error(transparent)]
296    Io(#[from] IoError),
297
298    /// An error occurred in the crypto store.
299    #[cfg(feature = "e2e-encryption")]
300    #[error(transparent)]
301    CryptoStoreError(Box<CryptoStoreError>),
302
303    /// An error occurred with a cross-process store lock.
304    #[error(transparent)]
305    CrossProcessLockError(Box<CrossProcessLockError>),
306
307    /// An error occurred during a E2EE operation.
308    #[cfg(feature = "e2e-encryption")]
309    #[error(transparent)]
310    OlmError(Box<OlmError>),
311
312    /// An error occurred during a E2EE group operation.
313    #[cfg(feature = "e2e-encryption")]
314    #[error(transparent)]
315    MegolmError(Box<MegolmError>),
316
317    /// An error occurred during decryption.
318    #[cfg(feature = "e2e-encryption")]
319    #[error(transparent)]
320    DecryptorError(#[from] DecryptorError),
321
322    /// An error occurred in the state store.
323    #[error(transparent)]
324    StateStore(Box<StoreError>),
325
326    /// An error occurred in the event cache store.
327    #[error(transparent)]
328    EventCacheStore(Box<EventCacheStoreError>),
329
330    /// An error occurred in the media store.
331    #[error(transparent)]
332    MediaStore(Box<MediaStoreError>),
333
334    /// An error encountered when trying to parse an identifier.
335    #[error(transparent)]
336    Identifier(#[from] IdParseError),
337
338    /// An error encountered when trying to parse a url.
339    #[error(transparent)]
340    Url(#[from] UrlParseError),
341
342    /// An error while scanning a QR code.
343    #[cfg(feature = "qrcode")]
344    #[error(transparent)]
345    QrCodeScanError(Box<ScanError>),
346
347    /// An error encountered when trying to parse a user tag name.
348    #[error(transparent)]
349    UserTagName(#[from] InvalidUserTagName),
350
351    /// An error occurred within sliding-sync
352    #[error(transparent)]
353    SlidingSync(Box<SlidingSyncError>),
354
355    /// Attempted to call a method on a room that requires the user to have a
356    /// specific membership state in the room, but the membership state is
357    /// different.
358    #[error("wrong room state: {0}")]
359    WrongRoomState(Box<WrongRoomState>),
360
361    /// Session callbacks have been set multiple times.
362    #[error("session callbacks have been set multiple times")]
363    MultipleSessionCallbacks,
364
365    /// An error occurred interacting with the OAuth 2.0 API.
366    #[error(transparent)]
367    OAuth(Box<OAuthError>),
368
369    /// A concurrent request to a deduplicated request has failed.
370    #[error("a concurrent request failed; see logs for details")]
371    ConcurrentRequestFailed,
372
373    /// An other error was raised.
374    ///
375    /// This might happen because encryption was enabled on the base-crate
376    /// but not here and that raised.
377    #[cfg(not(target_family = "wasm"))]
378    #[error("unknown error: {0}")]
379    UnknownError(Box<dyn std::error::Error + Send + Sync>),
380
381    /// An other error was raised.
382    #[cfg(target_family = "wasm")]
383    #[error("unknown error: {0}")]
384    UnknownError(Box<dyn std::error::Error>),
385
386    /// An error coming from the event cache subsystem.
387    #[error(transparent)]
388    EventCache(Box<EventCacheError>),
389
390    /// An item has been wedged in the send queue.
391    #[error(transparent)]
392    SendQueueWedgeError(Box<QueueWedgeError>),
393
394    /// Backups are not enabled
395    #[error("backups are not enabled")]
396    BackupNotEnabled,
397
398    /// It's forbidden to ignore your own user.
399    #[error("can't ignore the logged-in user")]
400    CantIgnoreLoggedInUser,
401
402    /// An error happened during handling of a media subrequest.
403    #[error(transparent)]
404    Media(#[from] MediaError),
405
406    /// An error happened while attempting to reply to an event.
407    #[error(transparent)]
408    ReplyError(#[from] ReplyError),
409
410    /// An error happened while attempting to change power levels.
411    #[error("power levels error: {0}")]
412    PowerLevels(#[from] PowerLevelsError),
413}
414
415#[rustfmt::skip] // stop rustfmt breaking the `<code>` in docs across multiple lines
416impl Error {
417    /// If `self` is
418    /// <code>[Http](Self::Http)([Api](HttpError::Api)([Server](FromHttpResponseError::Server)(e)))</code>,
419    /// returns `Some(e)`.
420    ///
421    /// Otherwise, returns `None`.
422    pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
423        as_variant!(self, Self::Http).and_then(|e| e.as_ruma_api_error())
424    }
425
426    /// Shorthand for
427    /// <code>.[as_ruma_api_error](Self::as_ruma_api_error)().[and_then](Option::and_then)([RumaApiError::as_client_api_error])</code>.
428    pub fn as_client_api_error(&self) -> Option<&ruma::api::client::Error> {
429        self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
430    }
431
432    /// If `self` is a server error in the `errcode` + `error` format expected
433    /// for client-API endpoints, returns the error kind (`errcode`).
434    pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
435        self.as_client_api_error().and_then(ruma::api::client::Error::error_kind)
436    }
437
438    /// Try to destructure the error into an universal interactive auth info.
439    ///
440    /// Some requests require universal interactive auth, doing such a request
441    /// will always fail the first time with a 401 status code, the response
442    /// body will contain info how the client can authenticate.
443    ///
444    /// The request will need to be retried, this time containing additional
445    /// authentication data.
446    ///
447    /// This method is an convenience method to get to the info the server
448    /// returned on the first, failed request.
449    pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
450        self.as_ruma_api_error().and_then(as_variant!(RumaApiError::Uiaa))
451    }
452}
453
454impl From<HttpError> for Error {
455    fn from(error: HttpError) -> Self {
456        Error::Http(Box::new(error))
457    }
458}
459
460#[cfg(feature = "e2e-encryption")]
461impl From<CryptoStoreError> for Error {
462    fn from(error: CryptoStoreError) -> Self {
463        Error::CryptoStoreError(Box::new(error))
464    }
465}
466
467impl From<CrossProcessLockError> for Error {
468    fn from(error: CrossProcessLockError) -> Self {
469        Error::CrossProcessLockError(Box::new(error))
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/// Error for the room key importing functionality.
537#[cfg(feature = "e2e-encryption")]
538#[derive(Error, Debug)]
539// This is allowed because key importing isn't enabled under wasm.
540#[allow(dead_code)]
541pub enum RoomKeyImportError {
542    /// An error de/serializing type for the `StateStore`
543    #[error(transparent)]
544    SerdeJson(#[from] JsonError),
545
546    /// The crypto store isn't yet open. Logging in is required to open the
547    /// crypto store.
548    #[error("The crypto store hasn't been yet opened, can't import yet.")]
549    StoreClosed,
550
551    /// An IO error happened.
552    #[error(transparent)]
553    Io(#[from] IoError),
554
555    /// An error occurred in the crypto store.
556    #[error(transparent)]
557    CryptoStore(#[from] CryptoStoreError),
558
559    /// An error occurred while importing the key export.
560    #[error(transparent)]
561    Export(#[from] KeyExportError),
562}
563
564impl From<FromHttpResponseError<ruma::api::client::Error>> for HttpError {
565    fn from(err: FromHttpResponseError<ruma::api::client::Error>) -> Self {
566        Self::Api(Box::new(err.map(RumaApiError::ClientApi)))
567    }
568}
569
570impl From<FromHttpResponseError<UiaaResponse>> for HttpError {
571    fn from(err: FromHttpResponseError<UiaaResponse>) -> Self {
572        Self::Api(Box::new(err.map(|e| match e {
573            UiaaResponse::AuthResponse(i) => RumaApiError::Uiaa(i),
574            UiaaResponse::MatrixError(e) => RumaApiError::ClientApi(e),
575        })))
576    }
577}
578
579impl From<FromHttpResponseError<ruma::api::error::MatrixError>> for HttpError {
580    fn from(err: FromHttpResponseError<ruma::api::error::MatrixError>) -> Self {
581        Self::Api(Box::new(err.map(RumaApiError::Other)))
582    }
583}
584
585impl From<SdkBaseError> for Error {
586    fn from(e: SdkBaseError) -> Self {
587        match e {
588            SdkBaseError::StateStore(e) => Self::StateStore(Box::new(e)),
589            #[cfg(feature = "e2e-encryption")]
590            SdkBaseError::CryptoStore(e) => Self::CryptoStoreError(Box::new(e)),
591            #[cfg(feature = "e2e-encryption")]
592            SdkBaseError::BadCryptoStoreState => Self::BadCryptoStoreState,
593            #[cfg(feature = "e2e-encryption")]
594            SdkBaseError::OlmError(e) => Self::OlmError(Box::new(e)),
595            #[cfg(feature = "eyre")]
596            _ => Self::UnknownError(eyre::eyre!(e).into()),
597            #[cfg(all(not(feature = "eyre"), feature = "anyhow", not(target_family = "wasm")))]
598            _ => Self::UnknownError(anyhow::anyhow!(e).into()),
599            #[cfg(all(not(feature = "eyre"), feature = "anyhow", target_family = "wasm"))]
600            _ => Self::UnknownError(e.into()),
601            #[cfg(all(
602                not(feature = "eyre"),
603                not(feature = "anyhow"),
604                not(target_family = "wasm")
605            ))]
606            _ => {
607                let e: Box<dyn std::error::Error + Send + Sync> = format!("{e:?}").into();
608                Self::UnknownError(e)
609            }
610            #[cfg(all(not(feature = "eyre"), not(feature = "anyhow"), target_family = "wasm"))]
611            _ => {
612                let e: Box<dyn std::error::Error> = format!("{e:?}").into();
613                Self::UnknownError(e)
614            }
615        }
616    }
617}
618
619impl From<ReqwestError> for Error {
620    fn from(e: ReqwestError) -> Self {
621        Error::Http(Box::new(HttpError::Reqwest(e)))
622    }
623}
624
625/// Errors that can happen when interacting with the beacon API.
626#[derive(Debug, Error)]
627pub enum BeaconError {
628    // A network error occurred.
629    #[error("Network error: {0}")]
630    Network(#[from] HttpError),
631
632    // The beacon information is not found.
633    #[error("Existing beacon information not found.")]
634    NotFound,
635
636    // The redacted event is not an error, but it's not useful for the client.
637    #[error("Beacon event is redacted and cannot be processed.")]
638    Redacted,
639
640    // The client must join the room to access the beacon information.
641    #[error("Must join the room to access beacon information.")]
642    Stripped,
643
644    // The beacon event could not be deserialized.
645    #[error("Deserialization error: {0}")]
646    Deserialization(#[from] serde_json::Error),
647
648    // The beacon event is expired.
649    #[error("The beacon event has expired.")]
650    NotLive,
651
652    // Allow for other errors to be wrapped.
653    #[error("Other error: {0}")]
654    Other(Box<Error>),
655}
656
657impl From<Error> for BeaconError {
658    fn from(err: Error) -> Self {
659        BeaconError::Other(Box::new(err))
660    }
661}
662
663/// Errors that can happen when refreshing an access token.
664///
665/// This is usually only returned by [`Client::refresh_access_token()`], unless
666/// [handling refresh tokens] is activated for the `Client`.
667///
668/// [`Client::refresh_access_token()`]: crate::Client::refresh_access_token()
669/// [handling refresh tokens]: crate::ClientBuilder::handle_refresh_tokens()
670#[derive(Debug, Error, Clone)]
671pub enum RefreshTokenError {
672    /// Tried to send a refresh token request without a refresh token.
673    #[error("missing refresh token")]
674    RefreshTokenRequired,
675
676    /// An error occurred interacting with the native Matrix authentication API.
677    #[error(transparent)]
678    MatrixAuth(Arc<HttpError>),
679
680    /// An error occurred interacting with the OAuth 2.0 API.
681    #[error(transparent)]
682    OAuth(#[from] Arc<OAuthError>),
683}
684
685/// Errors that can occur when manipulating push notification settings.
686#[derive(Debug, Error, Clone, PartialEq)]
687pub enum NotificationSettingsError {
688    /// Invalid parameter.
689    #[error("Invalid parameter `{0}`")]
690    InvalidParameter(String),
691    /// Unable to add push rule.
692    #[error("Unable to add push rule")]
693    UnableToAddPushRule,
694    /// Unable to remove push rule.
695    #[error("Unable to remove push rule")]
696    UnableToRemovePushRule,
697    /// Unable to update push rule.
698    #[error("Unable to update push rule")]
699    UnableToUpdatePushRule,
700    /// Rule not found
701    #[error("Rule `{0}` not found")]
702    RuleNotFound(String),
703    /// Unable to save the push rules
704    #[error("Unable to save push rules")]
705    UnableToSavePushRules,
706}
707
708impl NotificationSettingsError {
709    /// Whether this error is the [`RuleNotFound`](Self::RuleNotFound) variant.
710    pub fn is_rule_not_found(&self) -> bool {
711        matches!(self, Self::RuleNotFound(_))
712    }
713}
714
715impl From<InsertPushRuleError> for NotificationSettingsError {
716    fn from(_: InsertPushRuleError) -> Self {
717        Self::UnableToAddPushRule
718    }
719}
720
721impl From<RemovePushRuleError> for NotificationSettingsError {
722    fn from(_: RemovePushRuleError) -> Self {
723        Self::UnableToRemovePushRule
724    }
725}
726
727#[derive(Debug, Error)]
728#[error("expected: {expected}, got: {got:?}")]
729pub struct WrongRoomState {
730    expected: &'static str,
731    got: RoomState,
732}
733
734impl WrongRoomState {
735    pub(crate) fn new(expected: &'static str, got: RoomState) -> Self {
736        Self { expected, got }
737    }
738}