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};
9#[cfg(feature = "unstable-msc4363")]
10use serde_json::Value as JsonValue;
11use serde_json::{from_value as from_json_value, map::Entry};
12
13use super::{
14 BadStatusErrorData, CustomErrorKind, ErrorCode, ErrorKind, IncompatibleRoomVersionErrorData,
15 LimitExceededErrorData, ResourceLimitExceededErrorData, RetryAfter, UnknownTokenErrorData,
16 UserLimitExceededErrorData, WrongRoomKeysVersionErrorData,
17};
18#[cfg(feature = "unstable-msc4363")]
19use crate::api::{OAuthClientScope, error::InsufficientUserAuthenticationErrorData};
20#[cfg(feature = "unstable-msc4406")]
21use crate::{OwnedUserId, api::error::SenderIgnoredErrorData};
22
23enum Field<'de> {
24 ErrorCode,
25 SoftLogout,
26 RetryAfterMs,
27 RoomVersion,
28 AdminContact,
29 Status,
30 Body,
31 CurrentVersion,
32 InfoUri,
33 CanUpgrade,
34 #[cfg(feature = "unstable-msc4406")]
35 Sender,
36 #[cfg(feature = "unstable-msc4363")]
37 AcrValues,
38 #[cfg(feature = "unstable-msc4363")]
39 MaxAge,
40 #[cfg(feature = "unstable-msc4363")]
41 Scope,
42 Other(Cow<'de, str>),
43}
44
45impl<'de> Field<'de> {
46 fn new(s: Cow<'de, str>) -> Field<'de> {
47 match s.as_ref() {
48 "errcode" => Self::ErrorCode,
49 "soft_logout" => Self::SoftLogout,
50 "retry_after_ms" => Self::RetryAfterMs,
51 "room_version" => Self::RoomVersion,
52 "admin_contact" => Self::AdminContact,
53 "status" => Self::Status,
54 "body" => Self::Body,
55 "current_version" => Self::CurrentVersion,
56 "info_uri" => Self::InfoUri,
57 "can_upgrade" => Self::CanUpgrade,
58 #[cfg(feature = "unstable-msc4406")]
59 "sender" => Self::Sender,
60 #[cfg(feature = "unstable-msc4363")]
61 "org.matrix.msc4363.acr_values" => Self::AcrValues,
62 #[cfg(feature = "unstable-msc4363")]
63 "org.matrix.msc4363.max_age" => Self::MaxAge,
64 #[cfg(feature = "unstable-msc4363")]
65 "org.matrix.msc4363.scope" => Self::Scope,
66 _ => Self::Other(s),
67 }
68 }
69}
70
71impl<'de> Deserialize<'de> for Field<'de> {
72 fn deserialize<D>(deserializer: D) -> Result<Field<'de>, D::Error>
73 where
74 D: Deserializer<'de>,
75 {
76 struct FieldVisitor;
77
78 impl<'de> Visitor<'de> for FieldVisitor {
79 type Value = Field<'de>;
80
81 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
82 formatter.write_str("any struct field")
83 }
84
85 fn visit_str<E>(self, value: &str) -> Result<Field<'de>, E>
86 where
87 E: de::Error,
88 {
89 Ok(Field::new(Cow::Owned(value.to_owned())))
90 }
91
92 fn visit_borrowed_str<E>(self, value: &'de str) -> Result<Field<'de>, E>
93 where
94 E: de::Error,
95 {
96 Ok(Field::new(Cow::Borrowed(value)))
97 }
98
99 fn visit_string<E>(self, value: String) -> Result<Field<'de>, E>
100 where
101 E: de::Error,
102 {
103 Ok(Field::new(Cow::Owned(value)))
104 }
105 }
106
107 deserializer.deserialize_identifier(FieldVisitor)
108 }
109}
110
111struct ErrorKindVisitor;
112
113impl<'de> Visitor<'de> for ErrorKindVisitor {
114 type Value = ErrorKind;
115
116 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
117 formatter.write_str("enum ErrorKind")
118 }
119
120 fn visit_map<V>(self, mut map: V) -> Result<ErrorKind, V::Error>
121 where
122 V: MapAccess<'de>,
123 {
124 let mut errcode = None;
125 let mut soft_logout = None;
126 let mut retry_after_ms = None;
127 let mut room_version = None;
128 let mut admin_contact = None;
129 let mut status = None;
130 let mut body = None;
131 let mut current_version = None;
132 let mut info_uri = None;
133 let mut can_upgrade = None;
134 #[cfg(feature = "unstable-msc4406")]
135 let mut sender = None;
136 let mut data = JsonObject::new();
137 #[cfg(feature = "unstable-msc4363")]
138 let mut acr_values: Option<JsonValue> = None;
139 #[cfg(feature = "unstable-msc4363")]
140 let mut max_age = None;
141 #[cfg(feature = "unstable-msc4363")]
142 let mut scope: Option<JsonValue> = None;
143
144 macro_rules! set_field {
145 (errcode) => {
146 set_field!(@inner errcode)
147 };
148 ($field:ident) => {
149 match errcode {
150 Some(set_field!(@variant_containing $field)) | None => {
151 set_field!(@inner $field)
152 }
153 Some(_) => {
156 let _ = map.next_value::<de::IgnoredAny>()?;
157 },
158 }
159 };
160 (@variant_containing soft_logout) => { ErrorCode::UnknownToken };
161 (@variant_containing retry_after_ms) => { ErrorCode::LimitExceeded };
162 (@variant_containing room_version) => { ErrorCode::IncompatibleRoomVersion };
163 (@variant_containing admin_contact) => { ErrorCode::ResourceLimitExceeded };
164 (@variant_containing status) => { ErrorCode::BadStatus };
165 (@variant_containing body) => { ErrorCode::BadStatus };
166 (@variant_containing current_version) => { ErrorCode::WrongRoomKeysVersion };
167 (@variant_containing info_uri) => { ErrorCode::UserLimitExceeded };
168 (@variant_containing can_upgrade) => { ErrorCode::UserLimitExceeded };
169 (@variant_containing sender) => { ErrorCode::SenderIgnored };
170 (@variant_containing acr_values) => { ErrorCode::InsufficientUserAuthentication };
171 (@variant_containing max_age) => { ErrorCode::InsufficientUserAuthentication };
172 (@variant_containing scope) => { ErrorCode::InsufficientUserAuthentication };
173 (@inner $field:ident) => {
174 {
175 if $field.is_some() {
176 return Err(de::Error::duplicate_field(stringify!($field)));
177 }
178 $field = Some(map.next_value()?);
179 }
180 };
181 }
182
183 while let Some(key) = map.next_key()? {
184 match key {
185 Field::ErrorCode => set_field!(errcode),
186 Field::SoftLogout => set_field!(soft_logout),
187 Field::RetryAfterMs => set_field!(retry_after_ms),
188 Field::RoomVersion => set_field!(room_version),
189 Field::AdminContact => set_field!(admin_contact),
190 Field::Status => set_field!(status),
191 Field::Body => set_field!(body),
192 Field::CurrentVersion => set_field!(current_version),
193 Field::InfoUri => set_field!(info_uri),
194 Field::CanUpgrade => set_field!(can_upgrade),
195 #[cfg(feature = "unstable-msc4406")]
196 Field::Sender => set_field!(sender),
197 #[cfg(feature = "unstable-msc4363")]
198 Field::AcrValues => set_field!(acr_values),
199 #[cfg(feature = "unstable-msc4363")]
200 Field::MaxAge => set_field!(max_age),
201 #[cfg(feature = "unstable-msc4363")]
202 Field::Scope => set_field!(scope),
203 Field::Other(other) => match data.entry(other.into_owned()) {
204 Entry::Vacant(v) => {
205 v.insert(map.next_value()?);
206 }
207 Entry::Occupied(o) => {
208 return Err(de::Error::custom(format!("duplicate field `{}`", o.key())));
209 }
210 },
211 }
212 }
213
214 let errcode = errcode.ok_or_else(|| de::Error::missing_field("errcode"))?;
215
216 Ok(match errcode {
217 ErrorCode::AppserviceLoginUnsupported => ErrorKind::AppserviceLoginUnsupported,
218 ErrorCode::BadAlias => ErrorKind::BadAlias,
219 ErrorCode::BadJson => ErrorKind::BadJson,
220 ErrorCode::BadState => ErrorKind::BadState,
221 ErrorCode::BadStatus => ErrorKind::BadStatus(BadStatusErrorData {
222 status: status
223 .map(|s| {
224 from_json_value::<u16>(s)
225 .map_err(de::Error::custom)?
226 .try_into()
227 .map_err(de::Error::custom)
228 })
229 .transpose()?,
230 body: body.map(from_json_value).transpose().map_err(de::Error::custom)?,
231 }),
232 ErrorCode::CannotLeaveServerNoticeRoom => ErrorKind::CannotLeaveServerNoticeRoom,
233 ErrorCode::CannotOverwriteMedia => ErrorKind::CannotOverwriteMedia,
234 ErrorCode::CaptchaInvalid => ErrorKind::CaptchaInvalid,
235 ErrorCode::CaptchaNeeded => ErrorKind::CaptchaNeeded,
236 #[cfg(feature = "unstable-msc4388")]
237 ErrorCode::ConcurrentWrite => ErrorKind::ConcurrentWrite,
238 #[cfg(feature = "unstable-msc4306")]
239 ErrorCode::ConflictingUnsubscription => ErrorKind::ConflictingUnsubscription,
240 ErrorCode::ConnectionFailed => ErrorKind::ConnectionFailed,
241 ErrorCode::ConnectionTimeout => ErrorKind::ConnectionTimeout,
242 ErrorCode::DuplicateAnnotation => ErrorKind::DuplicateAnnotation,
243 ErrorCode::Exclusive => ErrorKind::Exclusive,
244 ErrorCode::Forbidden => ErrorKind::Forbidden,
245 ErrorCode::GuestAccessForbidden => ErrorKind::GuestAccessForbidden,
246 ErrorCode::IncompatibleRoomVersion => {
247 ErrorKind::IncompatibleRoomVersion(IncompatibleRoomVersionErrorData {
248 room_version: from_json_value(
249 room_version.ok_or_else(|| de::Error::missing_field("room_version"))?,
250 )
251 .map_err(de::Error::custom)?,
252 })
253 }
254 #[cfg(feature = "unstable-msc4363")]
255 ErrorCode::InsufficientUserAuthentication => {
256 use crate::{
257 Acr,
258 api::{OAuthClientScope, error::InsufficientUserAuthenticationErrorData},
259 };
260
261 ErrorKind::InsufficientUserAuthentication(Box::new(
262 InsufficientUserAuthenticationErrorData {
263 acr_values: acr_values
264 .clone()
265 .map(from_json_value::<String>)
266 .transpose()
267 .map_err(de::Error::custom)?
268 .map(|value| value.split(" ").map(Acr::parse).collect())
269 .transpose()
270 .map_err(de::Error::custom)?
271 .unwrap_or_default(),
272 max_age: max_age
273 .map(from_json_value::<UInt>)
274 .transpose()
275 .map_err(de::Error::custom)?
276 .map(Into::into)
277 .map(Duration::from_secs),
278 scope: scope
279 .clone()
280 .map(from_json_value::<String>)
281 .transpose()
282 .map_err(de::Error::custom)?
283 .map(|value| value.split(" ").map(OAuthClientScope::try_from).collect())
284 .transpose()
285 .map_err(de::Error::custom)?
286 .unwrap_or_default(),
287 },
288 ))
289 }
290 ErrorCode::InvalidParam => ErrorKind::InvalidParam,
291 ErrorCode::InvalidRoomState => ErrorKind::InvalidRoomState,
292 ErrorCode::InvalidUsername => ErrorKind::InvalidUsername,
293 ErrorCode::InviteBlocked => ErrorKind::InviteBlocked,
294 ErrorCode::KeyTooLarge => ErrorKind::KeyTooLarge,
295 ErrorCode::LimitExceeded => ErrorKind::LimitExceeded(LimitExceededErrorData {
296 retry_after: retry_after_ms
297 .map(from_json_value::<UInt>)
298 .transpose()
299 .map_err(de::Error::custom)?
300 .map(Into::into)
301 .map(Duration::from_millis)
302 .map(RetryAfter::Delay),
303 }),
304 ErrorCode::MissingParam => ErrorKind::MissingParam,
305 ErrorCode::MissingToken => ErrorKind::MissingToken,
306 ErrorCode::NotFound => ErrorKind::NotFound,
307 #[cfg(feature = "unstable-msc4306")]
308 ErrorCode::NotInThread => ErrorKind::NotInThread,
309 ErrorCode::NotJson => ErrorKind::NotJson,
310 ErrorCode::NotYetUploaded => ErrorKind::NotYetUploaded,
311 ErrorCode::ProfileTooLarge => ErrorKind::ProfileTooLarge,
312 ErrorCode::ResourceLimitExceeded => {
313 ErrorKind::ResourceLimitExceeded(ResourceLimitExceededErrorData {
314 admin_contact: from_json_value(
315 admin_contact.ok_or_else(|| de::Error::missing_field("admin_contact"))?,
316 )
317 .map_err(de::Error::custom)?,
318 })
319 }
320 ErrorCode::RoomInUse => ErrorKind::RoomInUse,
321 #[cfg(feature = "unstable-msc4406")]
322 ErrorCode::SenderIgnored => ErrorKind::SenderIgnored(SenderIgnoredErrorData {
323 sender: sender
324 .map(from_json_value::<Option<OwnedUserId>>)
325 .transpose()
326 .map_err(de::Error::custom)?
327 .flatten(),
328 }),
329 ErrorCode::ServerNotTrusted => ErrorKind::ServerNotTrusted,
330 ErrorCode::ThreepidAuthFailed => ErrorKind::ThreepidAuthFailed,
331 ErrorCode::ThreepidDenied => ErrorKind::ThreepidDenied,
332 ErrorCode::ThreepidInUse => ErrorKind::ThreepidInUse,
333 ErrorCode::ThreepidMediumNotSupported => ErrorKind::ThreepidMediumNotSupported,
334 ErrorCode::ThreepidNotFound => ErrorKind::ThreepidNotFound,
335 ErrorCode::TokenIncorrect => ErrorKind::TokenIncorrect,
336 ErrorCode::TooLarge => ErrorKind::TooLarge,
337 ErrorCode::UnableToAuthorizeJoin => ErrorKind::UnableToAuthorizeJoin,
338 ErrorCode::UnableToGrantJoin => ErrorKind::UnableToGrantJoin,
339 #[cfg(feature = "unstable-msc3843")]
340 ErrorCode::Unactionable => ErrorKind::Unactionable,
341 ErrorCode::Unauthorized => ErrorKind::Unauthorized,
342 ErrorCode::Unknown => ErrorKind::Unknown,
343 ErrorCode::UnknownDevice => ErrorKind::UnknownDevice,
344 #[cfg(feature = "unstable-msc4186")]
345 ErrorCode::UnknownPos => ErrorKind::UnknownPos,
346 ErrorCode::UnknownToken => ErrorKind::UnknownToken(UnknownTokenErrorData {
347 soft_logout: soft_logout
348 .map(from_json_value)
349 .transpose()
350 .map_err(de::Error::custom)?
351 .unwrap_or_default(),
352 }),
353 ErrorCode::Unrecognized => ErrorKind::Unrecognized,
354 ErrorCode::UnsupportedRoomVersion => ErrorKind::UnsupportedRoomVersion,
355 ErrorCode::UrlNotSet => ErrorKind::UrlNotSet,
356 ErrorCode::UserDeactivated => ErrorKind::UserDeactivated,
357 ErrorCode::UserInUse => ErrorKind::UserInUse,
358 ErrorCode::UserLimitExceeded => {
359 ErrorKind::UserLimitExceeded(UserLimitExceededErrorData {
360 info_uri: from_json_value(
361 info_uri.ok_or_else(|| de::Error::missing_field("info_uri"))?,
362 )
363 .map_err(de::Error::custom)?,
364 can_upgrade: can_upgrade
365 .map(from_json_value)
366 .transpose()
367 .map_err(de::Error::custom)?
368 .unwrap_or_default(),
369 })
370 }
371 ErrorCode::UserLocked => ErrorKind::UserLocked,
372 ErrorCode::UserSuspended => ErrorKind::UserSuspended,
373 ErrorCode::WeakPassword => ErrorKind::WeakPassword,
374 ErrorCode::WrongRoomKeysVersion => {
375 ErrorKind::WrongRoomKeysVersion(WrongRoomKeysVersionErrorData {
376 current_version: from_json_value(
377 current_version
378 .ok_or_else(|| de::Error::missing_field("current_version"))?,
379 )
380 .map_err(de::Error::custom)?,
381 })
382 }
383 ErrorCode::_Custom(errcode) => {
384 ErrorKind::_Custom(Box::new(CustomErrorKind { errcode: errcode.0.into(), data }))
385 }
386 })
387 }
388}
389
390impl<'de> Deserialize<'de> for ErrorKind {
391 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
392 where
393 D: Deserializer<'de>,
394 {
395 deserializer.deserialize_map(ErrorKindVisitor)
396 }
397}
398
399impl Serialize for ErrorKind {
400 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
401 where
402 S: Serializer,
403 {
404 let mut st = serializer.serialize_map(None)?;
405 st.serialize_entry("errcode", &self.errcode())?;
406 match self {
407 Self::BadStatus(BadStatusErrorData { status, body }) => {
408 if let Some(status) = status {
409 st.serialize_entry("status", &status.as_u16())?;
410 }
411 if let Some(body) = body {
412 st.serialize_entry("body", body)?;
413 }
414 }
415 Self::IncompatibleRoomVersion(IncompatibleRoomVersionErrorData { room_version }) => {
416 st.serialize_entry("room_version", room_version)?;
417 }
418 #[cfg(feature = "unstable-msc4363")]
419 Self::InsufficientUserAuthentication(data) => {
420 let InsufficientUserAuthenticationErrorData { acr_values, max_age, scope } =
421 &**data;
422
423 if !acr_values.is_empty() {
424 st.serialize_entry("org.matrix.msc4363.acr_values", &acr_values.join(" "))?;
425 }
426
427 if let Some(max_age) = max_age {
428 st.serialize_entry(
429 "org.matrix.msc4363.max_age",
430 &UInt::try_from(max_age.as_secs()).map_err(ser::Error::custom)?,
431 )?;
432 }
433
434 if !scope.is_empty() {
435 st.serialize_entry(
436 "org.matrix.msc4363.scope",
437 &scope.iter().map(OAuthClientScope::as_str).collect::<Vec<_>>().join(" "),
440 )?;
441 }
442 }
443 Self::LimitExceeded(LimitExceededErrorData {
444 retry_after: Some(RetryAfter::Delay(duration)),
445 }) => {
446 st.serialize_entry(
447 "retry_after_ms",
448 &UInt::try_from(duration.as_millis()).map_err(ser::Error::custom)?,
449 )?;
450 }
451 Self::ResourceLimitExceeded(ResourceLimitExceededErrorData { admin_contact }) => {
452 st.serialize_entry("admin_contact", admin_contact)?;
453 }
454 Self::UnknownToken(UnknownTokenErrorData { soft_logout: true }) | Self::UserLocked => {
455 st.serialize_entry("soft_logout", &true)?;
456 }
457 Self::UserLimitExceeded(UserLimitExceededErrorData { info_uri, can_upgrade }) => {
458 st.serialize_entry("info_uri", info_uri)?;
459
460 if *can_upgrade {
461 st.serialize_entry("can_upgrade", can_upgrade)?;
462 }
463 }
464 Self::WrongRoomKeysVersion(WrongRoomKeysVersionErrorData { current_version }) => {
465 st.serialize_entry("current_version", current_version)?;
466 }
467 #[cfg(feature = "unstable-msc4406")]
468 Self::SenderIgnored(SenderIgnoredErrorData { sender }) => {
469 if let Some(sender) = sender {
470 st.serialize_entry("sender", sender)?;
471 }
472 }
473 Self::_Custom(kind) => {
474 for (k, v) in &kind.data {
475 st.serialize_entry(k, v)?;
476 }
477 }
478 Self::AppserviceLoginUnsupported
479 | Self::BadAlias
480 | Self::BadJson
481 | Self::BadState
482 | Self::CannotLeaveServerNoticeRoom
483 | Self::CannotOverwriteMedia
484 | Self::CaptchaInvalid
485 | Self::CaptchaNeeded
486 | Self::ConnectionFailed
487 | Self::ConnectionTimeout
488 | Self::DuplicateAnnotation
489 | Self::Exclusive
490 | Self::Forbidden
491 | Self::GuestAccessForbidden
492 | Self::InvalidParam
493 | Self::InvalidRoomState
494 | Self::InvalidUsername
495 | Self::InviteBlocked
496 | Self::KeyTooLarge
497 | Self::LimitExceeded(LimitExceededErrorData {
498 retry_after: None | Some(RetryAfter::DateTime(_)),
499 })
500 | Self::MissingParam
501 | Self::MissingToken
502 | Self::NotFound
503 | Self::NotJson
504 | Self::NotYetUploaded
505 | Self::ProfileTooLarge
506 | Self::RoomInUse
507 | Self::ServerNotTrusted
508 | Self::ThreepidAuthFailed
509 | Self::ThreepidDenied
510 | Self::ThreepidInUse
511 | Self::ThreepidMediumNotSupported
512 | Self::ThreepidNotFound
513 | Self::TokenIncorrect
514 | Self::TooLarge
515 | Self::UnableToAuthorizeJoin
516 | Self::UnableToGrantJoin
517 | Self::Unauthorized
518 | Self::Unknown
519 | Self::UnknownDevice
520 | Self::UnknownToken(UnknownTokenErrorData { soft_logout: false })
521 | Self::Unrecognized
522 | Self::UnsupportedRoomVersion
523 | Self::UrlNotSet
524 | Self::UserDeactivated
525 | Self::UserInUse
526 | Self::UserSuspended
527 | Self::WeakPassword => {}
528 #[cfg(feature = "unstable-msc4306")]
529 Self::ConflictingUnsubscription => {}
530 #[cfg(feature = "unstable-msc4306")]
531 Self::NotInThread => {}
532 #[cfg(feature = "unstable-msc3843")]
533 Self::Unactionable => {}
534 #[cfg(feature = "unstable-msc4186")]
535 Self::UnknownPos => {}
536 #[cfg(feature = "unstable-msc4388")]
537 Self::ConcurrentWrite => {}
538 }
539 st.end()
540 }
541}
542
543#[cfg(test)]
544mod tests {
545 use ruma_common::room_version_id;
546 use serde_json::{from_value as from_json_value, json};
547
548 use super::{ErrorKind, IncompatibleRoomVersionErrorData};
549
550 #[test]
551 fn deserialize_forbidden() {
552 let deserialized: ErrorKind = from_json_value(json!({ "errcode": "M_FORBIDDEN" })).unwrap();
553 assert_eq!(deserialized, ErrorKind::Forbidden);
554 }
555
556 #[test]
557 fn deserialize_forbidden_with_extra_fields() {
558 let deserialized: ErrorKind = from_json_value(json!({
559 "errcode": "M_FORBIDDEN",
560 "error": "…",
561 }))
562 .unwrap();
563
564 assert_eq!(deserialized, ErrorKind::Forbidden);
565 }
566
567 #[test]
568 fn deserialize_incompatible_room_version() {
569 let deserialized: ErrorKind = from_json_value(json!({
570 "errcode": "M_INCOMPATIBLE_ROOM_VERSION",
571 "room_version": "7",
572 }))
573 .unwrap();
574
575 assert_eq!(
576 deserialized,
577 ErrorKind::IncompatibleRoomVersion(IncompatibleRoomVersionErrorData {
578 room_version: room_version_id!("7")
579 })
580 );
581 }
582
583 #[test]
584 #[cfg(feature = "unstable-msc4363")]
585 fn deserialize_insufficient_user_authentication() {
586 use std::time::Duration;
587
588 use crate::api::{OAuthClientScope, error::InsufficientUserAuthenticationErrorData};
589
590 let deserialized: ErrorKind = from_json_value(json!({
591 "errcode": "M_INSUFFICIENT_USER_AUTHENTICATION",
592 "error": "…",
593 "org.matrix.msc4363.acr_values": "urn:example:foo urn:example:bar",
594 "org.matrix.msc4363.max_age": 300,
595 "org.matrix.msc4363.scope": "urn:matrix:client:api:* urn:example:xyzzy"
596 }))
597 .unwrap();
598
599 assert_eq!(
600 deserialized,
601 ErrorKind::InsufficientUserAuthentication(Box::new(
602 InsufficientUserAuthenticationErrorData {
603 acr_values: vec![
604 "urn:example:foo".parse().unwrap(),
605 "urn:example:bar".parse().unwrap()
606 ],
607 max_age: Some(Duration::from_secs(300)),
608 scope: [
609 OAuthClientScope::ApiFullAccess,
610 "urn:example:xyzzy".try_into().unwrap()
611 ]
612 .into_iter()
613 .collect()
614 }
615 ))
616 );
617 }
618
619 #[test]
620 #[cfg(feature = "unstable-msc4363")]
621 fn serialize_insufficient_user_authentication() {
622 use std::time::Duration;
623
624 use serde_json::to_value as to_json_value;
625
626 use crate::api::{OAuthClientScope, error::InsufficientUserAuthenticationErrorData};
627
628 let serialized = to_json_value(ErrorKind::InsufficientUserAuthentication(Box::new(
629 InsufficientUserAuthenticationErrorData {
630 acr_values: vec![
631 "urn:example:foo".parse().unwrap(),
632 "urn:example:bar".parse().unwrap(),
633 ],
634 max_age: Some(Duration::from_secs(300)),
635 scope: [OAuthClientScope::ApiFullAccess, "urn:example:xyzzy".try_into().unwrap()]
636 .into_iter()
637 .collect(),
638 },
639 )))
640 .unwrap();
641
642 assert_eq!(
643 serialized,
644 json!({
645 "errcode": "M_INSUFFICIENT_USER_AUTHENTICATION",
646 "org.matrix.msc4363.acr_values": "urn:example:foo urn:example:bar",
647 "org.matrix.msc4363.max_age": 300,
648 "org.matrix.msc4363.scope": "urn:example:xyzzy urn:matrix:client:api:*"
649 })
650 );
651 }
652}