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 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#[derive(Debug, Clone, uniffi::Enum)]
237pub enum QueueWedgeError {
238 InsecureDevices {
241 user_device_map: HashMap<String, Vec<String>>,
243 },
244
245 IdentityViolations {
248 users: Vec<String>,
250 },
251
252 CrossVerificationRequired,
255
256 MissingMediaContent,
258
259 InvalidMimeType { mime_type: String },
261
262 GenericApiError { msg: String },
264}
265
266impl 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 #[error("Invalid parameter: {msg}")]
352 InvalidParameter { msg: String },
353 #[error("Invalid room ID {room_id}")]
355 InvalidRoomId { room_id: String },
356 #[error("Rule not found: {rule_id}")]
358 RuleNotFound { rule_id: String },
359 #[error("Unable to add push rule")]
361 UnableToAddPushRule,
362 #[error("Unable to remove push rule")]
364 UnableToRemovePushRule,
365 #[error("Unable to save push rules")]
367 UnableToSavePushRules,
368 #[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#[derive(thiserror::Error, Debug)]
394#[error("not implemented yet")]
395pub struct NotYetImplemented;
396
397#[derive(Clone, Debug, PartialEq, Eq, uniffi::Enum)]
398pub enum ErrorKind {
400 BadAlias,
407
408 BadJson,
413
414 BadState,
419
420 BadStatus {
424 status: Option<u16>,
426
427 body: Option<String>,
429 },
430
431 CannotLeaveServerNoticeRoom,
438
439 CannotOverwriteMedia,
446
447 CaptchaInvalid,
451
452 CaptchaNeeded,
456
457 ConnectionFailed,
461
462 ConnectionTimeout,
466
467 DuplicateAnnotation,
473
474 Exclusive,
480
481 Forbidden,
485
486 GuestAccessForbidden,
492
493 IncompatibleRoomVersion {
498 room_version: String,
500 },
501
502 InvalidParam,
507
508 InvalidRoomState,
516
517 InvalidUsername,
521
522 LimitExceeded {
529 retry_after_ms: Option<u64>,
531 },
532
533 MissingParam,
537
538 MissingToken,
544
545 NotFound,
549
550 NotJson,
554
555 NotYetUploaded,
562
563 ResourceLimitExceeded {
570 admin_contact: String,
572 },
573
574 RoomInUse,
582
583 ServerNotTrusted,
588
589 ThreepidAuthFailed,
595
596 ThreepidDenied,
604
605 ThreepidInUse,
611
612 ThreepidMediumNotSupported,
619
620 ThreepidNotFound,
626
627 TooLarge,
631
632 UnableToAuthorizeJoin,
640
641 UnableToGrantJoin,
651
652 Unauthorized,
656
657 Unknown,
661
662 UnknownToken {
668 soft_logout: bool,
675 },
676
677 Unrecognized,
685
686 UnsupportedRoomVersion,
693
694 UrlNotSet,
698
699 UserDeactivated,
703
704 UserInUse,
708
709 UserLocked,
715
716 UserSuspended,
723
724 WeakPassword,
730
731 WrongRoomKeysVersion {
738 current_version: Option<String>,
740 },
741
742 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 Ok(ErrorKind::Custom { errcode: value.errcode().to_string() })
828 }
829 _ => Err(NotYetImplemented),
831 }
832 }
833}