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