Skip to main content

ruma_common/api/error/
kind_serde.rs

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                    // if we already know we're deserializing a different variant to the one
154                    // containing this field, ignore its value.
155                    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                        // TODO: use interleave() here to collect directly into a String if it ever
438                        // becomes stable
439                        &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}