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