Skip to main content

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    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
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::error::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
78impl RumaApiError {
79    /// If `self` is `ClientApi(e)`, returns `Some(e)`.
80    ///
81    /// Otherwise, returns `None`.
82    pub fn as_client_api_error(&self) -> Option<&ruma::api::error::Error> {
83        as_variant!(self, Self::ClientApi)
84    }
85}
86
87/// An HTTP error, representing either a connection error or an error while
88/// converting the raw HTTP response into a Matrix response.
89#[derive(Error, Debug)]
90pub enum HttpError {
91    /// Error at the HTTP layer.
92    #[error(transparent)]
93    Reqwest(#[from] ReqwestError),
94
95    /// API response error (deserialization, or a Matrix-specific error).
96    // `Box` its inner value to reduce the enum size.
97    #[error(transparent)]
98    Api(#[from] Box<FromHttpResponseError<RumaApiError>>),
99
100    /// Error when creating an API request (e.g. serialization of
101    /// body/headers/query parameters).
102    #[error(transparent)]
103    IntoHttp(IntoHttpError),
104
105    /// Error while refreshing the access token.
106    #[error(transparent)]
107    RefreshToken(RefreshTokenError),
108
109    /// Error creating the TLS verifier.
110    #[cfg(target_os = "android")]
111    #[error(transparent)]
112    VerifierBuilder(#[from] VerifierBuilderError),
113}
114
115#[rustfmt::skip] // stop rustfmt breaking the `<code>` in docs across multiple lines
116impl HttpError {
117    /// If `self` is
118    /// <code>[Api](Self::Api)([Server](FromHttpResponseError::Server)(e))</code>,
119    /// returns `Some(e)`.
120    ///
121    /// Otherwise, returns `None`.
122    pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
123        match self {
124            Self::Api(error) => {
125                as_variant!(error.as_ref(), FromHttpResponseError::Server)
126            },
127            _ => None
128        }
129    }
130
131    /// Shorthand for
132    /// <code>.[as_ruma_api_error](Self::as_ruma_api_error)().[and_then](Option::and_then)([RumaApiError::as_client_api_error])</code>.
133    pub fn as_client_api_error(&self) -> Option<&ruma::api::error::Error> {
134        self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
135    }
136}
137
138// Another impl block that's formatted with rustfmt.
139impl HttpError {
140    /// If `self` is a server error in the `errcode` + `error` format expected
141    /// for client-API endpoints, returns the error kind (`errcode`).
142    pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
143        self.as_client_api_error().and_then(ruma::api::error::Error::error_kind)
144    }
145
146    /// Try to destructure the error into an universal interactive auth info.
147    ///
148    /// Some requests require universal interactive auth, doing such a request
149    /// will always fail the first time with a 401 status code, the response
150    /// body will contain info how the client can authenticate.
151    ///
152    /// The request will need to be retried, this time containing additional
153    /// authentication data.
154    ///
155    /// This method is an convenience method to get to the info the server
156    /// returned on the first, failed request.
157    pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
158        self.as_ruma_api_error().and_then(as_variant!(RumaApiError::Uiaa))
159    }
160
161    /// Returns whether an HTTP error response should be qualified as transient
162    /// or permanent.
163    pub(crate) fn retry_kind(&self) -> RetryKind {
164        match self {
165            // If it was a plain network error, it's either that we're disconnected from the
166            // internet, or that the remote is, so retry a few times.
167            HttpError::Reqwest(_) => RetryKind::NetworkFailure,
168
169            HttpError::Api(error) => match error.as_ref() {
170                FromHttpResponseError::Server(api_error) => RetryKind::from_api_error(api_error),
171                _ => RetryKind::Permanent,
172            },
173            _ => RetryKind::Permanent,
174        }
175    }
176}
177
178impl From<FromHttpResponseError<RumaApiError>> for HttpError {
179    fn from(value: FromHttpResponseError<RumaApiError>) -> Self {
180        Self::Api(Box::new(value))
181    }
182}
183
184/// How should we behave with respect to retry behavior after an [`HttpError`]
185/// happened?
186pub(crate) enum RetryKind {
187    /// The request failed because of an error at the network layer.
188    NetworkFailure,
189
190    /// The request failed with a "transient" error, meaning it could be retried
191    /// either soon, or after a given amount of time expressed in
192    /// `retry_after`.
193    Transient {
194        // This is used only for attempts to retry, so on non-wasm32 code (in the `native` module).
195        #[cfg_attr(target_family = "wasm", allow(dead_code))]
196        retry_after: Option<Duration>,
197    },
198
199    /// The request failed with a non-transient error, and retrying it would
200    /// likely cause the same error again, so it's not worth retrying.
201    Permanent,
202}
203
204impl RetryKind {
205    /// Construct a [`RetryKind`] from a Ruma API error.
206    ///
207    /// The Ruma API error is for errors which have the standard error response
208    /// format defined in the [spec].
209    ///
210    /// [spec]: https://spec.matrix.org/v1.11/client-server-api/#standard-error-response
211    fn from_api_error(api_error: &RumaApiError) -> Self {
212        match api_error {
213            RumaApiError::ClientApi(client_error) => match client_error.error_kind() {
214                Some(ErrorKind::LimitExceeded(limit_exceeded)) => {
215                    RetryKind::from_retry_after(limit_exceeded.retry_after.as_ref())
216                }
217                Some(ErrorKind::Unrecognized) => RetryKind::Permanent,
218                _ => RetryKind::from_status_code(client_error.status_code),
219            },
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    /// We timed out attempting to complete an operation.
415    #[error("timed out")]
416    Timeout,
417}
418
419#[rustfmt::skip] // stop rustfmt breaking the `<code>` in docs across multiple lines
420impl Error {
421    /// If `self` is
422    /// <code>[Http](Self::Http)([Api](HttpError::Api)([Server](FromHttpResponseError::Server)(e)))</code>,
423    /// returns `Some(e)`.
424    ///
425    /// Otherwise, returns `None`.
426    pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> {
427        as_variant!(self, Self::Http).and_then(|e| e.as_ruma_api_error())
428    }
429
430    /// Shorthand for
431    /// <code>.[as_ruma_api_error](Self::as_ruma_api_error)().[and_then](Option::and_then)([RumaApiError::as_client_api_error])</code>.
432    pub fn as_client_api_error(&self) -> Option<&ruma::api::error::Error> {
433        self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
434    }
435
436    /// If `self` is a server error in the `errcode` + `error` format expected
437    /// for client-API endpoints, returns the error kind (`errcode`).
438    pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
439        self.as_client_api_error().and_then(ruma::api::error::Error::error_kind)
440    }
441
442    /// Try to destructure the error into an universal interactive auth info.
443    ///
444    /// Some requests require universal interactive auth, doing such a request
445    /// will always fail the first time with a 401 status code, the response
446    /// body will contain info how the client can authenticate.
447    ///
448    /// The request will need to be retried, this time containing additional
449    /// authentication data.
450    ///
451    /// This method is an convenience method to get to the info the server
452    /// returned on the first, failed request.
453    pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
454        self.as_ruma_api_error().and_then(as_variant!(RumaApiError::Uiaa))
455    }
456}
457
458impl From<HttpError> for Error {
459    fn from(error: HttpError) -> Self {
460        Error::Http(Box::new(error))
461    }
462}
463
464#[cfg(feature = "e2e-encryption")]
465impl From<CryptoStoreError> for Error {
466    fn from(error: CryptoStoreError) -> Self {
467        Error::CryptoStoreError(Box::new(error))
468    }
469}
470
471impl From<CrossProcessLockError> for Error {
472    fn from(error: CrossProcessLockError) -> Self {
473        Error::CrossProcessLockError(Box::new(error))
474    }
475}
476
477impl From<CrossProcessLockUnobtained> for Error {
478    fn from(error: CrossProcessLockUnobtained) -> Self {
479        CrossProcessLockError::from(error).into()
480    }
481}
482
483#[cfg(feature = "e2e-encryption")]
484impl From<OlmError> for Error {
485    fn from(error: OlmError) -> Self {
486        Error::OlmError(Box::new(error))
487    }
488}
489
490#[cfg(feature = "e2e-encryption")]
491impl From<MegolmError> for Error {
492    fn from(error: MegolmError) -> Self {
493        Error::MegolmError(Box::new(error))
494    }
495}
496
497impl From<StoreError> for Error {
498    fn from(error: StoreError) -> Self {
499        Error::StateStore(Box::new(error))
500    }
501}
502
503impl From<EventCacheStoreError> for Error {
504    fn from(error: EventCacheStoreError) -> Self {
505        Error::EventCacheStore(Box::new(error))
506    }
507}
508
509impl From<MediaStoreError> for Error {
510    fn from(error: MediaStoreError) -> Self {
511        Error::MediaStore(Box::new(error))
512    }
513}
514
515#[cfg(feature = "qrcode")]
516impl From<ScanError> for Error {
517    fn from(error: ScanError) -> Self {
518        Error::QrCodeScanError(Box::new(error))
519    }
520}
521
522impl From<SlidingSyncError> for Error {
523    fn from(error: SlidingSyncError) -> Self {
524        Error::SlidingSync(Box::new(error))
525    }
526}
527
528impl From<OAuthError> for Error {
529    fn from(error: OAuthError) -> Self {
530        Error::OAuth(Box::new(error))
531    }
532}
533
534impl From<EventCacheError> for Error {
535    fn from(error: EventCacheError) -> Self {
536        Error::EventCache(Box::new(error))
537    }
538}
539
540impl From<QueueWedgeError> for Error {
541    fn from(error: QueueWedgeError) -> Self {
542        Error::SendQueueWedgeError(Box::new(error))
543    }
544}
545
546/// Error for the room key importing functionality.
547#[cfg(feature = "e2e-encryption")]
548#[derive(Error, Debug)]
549// This is allowed because key importing isn't enabled under wasm.
550#[allow(dead_code)]
551pub enum RoomKeyImportError {
552    /// An error de/serializing type for the `StateStore`
553    #[error(transparent)]
554    SerdeJson(#[from] JsonError),
555
556    /// The crypto store isn't yet open. Logging in is required to open the
557    /// crypto store.
558    #[error("The crypto store hasn't been yet opened, can't import yet.")]
559    StoreClosed,
560
561    /// An IO error happened.
562    #[error(transparent)]
563    Io(#[from] IoError),
564
565    /// An error occurred in the crypto store.
566    #[error(transparent)]
567    CryptoStore(#[from] CryptoStoreError),
568
569    /// An error occurred while importing the key export.
570    #[error(transparent)]
571    Export(#[from] KeyExportError),
572}
573
574impl From<FromHttpResponseError<ruma::api::error::Error>> for HttpError {
575    fn from(err: FromHttpResponseError<ruma::api::error::Error>) -> Self {
576        Self::Api(Box::new(err.map(RumaApiError::ClientApi)))
577    }
578}
579
580impl From<FromHttpResponseError<UiaaResponse>> for HttpError {
581    fn from(err: FromHttpResponseError<UiaaResponse>) -> Self {
582        Self::Api(Box::new(err.map(|e| match e {
583            UiaaResponse::AuthResponse(i) => RumaApiError::Uiaa(i),
584            UiaaResponse::MatrixError(e) => RumaApiError::ClientApi(e),
585        })))
586    }
587}
588
589impl From<SdkBaseError> for Error {
590    fn from(e: SdkBaseError) -> Self {
591        match e {
592            SdkBaseError::StateStore(e) => Self::StateStore(Box::new(e)),
593            #[cfg(feature = "e2e-encryption")]
594            SdkBaseError::CryptoStore(e) => Self::CryptoStoreError(Box::new(e)),
595            #[cfg(feature = "e2e-encryption")]
596            SdkBaseError::BadCryptoStoreState => Self::BadCryptoStoreState,
597            #[cfg(feature = "e2e-encryption")]
598            SdkBaseError::OlmError(e) => Self::OlmError(Box::new(e)),
599            #[cfg(feature = "eyre")]
600            _ => Self::UnknownError(eyre::eyre!(e).into()),
601            #[cfg(all(not(feature = "eyre"), feature = "anyhow", not(target_family = "wasm")))]
602            _ => Self::UnknownError(anyhow::anyhow!(e).into()),
603            #[cfg(all(not(feature = "eyre"), feature = "anyhow", target_family = "wasm"))]
604            _ => Self::UnknownError(e.into()),
605            #[cfg(all(
606                not(feature = "eyre"),
607                not(feature = "anyhow"),
608                not(target_family = "wasm")
609            ))]
610            _ => {
611                let e: Box<dyn std::error::Error + Send + Sync> = format!("{e:?}").into();
612                Self::UnknownError(e)
613            }
614            #[cfg(all(not(feature = "eyre"), not(feature = "anyhow"), target_family = "wasm"))]
615            _ => {
616                let e: Box<dyn std::error::Error> = format!("{e:?}").into();
617                Self::UnknownError(e)
618            }
619        }
620    }
621}
622
623impl From<ReqwestError> for Error {
624    fn from(e: ReqwestError) -> Self {
625        Error::Http(Box::new(HttpError::Reqwest(e)))
626    }
627}
628
629/// Errors that can happen when interacting with the beacon API.
630#[derive(Debug, Error)]
631pub enum BeaconError {
632    // A network error occurred.
633    #[error("Network error: {0}")]
634    Network(#[from] HttpError),
635
636    // The beacon information is not found.
637    #[error("Existing beacon information not found.")]
638    NotFound,
639
640    // The redacted event is not an error, but it's not useful for the client.
641    #[error("Beacon event is redacted and cannot be processed.")]
642    Redacted,
643
644    // The client must join the room to access the beacon information.
645    #[error("Must join the room to access beacon information.")]
646    Stripped,
647
648    // The beacon event could not be deserialized.
649    #[error("Deserialization error: {0}")]
650    Deserialization(#[from] serde_json::Error),
651
652    // The beacon event is expired.
653    #[error("The beacon event has expired.")]
654    NotLive,
655
656    // Allow for other errors to be wrapped.
657    #[error("Other error: {0}")]
658    Other(Box<Error>),
659}
660
661impl From<Error> for BeaconError {
662    fn from(err: Error) -> Self {
663        BeaconError::Other(Box::new(err))
664    }
665}
666
667/// Errors that can happen when refreshing an access token.
668///
669/// This is usually only returned by [`Client::refresh_access_token()`], unless
670/// [handling refresh tokens] is activated for the `Client`.
671///
672/// [`Client::refresh_access_token()`]: crate::Client::refresh_access_token()
673/// [handling refresh tokens]: crate::ClientBuilder::handle_refresh_tokens()
674#[derive(Debug, Error, Clone)]
675pub enum RefreshTokenError {
676    /// Tried to send a refresh token request without a refresh token.
677    #[error("missing refresh token")]
678    RefreshTokenRequired,
679
680    /// An error occurred interacting with the native Matrix authentication API.
681    #[error(transparent)]
682    MatrixAuth(Arc<HttpError>),
683
684    /// An error occurred interacting with the OAuth 2.0 API.
685    #[error(transparent)]
686    OAuth(#[from] Arc<OAuthError>),
687}
688
689/// Errors that can occur when manipulating push notification settings.
690#[derive(Debug, Error, Clone, PartialEq)]
691pub enum NotificationSettingsError {
692    /// Invalid parameter.
693    #[error("Invalid parameter `{0}`")]
694    InvalidParameter(String),
695    /// Unable to add push rule.
696    #[error("Unable to add push rule")]
697    UnableToAddPushRule,
698    /// Unable to remove push rule.
699    #[error("Unable to remove push rule")]
700    UnableToRemovePushRule,
701    /// Unable to update push rule.
702    #[error("Unable to update push rule")]
703    UnableToUpdatePushRule,
704    /// Rule not found
705    #[error("Rule `{0}` not found")]
706    RuleNotFound(String),
707    /// Unable to save the push rules
708    #[error("Unable to save push rules")]
709    UnableToSavePushRules,
710}
711
712impl NotificationSettingsError {
713    /// Whether this error is the [`RuleNotFound`](Self::RuleNotFound) variant.
714    pub fn is_rule_not_found(&self) -> bool {
715        matches!(self, Self::RuleNotFound(_))
716    }
717}
718
719impl From<InsertPushRuleError> for NotificationSettingsError {
720    fn from(_: InsertPushRuleError) -> Self {
721        Self::UnableToAddPushRule
722    }
723}
724
725impl From<RemovePushRuleError> for NotificationSettingsError {
726    fn from(_: RemovePushRuleError) -> Self {
727        Self::UnableToRemovePushRule
728    }
729}
730
731#[derive(Debug, Error)]
732#[error("expected: {expected}, got: {got:?}")]
733pub struct WrongRoomState {
734    expected: &'static str,
735    got: RoomState,
736}
737
738impl WrongRoomState {
739    pub(crate) fn new(expected: &'static str, got: RoomState) -> Self {
740        Self { expected, got }
741    }
742}