ruma_events/room/
thumbnail_source_serde.rs

1//! De-/serialization functions for `Option<MediaSource>` objects representing a thumbnail source.
2
3use ruma_common::OwnedMxcUri;
4use serde::{
5    ser::{SerializeStruct, Serializer},
6    Deserialize, Deserializer,
7};
8
9use super::{EncryptedFile, MediaSource};
10
11/// Serializes a MediaSource to a thumbnail source.
12pub(crate) fn serialize<S>(source: &Option<MediaSource>, serializer: S) -> Result<S::Ok, S::Error>
13where
14    S: Serializer,
15{
16    if let Some(source) = source {
17        let mut st = serializer.serialize_struct("ThumbnailSource", 1)?;
18        match source {
19            MediaSource::Plain(url) => st.serialize_field("thumbnail_url", url)?,
20            MediaSource::Encrypted(file) => st.serialize_field("thumbnail_file", file)?,
21        }
22        st.end()
23    } else {
24        serializer.serialize_none()
25    }
26}
27
28/// Deserializes a thumbnail source to a MediaSource.
29pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<Option<MediaSource>, D::Error>
30where
31    D: Deserializer<'de>,
32{
33    #[derive(Deserialize)]
34    struct ThumbnailSourceJsonRepr {
35        thumbnail_url: Option<OwnedMxcUri>,
36        thumbnail_file: Option<Box<EncryptedFile>>,
37    }
38
39    match ThumbnailSourceJsonRepr::deserialize(deserializer)? {
40        ThumbnailSourceJsonRepr { thumbnail_url: None, thumbnail_file: None } => Ok(None),
41        // Prefer file if it is set
42        ThumbnailSourceJsonRepr { thumbnail_file: Some(file), .. } => {
43            Ok(Some(MediaSource::Encrypted(file)))
44        }
45        ThumbnailSourceJsonRepr { thumbnail_url: Some(url), .. } => {
46            Ok(Some(MediaSource::Plain(url)))
47        }
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use assert_matches2::assert_matches;
54    use ruma_common::{mxc_uri, serde::Base64};
55    use serde::{Deserialize, Serialize};
56    use serde_json::json;
57
58    use crate::room::{EncryptedFileInit, JsonWebKeyInit, MediaSource};
59
60    #[derive(Clone, Debug, Deserialize, Serialize)]
61    struct ThumbnailSourceTest {
62        #[serde(flatten, with = "super", skip_serializing_if = "Option::is_none")]
63        source: Option<MediaSource>,
64    }
65
66    #[test]
67    fn deserialize_plain() {
68        let json = json!({ "thumbnail_url": "mxc://notareal.hs/abcdef" });
69
70        assert_matches!(
71            serde_json::from_value::<ThumbnailSourceTest>(json),
72            Ok(ThumbnailSourceTest { source: Some(MediaSource::Plain(url)) })
73        );
74        assert_eq!(url, "mxc://notareal.hs/abcdef");
75    }
76
77    #[test]
78    fn deserialize_encrypted() {
79        let json = json!({
80            "thumbnail_file": {
81                "url": "mxc://notareal.hs/abcdef",
82                "key": {
83                    "kty": "oct",
84                    "key_ops": ["encrypt", "decrypt"],
85                    "alg": "A256CTR",
86                    "k": "TLlG_OpX807zzQuuwv4QZGJ21_u7weemFGYJFszMn9A",
87                    "ext": true
88                },
89                "iv": "S22dq3NAX8wAAAAAAAAAAA",
90                "hashes": {
91                    "sha256": "aWOHudBnDkJ9IwaR1Nd8XKoI7DOrqDTwt6xDPfVGN6Q"
92                },
93                "v": "v2",
94            },
95        });
96
97        assert_matches!(
98            serde_json::from_value::<ThumbnailSourceTest>(json),
99            Ok(ThumbnailSourceTest { source: Some(MediaSource::Encrypted(file)) })
100        );
101        assert_eq!(file.url, "mxc://notareal.hs/abcdef");
102    }
103
104    #[test]
105    fn deserialize_none_by_absence() {
106        let json = json!({});
107
108        assert_matches!(
109            serde_json::from_value::<ThumbnailSourceTest>(json).unwrap(),
110            ThumbnailSourceTest { source: None }
111        );
112    }
113
114    #[test]
115    fn deserialize_none_by_null_plain() {
116        let json = json!({ "thumbnail_url": null });
117
118        assert_matches!(
119            serde_json::from_value::<ThumbnailSourceTest>(json).unwrap(),
120            ThumbnailSourceTest { source: None }
121        );
122    }
123
124    #[test]
125    fn deserialize_none_by_null_encrypted() {
126        let json = json!({ "thumbnail_file": null });
127
128        assert_matches!(
129            serde_json::from_value::<ThumbnailSourceTest>(json).unwrap(),
130            ThumbnailSourceTest { source: None }
131        );
132    }
133
134    #[test]
135    fn serialize_plain() {
136        let request = ThumbnailSourceTest {
137            source: Some(MediaSource::Plain(mxc_uri!("mxc://notareal.hs/abcdef").into())),
138        };
139        assert_eq!(
140            serde_json::to_value(&request).unwrap(),
141            json!({ "thumbnail_url": "mxc://notareal.hs/abcdef" })
142        );
143    }
144
145    #[test]
146    fn serialize_encrypted() {
147        let request = ThumbnailSourceTest {
148            source: Some(MediaSource::Encrypted(Box::new(
149                EncryptedFileInit {
150                    url: mxc_uri!("mxc://notareal.hs/abcdef").to_owned(),
151                    key: JsonWebKeyInit {
152                        kty: "oct".to_owned(),
153                        key_ops: vec!["encrypt".to_owned(), "decrypt".to_owned()],
154                        alg: "A256CTR".to_owned(),
155                        k: Base64::parse("TLlG_OpX807zzQuuwv4QZGJ21_u7weemFGYJFszMn9A").unwrap(),
156                        ext: true,
157                    }
158                    .into(),
159                    iv: Base64::parse("S22dq3NAX8wAAAAAAAAAAA").unwrap(),
160                    hashes: [(
161                        "sha256".to_owned(),
162                        Base64::parse("aWOHudBnDkJ9IwaR1Nd8XKoI7DOrqDTwt6xDPfVGN6Q").unwrap(),
163                    )]
164                    .into(),
165                    v: "v2".to_owned(),
166                }
167                .into(),
168            ))),
169        };
170        assert_eq!(
171            serde_json::to_value(&request).unwrap(),
172            json!({
173                "thumbnail_file": {
174                    "url": "mxc://notareal.hs/abcdef",
175                    "key": {
176                        "kty": "oct",
177                        "key_ops": ["encrypt", "decrypt"],
178                        "alg": "A256CTR",
179                        "k": "TLlG_OpX807zzQuuwv4QZGJ21_u7weemFGYJFszMn9A",
180                        "ext": true
181                    },
182                    "iv": "S22dq3NAX8wAAAAAAAAAAA",
183                    "hashes": {
184                        "sha256": "aWOHudBnDkJ9IwaR1Nd8XKoI7DOrqDTwt6xDPfVGN6Q"
185                    },
186                    "v": "v2",
187                },
188            })
189        );
190    }
191
192    #[test]
193    fn serialize_none() {
194        let request = ThumbnailSourceTest { source: None };
195        assert_eq!(serde_json::to_value(&request).unwrap(), json!({}));
196    }
197}