1use std::{borrow::Cow, fmt, time::Duration};
2
3use js_int::UInt;
4use ruma_common::serde::JsonObject;
5use serde::{
6 de::{self, Deserialize, Deserializer, MapAccess, Visitor},
7 ser::{self, Serialize, SerializeMap, Serializer},
8};
9use serde_json::{from_value as from_json_value, map::Entry};
10
11use super::{
12 BadStatusErrorData, CustomErrorKind, ErrorCode, ErrorKind, IncompatibleRoomVersionErrorData,
13 LimitExceededErrorData, ResourceLimitExceededErrorData, RetryAfter, UnknownTokenErrorData,
14 UserLimitExceededErrorData, WrongRoomKeysVersionErrorData,
15};
16#[cfg(feature = "unstable-msc4406")]
17use crate::{OwnedUserId, api::error::SenderIgnoredErrorData};
18
19enum Field<'de> {
20 ErrorCode,
21 SoftLogout,
22 RetryAfterMs,
23 RoomVersion,
24 AdminContact,
25 Status,
26 Body,
27 CurrentVersion,
28 InfoUri,
29 CanUpgrade,
30 #[cfg(feature = "unstable-msc4406")]
31 Sender,
32 Other(Cow<'de, str>),
33}
34
35impl<'de> Field<'de> {
36 fn new(s: Cow<'de, str>) -> Field<'de> {
37 match s.as_ref() {
38 "errcode" => Self::ErrorCode,
39 "soft_logout" => Self::SoftLogout,
40 "retry_after_ms" => Self::RetryAfterMs,
41 "room_version" => Self::RoomVersion,
42 "admin_contact" => Self::AdminContact,
43 "status" => Self::Status,
44 "body" => Self::Body,
45 "current_version" => Self::CurrentVersion,
46 "info_uri" => Self::InfoUri,
47 "can_upgrade" => Self::CanUpgrade,
48 #[cfg(feature = "unstable-msc4406")]
49 "sender" => Self::Sender,
50 _ => Self::Other(s),
51 }
52 }
53}
54
55impl<'de> Deserialize<'de> for Field<'de> {
56 fn deserialize<D>(deserializer: D) -> Result<Field<'de>, D::Error>
57 where
58 D: Deserializer<'de>,
59 {
60 struct FieldVisitor;
61
62 impl<'de> Visitor<'de> for FieldVisitor {
63 type Value = Field<'de>;
64
65 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
66 formatter.write_str("any struct field")
67 }
68
69 fn visit_str<E>(self, value: &str) -> Result<Field<'de>, E>
70 where
71 E: de::Error,
72 {
73 Ok(Field::new(Cow::Owned(value.to_owned())))
74 }
75
76 fn visit_borrowed_str<E>(self, value: &'de str) -> Result<Field<'de>, E>
77 where
78 E: de::Error,
79 {
80 Ok(Field::new(Cow::Borrowed(value)))
81 }
82
83 fn visit_string<E>(self, value: String) -> Result<Field<'de>, E>
84 where
85 E: de::Error,
86 {
87 Ok(Field::new(Cow::Owned(value)))
88 }
89 }
90
91 deserializer.deserialize_identifier(FieldVisitor)
92 }
93}
94
95struct ErrorKindVisitor;
96
97impl<'de> Visitor<'de> for ErrorKindVisitor {
98 type Value = ErrorKind;
99
100 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
101 formatter.write_str("enum ErrorKind")
102 }
103
104 fn visit_map<V>(self, mut map: V) -> Result<ErrorKind, V::Error>
105 where
106 V: MapAccess<'de>,
107 {
108 let mut errcode = None;
109 let mut soft_logout = None;
110 let mut retry_after_ms = None;
111 let mut room_version = None;
112 let mut admin_contact = None;
113 let mut status = None;
114 let mut body = None;
115 let mut current_version = None;
116 let mut info_uri = None;
117 let mut can_upgrade = None;
118 #[cfg(feature = "unstable-msc4406")]
119 let mut sender = None;
120 let mut data = JsonObject::new();
121
122 macro_rules! set_field {
123 (errcode) => {
124 set_field!(@inner errcode)
125 };
126 ($field:ident) => {
127 match errcode {
128 Some(set_field!(@variant_containing $field)) | None => {
129 set_field!(@inner $field)
130 }
131 Some(_) => {
134 let _ = map.next_value::<de::IgnoredAny>()?;
135 },
136 }
137 };
138 (@variant_containing soft_logout) => { ErrorCode::UnknownToken };
139 (@variant_containing retry_after_ms) => { ErrorCode::LimitExceeded };
140 (@variant_containing room_version) => { ErrorCode::IncompatibleRoomVersion };
141 (@variant_containing admin_contact) => { ErrorCode::ResourceLimitExceeded };
142 (@variant_containing status) => { ErrorCode::BadStatus };
143 (@variant_containing body) => { ErrorCode::BadStatus };
144 (@variant_containing current_version) => { ErrorCode::WrongRoomKeysVersion };
145 (@variant_containing info_uri) => { ErrorCode::UserLimitExceeded };
146 (@variant_containing can_upgrade) => { ErrorCode::UserLimitExceeded };
147 (@variant_containing sender) => { ErrorCode::SenderIgnored };
148 (@inner $field:ident) => {
149 {
150 if $field.is_some() {
151 return Err(de::Error::duplicate_field(stringify!($field)));
152 }
153 $field = Some(map.next_value()?);
154 }
155 };
156 }
157
158 while let Some(key) = map.next_key()? {
159 match key {
160 Field::ErrorCode => set_field!(errcode),
161 Field::SoftLogout => set_field!(soft_logout),
162 Field::RetryAfterMs => set_field!(retry_after_ms),
163 Field::RoomVersion => set_field!(room_version),
164 Field::AdminContact => set_field!(admin_contact),
165 Field::Status => set_field!(status),
166 Field::Body => set_field!(body),
167 Field::CurrentVersion => set_field!(current_version),
168 Field::InfoUri => set_field!(info_uri),
169 Field::CanUpgrade => set_field!(can_upgrade),
170 #[cfg(feature = "unstable-msc4406")]
171 Field::Sender => set_field!(sender),
172 Field::Other(other) => match data.entry(other.into_owned()) {
173 Entry::Vacant(v) => {
174 v.insert(map.next_value()?);
175 }
176 Entry::Occupied(o) => {
177 return Err(de::Error::custom(format!("duplicate field `{}`", o.key())));
178 }
179 },
180 }
181 }
182
183 let errcode = errcode.ok_or_else(|| de::Error::missing_field("errcode"))?;
184
185 Ok(match errcode {
186 ErrorCode::AppserviceLoginUnsupported => ErrorKind::AppserviceLoginUnsupported,
187 ErrorCode::BadAlias => ErrorKind::BadAlias,
188 ErrorCode::BadJson => ErrorKind::BadJson,
189 ErrorCode::BadState => ErrorKind::BadState,
190 ErrorCode::BadStatus => ErrorKind::BadStatus(BadStatusErrorData {
191 status: status
192 .map(|s| {
193 from_json_value::<u16>(s)
194 .map_err(de::Error::custom)?
195 .try_into()
196 .map_err(de::Error::custom)
197 })
198 .transpose()?,
199 body: body.map(from_json_value).transpose().map_err(de::Error::custom)?,
200 }),
201 ErrorCode::CannotLeaveServerNoticeRoom => ErrorKind::CannotLeaveServerNoticeRoom,
202 ErrorCode::CannotOverwriteMedia => ErrorKind::CannotOverwriteMedia,
203 ErrorCode::CaptchaInvalid => ErrorKind::CaptchaInvalid,
204 ErrorCode::CaptchaNeeded => ErrorKind::CaptchaNeeded,
205 #[cfg(feature = "unstable-msc4388")]
206 ErrorCode::ConcurrentWrite => ErrorKind::ConcurrentWrite,
207 #[cfg(feature = "unstable-msc4306")]
208 ErrorCode::ConflictingUnsubscription => ErrorKind::ConflictingUnsubscription,
209 ErrorCode::ConnectionFailed => ErrorKind::ConnectionFailed,
210 ErrorCode::ConnectionTimeout => ErrorKind::ConnectionTimeout,
211 ErrorCode::DuplicateAnnotation => ErrorKind::DuplicateAnnotation,
212 ErrorCode::Exclusive => ErrorKind::Exclusive,
213 ErrorCode::Forbidden => ErrorKind::Forbidden,
214 ErrorCode::GuestAccessForbidden => ErrorKind::GuestAccessForbidden,
215 ErrorCode::IncompatibleRoomVersion => {
216 ErrorKind::IncompatibleRoomVersion(IncompatibleRoomVersionErrorData {
217 room_version: from_json_value(
218 room_version.ok_or_else(|| de::Error::missing_field("room_version"))?,
219 )
220 .map_err(de::Error::custom)?,
221 })
222 }
223 ErrorCode::InvalidParam => ErrorKind::InvalidParam,
224 ErrorCode::InvalidRoomState => ErrorKind::InvalidRoomState,
225 ErrorCode::InvalidUsername => ErrorKind::InvalidUsername,
226 ErrorCode::InviteBlocked => ErrorKind::InviteBlocked,
227 ErrorCode::KeyTooLarge => ErrorKind::KeyTooLarge,
228 ErrorCode::LimitExceeded => ErrorKind::LimitExceeded(LimitExceededErrorData {
229 retry_after: retry_after_ms
230 .map(from_json_value::<UInt>)
231 .transpose()
232 .map_err(de::Error::custom)?
233 .map(Into::into)
234 .map(Duration::from_millis)
235 .map(RetryAfter::Delay),
236 }),
237 ErrorCode::MissingParam => ErrorKind::MissingParam,
238 ErrorCode::MissingToken => ErrorKind::MissingToken,
239 ErrorCode::NotFound => ErrorKind::NotFound,
240 #[cfg(feature = "unstable-msc4306")]
241 ErrorCode::NotInThread => ErrorKind::NotInThread,
242 ErrorCode::NotJson => ErrorKind::NotJson,
243 ErrorCode::NotYetUploaded => ErrorKind::NotYetUploaded,
244 ErrorCode::ProfileTooLarge => ErrorKind::ProfileTooLarge,
245 ErrorCode::ResourceLimitExceeded => {
246 ErrorKind::ResourceLimitExceeded(ResourceLimitExceededErrorData {
247 admin_contact: from_json_value(
248 admin_contact.ok_or_else(|| de::Error::missing_field("admin_contact"))?,
249 )
250 .map_err(de::Error::custom)?,
251 })
252 }
253 ErrorCode::RoomInUse => ErrorKind::RoomInUse,
254 #[cfg(feature = "unstable-msc4406")]
255 ErrorCode::SenderIgnored => ErrorKind::SenderIgnored(SenderIgnoredErrorData {
256 sender: sender
257 .map(from_json_value::<Option<OwnedUserId>>)
258 .transpose()
259 .map_err(de::Error::custom)?
260 .flatten(),
261 }),
262 ErrorCode::ServerNotTrusted => ErrorKind::ServerNotTrusted,
263 ErrorCode::ThreepidAuthFailed => ErrorKind::ThreepidAuthFailed,
264 ErrorCode::ThreepidDenied => ErrorKind::ThreepidDenied,
265 ErrorCode::ThreepidInUse => ErrorKind::ThreepidInUse,
266 ErrorCode::ThreepidMediumNotSupported => ErrorKind::ThreepidMediumNotSupported,
267 ErrorCode::ThreepidNotFound => ErrorKind::ThreepidNotFound,
268 ErrorCode::TokenIncorrect => ErrorKind::TokenIncorrect,
269 ErrorCode::TooLarge => ErrorKind::TooLarge,
270 ErrorCode::UnableToAuthorizeJoin => ErrorKind::UnableToAuthorizeJoin,
271 ErrorCode::UnableToGrantJoin => ErrorKind::UnableToGrantJoin,
272 #[cfg(feature = "unstable-msc3843")]
273 ErrorCode::Unactionable => ErrorKind::Unactionable,
274 ErrorCode::Unauthorized => ErrorKind::Unauthorized,
275 ErrorCode::Unknown => ErrorKind::Unknown,
276 ErrorCode::UnknownDevice => ErrorKind::UnknownDevice,
277 #[cfg(feature = "unstable-msc4186")]
278 ErrorCode::UnknownPos => ErrorKind::UnknownPos,
279 ErrorCode::UnknownToken => ErrorKind::UnknownToken(UnknownTokenErrorData {
280 soft_logout: soft_logout
281 .map(from_json_value)
282 .transpose()
283 .map_err(de::Error::custom)?
284 .unwrap_or_default(),
285 }),
286 ErrorCode::Unrecognized => ErrorKind::Unrecognized,
287 ErrorCode::UnsupportedRoomVersion => ErrorKind::UnsupportedRoomVersion,
288 ErrorCode::UrlNotSet => ErrorKind::UrlNotSet,
289 ErrorCode::UserDeactivated => ErrorKind::UserDeactivated,
290 ErrorCode::UserInUse => ErrorKind::UserInUse,
291 ErrorCode::UserLimitExceeded => {
292 ErrorKind::UserLimitExceeded(UserLimitExceededErrorData {
293 info_uri: from_json_value(
294 info_uri.ok_or_else(|| de::Error::missing_field("info_uri"))?,
295 )
296 .map_err(de::Error::custom)?,
297 can_upgrade: can_upgrade
298 .map(from_json_value)
299 .transpose()
300 .map_err(de::Error::custom)?
301 .unwrap_or_default(),
302 })
303 }
304 ErrorCode::UserLocked => ErrorKind::UserLocked,
305 ErrorCode::UserSuspended => ErrorKind::UserSuspended,
306 ErrorCode::WeakPassword => ErrorKind::WeakPassword,
307 ErrorCode::WrongRoomKeysVersion => {
308 ErrorKind::WrongRoomKeysVersion(WrongRoomKeysVersionErrorData {
309 current_version: from_json_value(
310 current_version
311 .ok_or_else(|| de::Error::missing_field("current_version"))?,
312 )
313 .map_err(de::Error::custom)?,
314 })
315 }
316 ErrorCode::_Custom(errcode) => {
317 ErrorKind::_Custom(Box::new(CustomErrorKind { errcode: errcode.0.into(), data }))
318 }
319 })
320 }
321}
322
323impl<'de> Deserialize<'de> for ErrorKind {
324 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
325 where
326 D: Deserializer<'de>,
327 {
328 deserializer.deserialize_map(ErrorKindVisitor)
329 }
330}
331
332impl Serialize for ErrorKind {
333 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
334 where
335 S: Serializer,
336 {
337 let mut st = serializer.serialize_map(None)?;
338 st.serialize_entry("errcode", &self.errcode())?;
339 match self {
340 Self::BadStatus(BadStatusErrorData { status, body }) => {
341 if let Some(status) = status {
342 st.serialize_entry("status", &status.as_u16())?;
343 }
344 if let Some(body) = body {
345 st.serialize_entry("body", body)?;
346 }
347 }
348 Self::IncompatibleRoomVersion(IncompatibleRoomVersionErrorData { room_version }) => {
349 st.serialize_entry("room_version", room_version)?;
350 }
351 Self::LimitExceeded(LimitExceededErrorData {
352 retry_after: Some(RetryAfter::Delay(duration)),
353 }) => {
354 st.serialize_entry(
355 "retry_after_ms",
356 &UInt::try_from(duration.as_millis()).map_err(ser::Error::custom)?,
357 )?;
358 }
359 Self::ResourceLimitExceeded(ResourceLimitExceededErrorData { admin_contact }) => {
360 st.serialize_entry("admin_contact", admin_contact)?;
361 }
362 Self::UnknownToken(UnknownTokenErrorData { soft_logout: true }) | Self::UserLocked => {
363 st.serialize_entry("soft_logout", &true)?;
364 }
365 Self::UserLimitExceeded(UserLimitExceededErrorData { info_uri, can_upgrade }) => {
366 st.serialize_entry("info_uri", info_uri)?;
367
368 if *can_upgrade {
369 st.serialize_entry("can_upgrade", can_upgrade)?;
370 }
371 }
372 Self::WrongRoomKeysVersion(WrongRoomKeysVersionErrorData { current_version }) => {
373 st.serialize_entry("current_version", current_version)?;
374 }
375 #[cfg(feature = "unstable-msc4406")]
376 Self::SenderIgnored(SenderIgnoredErrorData { sender }) => {
377 if let Some(sender) = sender {
378 st.serialize_entry("sender", sender)?;
379 }
380 }
381 Self::_Custom(kind) => {
382 for (k, v) in &kind.data {
383 st.serialize_entry(k, v)?;
384 }
385 }
386 Self::AppserviceLoginUnsupported
387 | Self::BadAlias
388 | Self::BadJson
389 | Self::BadState
390 | Self::CannotLeaveServerNoticeRoom
391 | Self::CannotOverwriteMedia
392 | Self::CaptchaInvalid
393 | Self::CaptchaNeeded
394 | Self::ConnectionFailed
395 | Self::ConnectionTimeout
396 | Self::DuplicateAnnotation
397 | Self::Exclusive
398 | Self::Forbidden
399 | Self::GuestAccessForbidden
400 | Self::InvalidParam
401 | Self::InvalidRoomState
402 | Self::InvalidUsername
403 | Self::InviteBlocked
404 | Self::KeyTooLarge
405 | Self::LimitExceeded(LimitExceededErrorData {
406 retry_after: None | Some(RetryAfter::DateTime(_)),
407 })
408 | Self::MissingParam
409 | Self::MissingToken
410 | Self::NotFound
411 | Self::NotJson
412 | Self::NotYetUploaded
413 | Self::ProfileTooLarge
414 | Self::RoomInUse
415 | Self::ServerNotTrusted
416 | Self::ThreepidAuthFailed
417 | Self::ThreepidDenied
418 | Self::ThreepidInUse
419 | Self::ThreepidMediumNotSupported
420 | Self::ThreepidNotFound
421 | Self::TokenIncorrect
422 | Self::TooLarge
423 | Self::UnableToAuthorizeJoin
424 | Self::UnableToGrantJoin
425 | Self::Unauthorized
426 | Self::Unknown
427 | Self::UnknownDevice
428 | Self::UnknownToken(UnknownTokenErrorData { soft_logout: false })
429 | Self::Unrecognized
430 | Self::UnsupportedRoomVersion
431 | Self::UrlNotSet
432 | Self::UserDeactivated
433 | Self::UserInUse
434 | Self::UserSuspended
435 | Self::WeakPassword => {}
436 #[cfg(feature = "unstable-msc4306")]
437 Self::ConflictingUnsubscription => {}
438 #[cfg(feature = "unstable-msc4306")]
439 Self::NotInThread => {}
440 #[cfg(feature = "unstable-msc3843")]
441 Self::Unactionable => {}
442 #[cfg(feature = "unstable-msc4186")]
443 Self::UnknownPos => {}
444 #[cfg(feature = "unstable-msc4388")]
445 Self::ConcurrentWrite => {}
446 }
447 st.end()
448 }
449}
450
451#[cfg(test)]
452mod tests {
453 use ruma_common::room_version_id;
454 use serde_json::{from_value as from_json_value, json};
455
456 use super::{ErrorKind, IncompatibleRoomVersionErrorData};
457
458 #[test]
459 fn deserialize_forbidden() {
460 let deserialized: ErrorKind = from_json_value(json!({ "errcode": "M_FORBIDDEN" })).unwrap();
461 assert_eq!(deserialized, ErrorKind::Forbidden);
462 }
463
464 #[test]
465 fn deserialize_forbidden_with_extra_fields() {
466 let deserialized: ErrorKind = from_json_value(json!({
467 "errcode": "M_FORBIDDEN",
468 "error": "…",
469 }))
470 .unwrap();
471
472 assert_eq!(deserialized, ErrorKind::Forbidden);
473 }
474
475 #[test]
476 fn deserialize_incompatible_room_version() {
477 let deserialized: ErrorKind = from_json_value(json!({
478 "errcode": "M_INCOMPATIBLE_ROOM_VERSION",
479 "room_version": "7",
480 }))
481 .unwrap();
482
483 assert_eq!(
484 deserialized,
485 ErrorKind::IncompatibleRoomVersion(IncompatibleRoomVersionErrorData {
486 room_version: room_version_id!("7")
487 })
488 );
489 }
490}