1use std::{collections::HashMap, fmt, fmt::Display, time::SystemTime};
2
3use matrix_sdk::{
4 authentication::oidc::OidcError, encryption::CryptoStoreError, event_cache::EventCacheError,
5 reqwest, room::edit::EditError, send_queue::RoomSendQueueError, HttpError, IdParseError,
6 NotificationSettingsError as SdkNotificationSettingsError,
7 QueueWedgeError as SdkQueueWedgeError, StoreError,
8};
9use matrix_sdk_ui::{encryption_sync_service, notification_client, sync_service, timeline};
10use ruma::api::client::error::{ErrorBody, ErrorKind as RumaApiErrorKind, RetryAfter};
11use uniffi::UnexpectedUniFFICallbackError;
12
13use crate::{room_list::RoomListError, timeline::FocusEventError};
14
15#[derive(Debug, thiserror::Error, uniffi::Error)]
16pub enum ClientError {
17 #[error("client error: {msg}")]
18 Generic { msg: String },
19 #[error("api error {code}: {msg}")]
20 MatrixApi { kind: ErrorKind, code: String, msg: String },
21}
22
23impl ClientError {
24 pub(crate) fn new<E: Display>(error: E) -> Self {
25 Self::Generic { msg: error.to_string() }
26 }
27}
28
29impl From<anyhow::Error> for ClientError {
30 fn from(e: anyhow::Error) -> ClientError {
31 ClientError::Generic { msg: format!("{e:#}") }
32 }
33}
34
35impl From<reqwest::Error> for ClientError {
36 fn from(e: reqwest::Error) -> Self {
37 Self::new(e)
38 }
39}
40
41impl From<UnexpectedUniFFICallbackError> for ClientError {
42 fn from(e: UnexpectedUniFFICallbackError) -> Self {
43 Self::new(e)
44 }
45}
46
47impl From<matrix_sdk::Error> for ClientError {
48 fn from(e: matrix_sdk::Error) -> Self {
49 match e {
50 matrix_sdk::Error::Http(http_error) => {
51 if let Some(api_error) = http_error.as_client_api_error() {
52 if let ErrorBody::Standard { kind, message } = &api_error.body {
53 let code = kind.errcode().to_string();
54 let Ok(kind) = kind.to_owned().try_into() else {
55 return Self::Generic { msg: message.to_string() };
57 };
58 return Self::MatrixApi { kind, code, msg: message.to_owned() };
59 }
60 }
61 Self::Generic { msg: http_error.to_string() }
62 }
63 _ => Self::Generic { msg: e.to_string() },
64 }
65 }
66}
67
68impl From<StoreError> for ClientError {
69 fn from(e: StoreError) -> Self {
70 Self::new(e)
71 }
72}
73
74impl From<CryptoStoreError> for ClientError {
75 fn from(e: CryptoStoreError) -> Self {
76 Self::new(e)
77 }
78}
79
80impl From<HttpError> for ClientError {
81 fn from(e: HttpError) -> Self {
82 Self::new(e)
83 }
84}
85
86impl From<IdParseError> for ClientError {
87 fn from(e: IdParseError) -> Self {
88 Self::new(e)
89 }
90}
91
92impl From<serde_json::Error> for ClientError {
93 fn from(e: serde_json::Error) -> Self {
94 Self::new(e)
95 }
96}
97
98impl From<url::ParseError> for ClientError {
99 fn from(e: url::ParseError) -> Self {
100 Self::new(e)
101 }
102}
103
104impl From<mime::FromStrError> for ClientError {
105 fn from(e: mime::FromStrError) -> Self {
106 Self::new(e)
107 }
108}
109
110impl From<encryption_sync_service::Error> for ClientError {
111 fn from(e: encryption_sync_service::Error) -> Self {
112 Self::new(e)
113 }
114}
115
116impl From<timeline::Error> for ClientError {
117 fn from(e: timeline::Error) -> Self {
118 Self::new(e)
119 }
120}
121
122impl From<timeline::UnsupportedEditItem> for ClientError {
123 fn from(e: timeline::UnsupportedEditItem) -> Self {
124 Self::new(e)
125 }
126}
127
128impl From<notification_client::Error> for ClientError {
129 fn from(e: notification_client::Error) -> Self {
130 Self::new(e)
131 }
132}
133
134impl From<sync_service::Error> for ClientError {
135 fn from(e: sync_service::Error) -> Self {
136 Self::new(e)
137 }
138}
139
140impl From<OidcError> for ClientError {
141 fn from(e: OidcError) -> Self {
142 Self::new(e)
143 }
144}
145
146impl From<RoomError> for ClientError {
147 fn from(e: RoomError) -> Self {
148 Self::new(e)
149 }
150}
151
152impl From<RoomListError> for ClientError {
153 fn from(e: RoomListError) -> Self {
154 Self::new(e)
155 }
156}
157
158impl From<EventCacheError> for ClientError {
159 fn from(e: EventCacheError) -> Self {
160 Self::new(e)
161 }
162}
163
164impl From<EditError> for ClientError {
165 fn from(e: EditError) -> Self {
166 Self::new(e)
167 }
168}
169
170impl From<RoomSendQueueError> for ClientError {
171 fn from(e: RoomSendQueueError) -> Self {
172 Self::new(e)
173 }
174}
175
176impl From<NotYetImplemented> for ClientError {
177 fn from(_: NotYetImplemented) -> Self {
178 Self::new("This functionality is not implemented yet.")
179 }
180}
181
182impl From<FocusEventError> for ClientError {
183 fn from(e: FocusEventError) -> Self {
184 Self::new(e)
185 }
186}
187
188#[derive(Debug, Clone, uniffi::Enum)]
197pub enum QueueWedgeError {
198 InsecureDevices {
201 user_device_map: HashMap<String, Vec<String>>,
203 },
204
205 IdentityViolations {
208 users: Vec<String>,
210 },
211
212 CrossVerificationRequired,
215
216 MissingMediaContent,
218
219 InvalidMimeType { mime_type: String },
221
222 GenericApiError { msg: String },
224}
225
226impl Display for QueueWedgeError {
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230 match self {
231 QueueWedgeError::InsecureDevices { .. } => {
232 f.write_str("There are insecure devices in the room")
233 }
234 QueueWedgeError::IdentityViolations { .. } => {
235 f.write_str("Some users that were previously verified are not anymore")
236 }
237 QueueWedgeError::CrossVerificationRequired => {
238 f.write_str("Own verification is required")
239 }
240 QueueWedgeError::MissingMediaContent => {
241 f.write_str("Media to be sent disappeared from local storage")
242 }
243 QueueWedgeError::InvalidMimeType { mime_type } => {
244 write!(f, "Invalid mime type '{mime_type}' for media upload")
245 }
246 QueueWedgeError::GenericApiError { msg } => f.write_str(msg),
247 }
248 }
249}
250
251impl From<SdkQueueWedgeError> for QueueWedgeError {
252 fn from(value: SdkQueueWedgeError) -> Self {
253 match value {
254 SdkQueueWedgeError::InsecureDevices { user_device_map } => Self::InsecureDevices {
255 user_device_map: user_device_map
256 .iter()
257 .map(|(user_id, devices)| {
258 (
259 user_id.to_string(),
260 devices.iter().map(|device_id| device_id.to_string()).collect(),
261 )
262 })
263 .collect(),
264 },
265 SdkQueueWedgeError::IdentityViolations { users } => Self::IdentityViolations {
266 users: users.iter().map(ruma::OwnedUserId::to_string).collect(),
267 },
268 SdkQueueWedgeError::CrossVerificationRequired => Self::CrossVerificationRequired,
269 SdkQueueWedgeError::MissingMediaContent => Self::MissingMediaContent,
270 SdkQueueWedgeError::InvalidMimeType { mime_type } => {
271 Self::InvalidMimeType { mime_type }
272 }
273 SdkQueueWedgeError::GenericApiError { msg } => Self::GenericApiError { msg },
274 }
275 }
276}
277
278#[derive(Debug, thiserror::Error, uniffi::Error)]
279#[uniffi(flat_error)]
280pub enum RoomError {
281 #[error("Invalid attachment data")]
282 InvalidAttachmentData,
283 #[error("Invalid attachment mime type")]
284 InvalidAttachmentMimeType,
285 #[error("Invalid media info")]
286 InvalidMediaInfo,
287 #[error("Timeline unavailable")]
288 TimelineUnavailable,
289 #[error("Invalid thumbnail data")]
290 InvalidThumbnailData,
291 #[error("Failed sending attachment")]
292 FailedSendingAttachment,
293}
294
295#[derive(Debug, thiserror::Error, uniffi::Error)]
296#[uniffi(flat_error)]
297pub enum MediaInfoError {
298 #[error("Required value missing from the media info")]
299 MissingField,
300 #[error("Media info field invalid")]
301 InvalidField,
302}
303
304#[derive(Debug, thiserror::Error, uniffi::Error)]
305pub enum NotificationSettingsError {
306 #[error("client error: {msg}")]
307 Generic { msg: String },
308 #[error("Invalid parameter: {msg}")]
310 InvalidParameter { msg: String },
311 #[error("Invalid room ID {room_id}")]
313 InvalidRoomId { room_id: String },
314 #[error("Rule not found: {rule_id}")]
316 RuleNotFound { rule_id: String },
317 #[error("Unable to add push rule")]
319 UnableToAddPushRule,
320 #[error("Unable to remove push rule")]
322 UnableToRemovePushRule,
323 #[error("Unable to save push rules")]
325 UnableToSavePushRules,
326 #[error("Unable to update push rule")]
328 UnableToUpdatePushRule,
329}
330
331impl From<SdkNotificationSettingsError> for NotificationSettingsError {
332 fn from(value: SdkNotificationSettingsError) -> Self {
333 match value {
334 SdkNotificationSettingsError::RuleNotFound(rule_id) => Self::RuleNotFound { rule_id },
335 SdkNotificationSettingsError::UnableToAddPushRule => Self::UnableToAddPushRule,
336 SdkNotificationSettingsError::UnableToRemovePushRule => Self::UnableToRemovePushRule,
337 SdkNotificationSettingsError::UnableToSavePushRules => Self::UnableToSavePushRules,
338 SdkNotificationSettingsError::InvalidParameter(msg) => Self::InvalidParameter { msg },
339 SdkNotificationSettingsError::UnableToUpdatePushRule => Self::UnableToUpdatePushRule,
340 }
341 }
342}
343
344impl From<matrix_sdk::Error> for NotificationSettingsError {
345 fn from(e: matrix_sdk::Error) -> Self {
346 Self::Generic { msg: e.to_string() }
347 }
348}
349
350#[derive(thiserror::Error, Debug)]
352#[error("not implemented yet")]
353pub struct NotYetImplemented;
354
355#[derive(Clone, Debug, PartialEq, Eq, uniffi::Enum)]
356pub enum ErrorKind {
358 BadAlias,
365
366 BadJson,
371
372 BadState,
377
378 BadStatus {
382 status: Option<u16>,
384
385 body: Option<String>,
387 },
388
389 CannotLeaveServerNoticeRoom,
396
397 CannotOverwriteMedia,
404
405 CaptchaInvalid,
409
410 CaptchaNeeded,
414
415 ConnectionFailed,
419
420 ConnectionTimeout,
424
425 DuplicateAnnotation,
431
432 Exclusive,
438
439 Forbidden,
443
444 GuestAccessForbidden,
450
451 IncompatibleRoomVersion {
456 room_version: String,
458 },
459
460 InvalidParam,
465
466 InvalidRoomState,
474
475 InvalidUsername,
479
480 LimitExceeded {
487 retry_after_ms: Option<u64>,
489 },
490
491 MissingParam,
495
496 MissingToken,
502
503 NotFound,
507
508 NotJson,
512
513 NotYetUploaded,
520
521 ResourceLimitExceeded {
528 admin_contact: String,
530 },
531
532 RoomInUse,
540
541 ServerNotTrusted,
546
547 ThreepidAuthFailed,
553
554 ThreepidDenied,
562
563 ThreepidInUse,
569
570 ThreepidMediumNotSupported,
577
578 ThreepidNotFound,
584
585 TooLarge,
589
590 UnableToAuthorizeJoin,
598
599 UnableToGrantJoin,
609
610 Unauthorized,
614
615 Unknown,
619
620 UnknownToken {
626 soft_logout: bool,
633 },
634
635 Unrecognized,
643
644 UnsupportedRoomVersion,
651
652 UrlNotSet,
656
657 UserDeactivated,
661
662 UserInUse,
666
667 UserLocked,
673
674 UserSuspended,
681
682 WeakPassword,
688
689 WrongRoomKeysVersion {
696 current_version: Option<String>,
698 },
699
700 Custom { errcode: String },
702}
703
704impl TryFrom<RumaApiErrorKind> for ErrorKind {
705 type Error = NotYetImplemented;
706 fn try_from(value: RumaApiErrorKind) -> Result<Self, Self::Error> {
707 match &value {
708 RumaApiErrorKind::BadAlias => Ok(ErrorKind::BadAlias),
709 RumaApiErrorKind::BadJson => Ok(ErrorKind::BadJson),
710 RumaApiErrorKind::BadState => Ok(ErrorKind::BadState),
711 RumaApiErrorKind::BadStatus { status, body } => Ok(ErrorKind::BadStatus {
712 status: status.map(|code| code.clone().as_u16()),
713 body: body.clone(),
714 }),
715 RumaApiErrorKind::CannotLeaveServerNoticeRoom => {
716 Ok(ErrorKind::CannotLeaveServerNoticeRoom)
717 }
718 RumaApiErrorKind::CannotOverwriteMedia => Ok(ErrorKind::CannotOverwriteMedia),
719 RumaApiErrorKind::CaptchaInvalid => Ok(ErrorKind::CaptchaInvalid),
720 RumaApiErrorKind::CaptchaNeeded => Ok(ErrorKind::CaptchaNeeded),
721 RumaApiErrorKind::ConnectionFailed => Ok(ErrorKind::ConnectionFailed),
722 RumaApiErrorKind::ConnectionTimeout => Ok(ErrorKind::ConnectionTimeout),
723 RumaApiErrorKind::DuplicateAnnotation => Ok(ErrorKind::DuplicateAnnotation),
724 RumaApiErrorKind::Exclusive => Ok(ErrorKind::Exclusive),
725 RumaApiErrorKind::Forbidden { .. } => Ok(ErrorKind::Forbidden),
726 RumaApiErrorKind::GuestAccessForbidden => Ok(ErrorKind::GuestAccessForbidden),
727 RumaApiErrorKind::IncompatibleRoomVersion { room_version } => {
728 Ok(ErrorKind::IncompatibleRoomVersion { room_version: room_version.to_string() })
729 }
730 RumaApiErrorKind::InvalidParam => Ok(ErrorKind::InvalidParam),
731 RumaApiErrorKind::InvalidRoomState => Ok(ErrorKind::InvalidRoomState),
732 RumaApiErrorKind::InvalidUsername => Ok(ErrorKind::InvalidUsername),
733 RumaApiErrorKind::LimitExceeded { retry_after } => {
734 let retry_after_ms = match retry_after {
735 Some(RetryAfter::Delay(duration)) => Some(duration.as_millis() as u64),
736 Some(RetryAfter::DateTime(system_time)) => {
737 let duration = system_time.duration_since(SystemTime::now()).ok();
738 duration.map(|duration| duration.as_millis() as u64)
739 }
740 None => None,
741 };
742 Ok(ErrorKind::LimitExceeded { retry_after_ms })
743 }
744 RumaApiErrorKind::MissingParam => Ok(ErrorKind::MissingParam),
745 RumaApiErrorKind::MissingToken => Ok(ErrorKind::MissingToken),
746 RumaApiErrorKind::NotFound => Ok(ErrorKind::NotFound),
747 RumaApiErrorKind::NotJson => Ok(ErrorKind::NotJson),
748 RumaApiErrorKind::NotYetUploaded => Ok(ErrorKind::NotYetUploaded),
749 RumaApiErrorKind::ResourceLimitExceeded { admin_contact } => {
750 Ok(ErrorKind::ResourceLimitExceeded { admin_contact: admin_contact.to_owned() })
751 }
752 RumaApiErrorKind::RoomInUse => Ok(ErrorKind::RoomInUse),
753 RumaApiErrorKind::ServerNotTrusted => Ok(ErrorKind::ServerNotTrusted),
754 RumaApiErrorKind::ThreepidAuthFailed => Ok(ErrorKind::ThreepidAuthFailed),
755 RumaApiErrorKind::ThreepidDenied => Ok(ErrorKind::ThreepidDenied),
756 RumaApiErrorKind::ThreepidInUse => Ok(ErrorKind::ThreepidInUse),
757 RumaApiErrorKind::ThreepidMediumNotSupported => {
758 Ok(ErrorKind::ThreepidMediumNotSupported)
759 }
760 RumaApiErrorKind::ThreepidNotFound => Ok(ErrorKind::ThreepidNotFound),
761 RumaApiErrorKind::TooLarge => Ok(ErrorKind::TooLarge),
762 RumaApiErrorKind::UnableToAuthorizeJoin => Ok(ErrorKind::UnableToAuthorizeJoin),
763 RumaApiErrorKind::UnableToGrantJoin => Ok(ErrorKind::UnableToGrantJoin),
764 RumaApiErrorKind::Unauthorized => Ok(ErrorKind::Unauthorized),
765 RumaApiErrorKind::Unknown => Ok(ErrorKind::Unknown),
766 RumaApiErrorKind::UnknownToken { soft_logout } => {
767 Ok(ErrorKind::UnknownToken { soft_logout: soft_logout.to_owned() })
768 }
769 RumaApiErrorKind::Unrecognized => Ok(ErrorKind::Unrecognized),
770 RumaApiErrorKind::UnsupportedRoomVersion => Ok(ErrorKind::UnsupportedRoomVersion),
771 RumaApiErrorKind::UrlNotSet => Ok(ErrorKind::UrlNotSet),
772 RumaApiErrorKind::UserDeactivated => Ok(ErrorKind::UserDeactivated),
773 RumaApiErrorKind::UserInUse => Ok(ErrorKind::UserInUse),
774 RumaApiErrorKind::UserLocked => Ok(ErrorKind::UserLocked),
775 RumaApiErrorKind::UserSuspended => Ok(ErrorKind::UserSuspended),
776 RumaApiErrorKind::WeakPassword => Ok(ErrorKind::WeakPassword),
777 RumaApiErrorKind::WrongRoomKeysVersion { current_version } => {
778 Ok(ErrorKind::WrongRoomKeysVersion { current_version: current_version.to_owned() })
779 }
780 RumaApiErrorKind::_Custom { .. } => {
781 Ok(ErrorKind::Custom { errcode: value.errcode().to_string() })
784 }
785 _ => Err(NotYetImplemented),
787 }
788 }
789}