matrix_sdk_ffi/
error.rs

1use std::{collections::HashMap, error::Error, fmt, fmt::Display};
2
3use matrix_sdk::{
4    authentication::oauth::OAuthError,
5    encryption::{identities::RequestVerificationError, CryptoStoreError},
6    event_cache::EventCacheError,
7    reqwest,
8    room::{calls::CallError, edit::EditError},
9    send_queue::RoomSendQueueError,
10    HttpError, IdParseError, NotificationSettingsError as SdkNotificationSettingsError,
11    QueueWedgeError as SdkQueueWedgeError, StoreError,
12};
13use matrix_sdk_ui::{encryption_sync_service, notification_client, spaces, sync_service, timeline};
14use ruma::{
15    api::client::error::{ErrorBody, ErrorKind as RumaApiErrorKind, RetryAfter, StandardErrorBody},
16    MilliSecondsSinceUnixEpoch,
17};
18use tracing::warn;
19use uniffi::UnexpectedUniFFICallbackError;
20
21use crate::{room_list::RoomListError, timeline::FocusEventError};
22
23#[derive(Debug, thiserror::Error, uniffi::Error)]
24pub enum ClientError {
25    #[error("client error: {msg}")]
26    Generic { msg: String, details: Option<String> },
27    #[error("api error {code}: {msg}")]
28    MatrixApi { kind: ErrorKind, code: String, msg: String, details: Option<String> },
29}
30
31impl ClientError {
32    pub(crate) fn from_str<E: Display>(error: E, details: Option<String>) -> Self {
33        warn!("Error: {error}");
34        Self::Generic { msg: error.to_string(), details }
35    }
36
37    pub(crate) fn from_err<E: Error>(e: E) -> Self {
38        let details = Some(format!("{e:?}"));
39        Self::from_str(e, details)
40    }
41}
42
43impl From<anyhow::Error> for ClientError {
44    fn from(e: anyhow::Error) -> ClientError {
45        let details = format!("{e:?}");
46        ClientError::Generic { msg: format!("{e:#}"), details: Some(details) }
47    }
48}
49
50impl From<reqwest::Error> for ClientError {
51    fn from(e: reqwest::Error) -> Self {
52        Self::from_err(e)
53    }
54}
55
56impl From<UnexpectedUniFFICallbackError> for ClientError {
57    fn from(e: UnexpectedUniFFICallbackError) -> Self {
58        Self::from_err(e)
59    }
60}
61
62impl From<matrix_sdk::Error> for ClientError {
63    fn from(e: matrix_sdk::Error) -> Self {
64        match e {
65            matrix_sdk::Error::Http(http_error) => {
66                if let Some(api_error) = http_error.as_client_api_error() {
67                    if let ErrorBody::Standard(StandardErrorBody { kind, message, .. }) =
68                        &api_error.body
69                    {
70                        let code = kind.errcode().to_string();
71                        let Ok(kind) = kind.to_owned().try_into() else {
72                            // We couldn't parse the API error, so we return a generic one instead
73                            return (*http_error).into();
74                        };
75                        return Self::MatrixApi {
76                            kind,
77                            code,
78                            msg: message.to_owned(),
79                            details: Some(format!("{api_error:?}")),
80                        };
81                    }
82                }
83                (*http_error).into()
84            }
85            _ => Self::from_err(e),
86        }
87    }
88}
89
90impl From<StoreError> for ClientError {
91    fn from(e: StoreError) -> Self {
92        Self::from_err(e)
93    }
94}
95
96impl From<CryptoStoreError> for ClientError {
97    fn from(e: CryptoStoreError) -> Self {
98        Self::from_err(e)
99    }
100}
101
102impl From<HttpError> for ClientError {
103    fn from(e: HttpError) -> Self {
104        Self::from_err(e)
105    }
106}
107
108impl From<IdParseError> for ClientError {
109    fn from(e: IdParseError) -> Self {
110        Self::from_err(e)
111    }
112}
113
114impl From<serde_json::Error> for ClientError {
115    fn from(e: serde_json::Error) -> Self {
116        Self::from_err(e)
117    }
118}
119
120impl From<url::ParseError> for ClientError {
121    fn from(e: url::ParseError) -> Self {
122        Self::from_err(e)
123    }
124}
125
126impl From<mime::FromStrError> for ClientError {
127    fn from(e: mime::FromStrError) -> Self {
128        Self::from_err(e)
129    }
130}
131
132impl From<encryption_sync_service::Error> for ClientError {
133    fn from(e: encryption_sync_service::Error) -> Self {
134        Self::from_err(e)
135    }
136}
137
138impl From<timeline::Error> for ClientError {
139    fn from(e: timeline::Error) -> Self {
140        Self::from_err(e)
141    }
142}
143
144impl From<timeline::UnsupportedEditItem> for ClientError {
145    fn from(e: timeline::UnsupportedEditItem) -> Self {
146        Self::from_err(e)
147    }
148}
149
150impl From<notification_client::Error> for ClientError {
151    fn from(e: notification_client::Error) -> Self {
152        Self::from_err(e)
153    }
154}
155
156impl From<sync_service::Error> for ClientError {
157    fn from(e: sync_service::Error) -> Self {
158        Self::from_err(e)
159    }
160}
161
162impl From<OAuthError> for ClientError {
163    fn from(e: OAuthError) -> Self {
164        Self::from_err(e)
165    }
166}
167
168impl From<RoomError> for ClientError {
169    fn from(e: RoomError) -> Self {
170        Self::from_err(e)
171    }
172}
173
174impl From<RoomListError> for ClientError {
175    fn from(e: RoomListError) -> Self {
176        Self::from_err(e)
177    }
178}
179
180impl From<EventCacheError> for ClientError {
181    fn from(e: EventCacheError) -> Self {
182        Self::from_err(e)
183    }
184}
185
186impl From<EditError> for ClientError {
187    fn from(e: EditError) -> Self {
188        Self::from_err(e)
189    }
190}
191
192impl From<CallError> for ClientError {
193    fn from(e: CallError) -> Self {
194        Self::from_err(e)
195    }
196}
197
198impl From<RoomSendQueueError> for ClientError {
199    fn from(e: RoomSendQueueError) -> Self {
200        Self::from_err(e)
201    }
202}
203
204impl From<NotYetImplemented> for ClientError {
205    fn from(_: NotYetImplemented) -> Self {
206        Self::from_str("This functionality is not implemented yet.", None)
207    }
208}
209
210impl From<FocusEventError> for ClientError {
211    fn from(e: FocusEventError) -> Self {
212        Self::from_err(e)
213    }
214}
215
216impl From<RequestVerificationError> for ClientError {
217    fn from(e: RequestVerificationError) -> Self {
218        Self::from_err(e)
219    }
220}
221
222impl From<spaces::Error> for ClientError {
223    fn from(e: spaces::Error) -> Self {
224        Self::from_err(e)
225    }
226}
227
228/// Bindings version of the sdk type replacing OwnedUserId/DeviceIds with simple
229/// String.
230///
231/// Represent a failed to send unrecoverable error of an event sent via the
232/// send_queue. It is a serializable representation of a client error, see
233/// `From` implementation for more details. These errors can not be
234/// automatically retried, but yet some manual action can be taken before retry
235/// sending. If not the only solution is to delete the local event.
236#[derive(Debug, Clone, uniffi::Enum)]
237pub enum QueueWedgeError {
238    /// This error occurs when there are some insecure devices in the room, and
239    /// the current encryption setting prohibit sharing with them.
240    InsecureDevices {
241        /// The insecure devices as a Map of userID to deviceID.
242        user_device_map: HashMap<String, Vec<String>>,
243    },
244
245    /// This error occurs when a previously verified user is not anymore, and
246    /// the current encryption setting prohibit sharing when it happens.
247    IdentityViolations {
248        /// The users that are expected to be verified but are not.
249        users: Vec<String>,
250    },
251
252    /// It is required to set up cross-signing and properly erify the current
253    /// session before sending.
254    CrossVerificationRequired,
255
256    /// Some media content to be sent has disappeared from the cache.
257    MissingMediaContent,
258
259    /// Some mime type couldn't be parsed.
260    InvalidMimeType { mime_type: String },
261
262    /// Other errors.
263    GenericApiError { msg: String },
264}
265
266/// Simple display implementation that strips out userIds/DeviceIds to avoid
267/// accidental logging.
268impl Display for QueueWedgeError {
269    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270        match self {
271            QueueWedgeError::InsecureDevices { .. } => {
272                f.write_str("There are insecure devices in the room")
273            }
274            QueueWedgeError::IdentityViolations { .. } => {
275                f.write_str("Some users that were previously verified are not anymore")
276            }
277            QueueWedgeError::CrossVerificationRequired => {
278                f.write_str("Own verification is required")
279            }
280            QueueWedgeError::MissingMediaContent => {
281                f.write_str("Media to be sent disappeared from local storage")
282            }
283            QueueWedgeError::InvalidMimeType { mime_type } => {
284                write!(f, "Invalid mime type '{mime_type}' for media upload")
285            }
286            QueueWedgeError::GenericApiError { msg } => f.write_str(msg),
287        }
288    }
289}
290
291impl From<SdkQueueWedgeError> for QueueWedgeError {
292    fn from(value: SdkQueueWedgeError) -> Self {
293        match value {
294            SdkQueueWedgeError::InsecureDevices { user_device_map } => Self::InsecureDevices {
295                user_device_map: user_device_map
296                    .iter()
297                    .map(|(user_id, devices)| {
298                        (
299                            user_id.to_string(),
300                            devices.iter().map(|device_id| device_id.to_string()).collect(),
301                        )
302                    })
303                    .collect(),
304            },
305            SdkQueueWedgeError::IdentityViolations { users } => Self::IdentityViolations {
306                users: users.iter().map(ruma::OwnedUserId::to_string).collect(),
307            },
308            SdkQueueWedgeError::CrossVerificationRequired => Self::CrossVerificationRequired,
309            SdkQueueWedgeError::MissingMediaContent => Self::MissingMediaContent,
310            SdkQueueWedgeError::InvalidMimeType { mime_type } => {
311                Self::InvalidMimeType { mime_type }
312            }
313            SdkQueueWedgeError::GenericApiError { msg } => Self::GenericApiError { msg },
314        }
315    }
316}
317
318#[derive(Debug, thiserror::Error, uniffi::Error)]
319#[uniffi(flat_error)]
320pub enum RoomError {
321    #[error("Invalid attachment data")]
322    InvalidAttachmentData,
323    #[error("Invalid attachment mime type")]
324    InvalidAttachmentMimeType,
325    #[error("Invalid media info")]
326    InvalidMediaInfo,
327    #[error("Timeline unavailable")]
328    TimelineUnavailable,
329    #[error("Invalid thumbnail data")]
330    InvalidThumbnailData,
331    #[error("Invalid replied to event ID")]
332    InvalidRepliedToEventId,
333    #[error("Failed sending attachment")]
334    FailedSendingAttachment,
335}
336
337#[derive(Debug, thiserror::Error, uniffi::Error)]
338#[uniffi(flat_error)]
339pub enum MediaInfoError {
340    #[error("Required value missing from the media info")]
341    MissingField,
342    #[error("Media info field invalid")]
343    InvalidField,
344}
345
346#[derive(Debug, thiserror::Error, uniffi::Error)]
347pub enum NotificationSettingsError {
348    #[error("client error: {msg}")]
349    Generic { msg: String },
350    /// Invalid parameter.
351    #[error("Invalid parameter: {msg}")]
352    InvalidParameter { msg: String },
353    /// Invalid room id.
354    #[error("Invalid room ID {room_id}")]
355    InvalidRoomId { room_id: String },
356    /// Rule not found
357    #[error("Rule not found: {rule_id}")]
358    RuleNotFound { rule_id: String },
359    /// Unable to add push rule.
360    #[error("Unable to add push rule")]
361    UnableToAddPushRule,
362    /// Unable to remove push rule.
363    #[error("Unable to remove push rule")]
364    UnableToRemovePushRule,
365    /// Unable to save the push rules
366    #[error("Unable to save push rules")]
367    UnableToSavePushRules,
368    /// Unable to update push rule.
369    #[error("Unable to update push rule")]
370    UnableToUpdatePushRule,
371}
372
373impl From<SdkNotificationSettingsError> for NotificationSettingsError {
374    fn from(value: SdkNotificationSettingsError) -> Self {
375        match value {
376            SdkNotificationSettingsError::RuleNotFound(rule_id) => Self::RuleNotFound { rule_id },
377            SdkNotificationSettingsError::UnableToAddPushRule => Self::UnableToAddPushRule,
378            SdkNotificationSettingsError::UnableToRemovePushRule => Self::UnableToRemovePushRule,
379            SdkNotificationSettingsError::UnableToSavePushRules => Self::UnableToSavePushRules,
380            SdkNotificationSettingsError::InvalidParameter(msg) => Self::InvalidParameter { msg },
381            SdkNotificationSettingsError::UnableToUpdatePushRule => Self::UnableToUpdatePushRule,
382        }
383    }
384}
385
386impl From<matrix_sdk::Error> for NotificationSettingsError {
387    fn from(e: matrix_sdk::Error) -> Self {
388        Self::Generic { msg: e.to_string() }
389    }
390}
391
392/// Something has not been implemented yet.
393#[derive(thiserror::Error, Debug)]
394#[error("not implemented yet")]
395pub struct NotYetImplemented;
396
397#[derive(Clone, Debug, PartialEq, Eq, uniffi::Enum)]
398// Please keep the variants sorted alphabetically.
399pub enum ErrorKind {
400    /// `M_BAD_ALIAS`
401    ///
402    /// One or more [room aliases] within the `m.room.canonical_alias` event do
403    /// not point to the room ID for which the state event is to be sent to.
404    ///
405    /// [room aliases]: https://spec.matrix.org/latest/client-server-api/#room-aliases
406    BadAlias,
407
408    /// `M_BAD_JSON`
409    ///
410    /// The request contained valid JSON, but it was malformed in some way, e.g.
411    /// missing required keys, invalid values for keys.
412    BadJson,
413
414    /// `M_BAD_STATE`
415    ///
416    /// The state change requested cannot be performed, such as attempting to
417    /// unban a user who is not banned.
418    BadState,
419
420    /// `M_BAD_STATUS`
421    ///
422    /// The application service returned a bad status.
423    BadStatus {
424        /// The HTTP status code of the response.
425        status: Option<u16>,
426
427        /// The body of the response.
428        body: Option<String>,
429    },
430
431    /// `M_CANNOT_LEAVE_SERVER_NOTICE_ROOM`
432    ///
433    /// The user is unable to reject an invite to join the [server notices]
434    /// room.
435    ///
436    /// [server notices]: https://spec.matrix.org/latest/client-server-api/#server-notices
437    CannotLeaveServerNoticeRoom,
438
439    /// `M_CANNOT_OVERWRITE_MEDIA`
440    ///
441    /// The [`create_content_async`] endpoint was called with a media ID that
442    /// already has content.
443    ///
444    /// [`create_content_async`]: crate::media::create_content_async
445    CannotOverwriteMedia,
446
447    /// `M_CAPTCHA_INVALID`
448    ///
449    /// The Captcha provided did not match what was expected.
450    CaptchaInvalid,
451
452    /// `M_CAPTCHA_NEEDED`
453    ///
454    /// A Captcha is required to complete the request.
455    CaptchaNeeded,
456
457    /// `M_CONNECTION_FAILED`
458    ///
459    /// The connection to the application service failed.
460    ConnectionFailed,
461
462    /// `M_CONNECTION_TIMEOUT`
463    ///
464    /// The connection to the application service timed out.
465    ConnectionTimeout,
466
467    /// `M_DUPLICATE_ANNOTATION`
468    ///
469    /// The request is an attempt to send a [duplicate annotation].
470    ///
471    /// [duplicate annotation]: https://spec.matrix.org/latest/client-server-api/#avoiding-duplicate-annotations
472    DuplicateAnnotation,
473
474    /// `M_EXCLUSIVE`
475    ///
476    /// The resource being requested is reserved by an application service, or
477    /// the application service making the request has not created the
478    /// resource.
479    Exclusive,
480
481    /// `M_FORBIDDEN`
482    ///
483    /// Forbidden access, e.g. joining a room without permission, failed login.
484    Forbidden,
485
486    /// `M_GUEST_ACCESS_FORBIDDEN`
487    ///
488    /// The room or resource does not permit [guests] to access it.
489    ///
490    /// [guests]: https://spec.matrix.org/latest/client-server-api/#guest-access
491    GuestAccessForbidden,
492
493    /// `M_INCOMPATIBLE_ROOM_VERSION`
494    ///
495    /// The client attempted to join a room that has a version the server does
496    /// not support.
497    IncompatibleRoomVersion {
498        /// The room's version.
499        room_version: String,
500    },
501
502    /// `M_INVALID_PARAM`
503    ///
504    /// A parameter that was specified has the wrong value. For example, the
505    /// server expected an integer and instead received a string.
506    InvalidParam,
507
508    /// `M_INVALID_ROOM_STATE`
509    ///
510    /// The initial state implied by the parameters to the [`create_room`]
511    /// request is invalid, e.g. the user's `power_level` is set below that
512    /// necessary to set the room name.
513    ///
514    /// [`create_room`]: crate::room::create_room
515    InvalidRoomState,
516
517    /// `M_INVALID_USERNAME`
518    ///
519    /// The desired user name is not valid.
520    InvalidUsername,
521
522    /// `M_LIMIT_EXCEEDED`
523    ///
524    /// The request has been refused due to [rate limiting]: too many requests
525    /// have been sent in a short period of time.
526    ///
527    /// [rate limiting]: https://spec.matrix.org/latest/client-server-api/#rate-limiting
528    LimitExceeded {
529        /// How long a client should wait before they can try again.
530        retry_after_ms: Option<u64>,
531    },
532
533    /// `M_MISSING_PARAM`
534    ///
535    /// A required parameter was missing from the request.
536    MissingParam,
537
538    /// `M_MISSING_TOKEN`
539    ///
540    /// No [access token] was specified for the request, but one is required.
541    ///
542    /// [access token]: https://spec.matrix.org/latest/client-server-api/#client-authentication
543    MissingToken,
544
545    /// `M_NOT_FOUND`
546    ///
547    /// No resource was found for this request.
548    NotFound,
549
550    /// `M_NOT_JSON`
551    ///
552    /// The request did not contain valid JSON.
553    NotJson,
554
555    /// `M_NOT_YET_UPLOADED`
556    ///
557    /// An `mxc:` URI generated with the [`create_mxc_uri`] endpoint was used
558    /// and the content is not yet available.
559    ///
560    /// [`create_mxc_uri`]: crate::media::create_mxc_uri
561    NotYetUploaded,
562
563    /// `M_RESOURCE_LIMIT_EXCEEDED`
564    ///
565    /// The request cannot be completed because the homeserver has reached a
566    /// resource limit imposed on it. For example, a homeserver held in a
567    /// shared hosting environment may reach a resource limit if it starts
568    /// using too much memory or disk space.
569    ResourceLimitExceeded {
570        /// A URI giving a contact method for the server administrator.
571        admin_contact: String,
572    },
573
574    /// `M_ROOM_IN_USE`
575    ///
576    /// The [room alias] specified in the [`create_room`] request is already
577    /// taken.
578    ///
579    /// [`create_room`]: crate::room::create_room
580    /// [room alias]: https://spec.matrix.org/latest/client-server-api/#room-aliases
581    RoomInUse,
582
583    /// `M_SERVER_NOT_TRUSTED`
584    ///
585    /// The client's request used a third-party server, e.g. identity server,
586    /// that this server does not trust.
587    ServerNotTrusted,
588
589    /// `M_THREEPID_AUTH_FAILED`
590    ///
591    /// Authentication could not be performed on the [third-party identifier].
592    ///
593    /// [third-party identifier]: https://spec.matrix.org/latest/client-server-api/#adding-account-administrative-contact-information
594    ThreepidAuthFailed,
595
596    /// `M_THREEPID_DENIED`
597    ///
598    /// The server does not permit this [third-party identifier]. This may
599    /// happen if the server only permits, for example, email addresses from
600    /// a particular domain.
601    ///
602    /// [third-party identifier]: https://spec.matrix.org/latest/client-server-api/#adding-account-administrative-contact-information
603    ThreepidDenied,
604
605    /// `M_THREEPID_IN_USE`
606    ///
607    /// The [third-party identifier] is already in use by another user.
608    ///
609    /// [third-party identifier]: https://spec.matrix.org/latest/client-server-api/#adding-account-administrative-contact-information
610    ThreepidInUse,
611
612    /// `M_THREEPID_MEDIUM_NOT_SUPPORTED`
613    ///
614    /// The homeserver does not support adding a [third-party identifier] of the
615    /// given medium.
616    ///
617    /// [third-party identifier]: https://spec.matrix.org/latest/client-server-api/#adding-account-administrative-contact-information
618    ThreepidMediumNotSupported,
619
620    /// `M_THREEPID_NOT_FOUND`
621    ///
622    /// No account matching the given [third-party identifier] could be found.
623    ///
624    /// [third-party identifier]: https://spec.matrix.org/latest/client-server-api/#adding-account-administrative-contact-information
625    ThreepidNotFound,
626
627    /// `M_TOO_LARGE`
628    ///
629    /// The request or entity was too large.
630    TooLarge,
631
632    /// `M_UNABLE_TO_AUTHORISE_JOIN`
633    ///
634    /// The room is [restricted] and none of the conditions can be validated by
635    /// the homeserver. This can happen if the homeserver does not know
636    /// about any of the rooms listed as conditions, for example.
637    ///
638    /// [restricted]: https://spec.matrix.org/latest/client-server-api/#restricted-rooms
639    UnableToAuthorizeJoin,
640
641    /// `M_UNABLE_TO_GRANT_JOIN`
642    ///
643    /// A different server should be attempted for the join. This is typically
644    /// because the resident server can see that the joining user satisfies
645    /// one or more conditions, such as in the case of [restricted rooms],
646    /// but the resident server would be unable to meet the authorization
647    /// rules.
648    ///
649    /// [restricted rooms]: https://spec.matrix.org/latest/client-server-api/#restricted-rooms
650    UnableToGrantJoin,
651
652    /// `M_UNAUTHORIZED`
653    ///
654    /// The request was not correctly authorized. Usually due to login failures.
655    Unauthorized,
656
657    /// `M_UNKNOWN`
658    ///
659    /// An unknown error has occurred.
660    Unknown,
661
662    /// `M_UNKNOWN_TOKEN`
663    ///
664    /// The [access or refresh token] specified was not recognized.
665    ///
666    /// [access or refresh token]: https://spec.matrix.org/latest/client-server-api/#client-authentication
667    UnknownToken {
668        /// If this is `true`, the client is in a "[soft logout]" state, i.e.
669        /// the server requires re-authentication but the session is not
670        /// invalidated. The client can acquire a new access token by
671        /// specifying the device ID it is already using to the login API.
672        ///
673        /// [soft logout]: https://spec.matrix.org/latest/client-server-api/#soft-logout
674        soft_logout: bool,
675    },
676
677    /// `M_UNRECOGNIZED`
678    ///
679    /// The server did not understand the request.
680    ///
681    /// This is expected to be returned with a 404 HTTP status code if the
682    /// endpoint is not implemented or a 405 HTTP status code if the
683    /// endpoint is implemented, but the incorrect HTTP method is used.
684    Unrecognized,
685
686    /// `M_UNSUPPORTED_ROOM_VERSION`
687    ///
688    /// The request to [`create_room`] used a room version that the server does
689    /// not support.
690    ///
691    /// [`create_room`]: crate::room::create_room
692    UnsupportedRoomVersion,
693
694    /// `M_URL_NOT_SET`
695    ///
696    /// The application service doesn't have a URL configured.
697    UrlNotSet,
698
699    /// `M_USER_DEACTIVATED`
700    ///
701    /// The user ID associated with the request has been deactivated.
702    UserDeactivated,
703
704    /// `M_USER_IN_USE`
705    ///
706    /// The desired user ID is already taken.
707    UserInUse,
708
709    /// `M_USER_LOCKED`
710    ///
711    /// The account has been [locked] and cannot be used at this time.
712    ///
713    /// [locked]: https://spec.matrix.org/latest/client-server-api/#account-locking
714    UserLocked,
715
716    /// `M_USER_SUSPENDED`
717    ///
718    /// The account has been [suspended] and can only be used for limited
719    /// actions at this time.
720    ///
721    /// [suspended]: https://spec.matrix.org/latest/client-server-api/#account-suspension
722    UserSuspended,
723
724    /// `M_WEAK_PASSWORD`
725    ///
726    /// The password was [rejected] by the server for being too weak.
727    ///
728    /// [rejected]: https://spec.matrix.org/latest/client-server-api/#notes-on-password-management
729    WeakPassword,
730
731    /// `M_WRONG_ROOM_KEYS_VERSION`
732    ///
733    /// The version of the [room keys backup] provided in the request does not
734    /// match the current backup version.
735    ///
736    /// [room keys backup]: https://spec.matrix.org/latest/client-server-api/#server-side-key-backups
737    WrongRoomKeysVersion {
738        /// The currently active backup version.
739        current_version: Option<String>,
740    },
741
742    /// A custom API error.
743    Custom { errcode: String },
744}
745
746impl TryFrom<RumaApiErrorKind> for ErrorKind {
747    type Error = NotYetImplemented;
748    fn try_from(value: RumaApiErrorKind) -> Result<Self, Self::Error> {
749        match &value {
750            RumaApiErrorKind::BadAlias => Ok(ErrorKind::BadAlias),
751            RumaApiErrorKind::BadJson => Ok(ErrorKind::BadJson),
752            RumaApiErrorKind::BadState => Ok(ErrorKind::BadState),
753            RumaApiErrorKind::BadStatus { status, body } => Ok(ErrorKind::BadStatus {
754                status: status.map(|code| code.clone().as_u16()),
755                body: body.clone(),
756            }),
757            RumaApiErrorKind::CannotLeaveServerNoticeRoom => {
758                Ok(ErrorKind::CannotLeaveServerNoticeRoom)
759            }
760            RumaApiErrorKind::CannotOverwriteMedia => Ok(ErrorKind::CannotOverwriteMedia),
761            RumaApiErrorKind::CaptchaInvalid => Ok(ErrorKind::CaptchaInvalid),
762            RumaApiErrorKind::CaptchaNeeded => Ok(ErrorKind::CaptchaNeeded),
763            RumaApiErrorKind::ConnectionFailed => Ok(ErrorKind::ConnectionFailed),
764            RumaApiErrorKind::ConnectionTimeout => Ok(ErrorKind::ConnectionTimeout),
765            RumaApiErrorKind::DuplicateAnnotation => Ok(ErrorKind::DuplicateAnnotation),
766            RumaApiErrorKind::Exclusive => Ok(ErrorKind::Exclusive),
767            RumaApiErrorKind::Forbidden { .. } => Ok(ErrorKind::Forbidden),
768            RumaApiErrorKind::GuestAccessForbidden => Ok(ErrorKind::GuestAccessForbidden),
769            RumaApiErrorKind::IncompatibleRoomVersion { room_version } => {
770                Ok(ErrorKind::IncompatibleRoomVersion { room_version: room_version.to_string() })
771            }
772            RumaApiErrorKind::InvalidParam => Ok(ErrorKind::InvalidParam),
773            RumaApiErrorKind::InvalidRoomState => Ok(ErrorKind::InvalidRoomState),
774            RumaApiErrorKind::InvalidUsername => Ok(ErrorKind::InvalidUsername),
775            RumaApiErrorKind::LimitExceeded { retry_after } => {
776                let retry_after_ms = match retry_after {
777                    Some(RetryAfter::Delay(duration)) => Some(duration.as_millis() as u64),
778                    Some(RetryAfter::DateTime(system_time)) => {
779                        let duration = MilliSecondsSinceUnixEpoch::now()
780                            .to_system_time()
781                            .and_then(|now| system_time.duration_since(now).ok());
782                        duration.map(|duration| duration.as_millis() as u64)
783                    }
784                    None => None,
785                };
786                Ok(ErrorKind::LimitExceeded { retry_after_ms })
787            }
788            RumaApiErrorKind::MissingParam => Ok(ErrorKind::MissingParam),
789            RumaApiErrorKind::MissingToken => Ok(ErrorKind::MissingToken),
790            RumaApiErrorKind::NotFound => Ok(ErrorKind::NotFound),
791            RumaApiErrorKind::NotJson => Ok(ErrorKind::NotJson),
792            RumaApiErrorKind::NotYetUploaded => Ok(ErrorKind::NotYetUploaded),
793            RumaApiErrorKind::ResourceLimitExceeded { admin_contact } => {
794                Ok(ErrorKind::ResourceLimitExceeded { admin_contact: admin_contact.to_owned() })
795            }
796            RumaApiErrorKind::RoomInUse => Ok(ErrorKind::RoomInUse),
797            RumaApiErrorKind::ServerNotTrusted => Ok(ErrorKind::ServerNotTrusted),
798            RumaApiErrorKind::ThreepidAuthFailed => Ok(ErrorKind::ThreepidAuthFailed),
799            RumaApiErrorKind::ThreepidDenied => Ok(ErrorKind::ThreepidDenied),
800            RumaApiErrorKind::ThreepidInUse => Ok(ErrorKind::ThreepidInUse),
801            RumaApiErrorKind::ThreepidMediumNotSupported => {
802                Ok(ErrorKind::ThreepidMediumNotSupported)
803            }
804            RumaApiErrorKind::ThreepidNotFound => Ok(ErrorKind::ThreepidNotFound),
805            RumaApiErrorKind::TooLarge => Ok(ErrorKind::TooLarge),
806            RumaApiErrorKind::UnableToAuthorizeJoin => Ok(ErrorKind::UnableToAuthorizeJoin),
807            RumaApiErrorKind::UnableToGrantJoin => Ok(ErrorKind::UnableToGrantJoin),
808            RumaApiErrorKind::Unauthorized => Ok(ErrorKind::Unauthorized),
809            RumaApiErrorKind::Unknown => Ok(ErrorKind::Unknown),
810            RumaApiErrorKind::UnknownToken { soft_logout } => {
811                Ok(ErrorKind::UnknownToken { soft_logout: soft_logout.to_owned() })
812            }
813            RumaApiErrorKind::Unrecognized => Ok(ErrorKind::Unrecognized),
814            RumaApiErrorKind::UnsupportedRoomVersion => Ok(ErrorKind::UnsupportedRoomVersion),
815            RumaApiErrorKind::UrlNotSet => Ok(ErrorKind::UrlNotSet),
816            RumaApiErrorKind::UserDeactivated => Ok(ErrorKind::UserDeactivated),
817            RumaApiErrorKind::UserInUse => Ok(ErrorKind::UserInUse),
818            RumaApiErrorKind::UserLocked => Ok(ErrorKind::UserLocked),
819            RumaApiErrorKind::UserSuspended => Ok(ErrorKind::UserSuspended),
820            RumaApiErrorKind::WeakPassword => Ok(ErrorKind::WeakPassword),
821            RumaApiErrorKind::WrongRoomKeysVersion { current_version } => {
822                Ok(ErrorKind::WrongRoomKeysVersion { current_version: current_version.to_owned() })
823            }
824            RumaApiErrorKind::_Custom { .. } => {
825                // There is no way to map the extra values since they're private, so we omit
826                // them
827                Ok(ErrorKind::Custom { errcode: value.errcode().to_string() })
828            }
829            // In any other case, return it as the mapping not being yet implemented
830            _ => Err(NotYetImplemented),
831        }
832    }
833}