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