ruma_identifiers/
mxc_uri.rs

1//! A URI that should be a Matrix-spec compliant [MXC URI].
2//!
3//! [MXC URI]: https://spec.matrix.org/v1.2/client-server-api/#matrix-content-mxc-uris
4
5use std::num::NonZeroU8;
6
7use ruma_identifiers_validation::{error::MxcUriError, mxc_uri::validate};
8
9use crate::ServerName;
10
11type Result<T, E = MxcUriError> = std::result::Result<T, E>;
12
13/// A URI that should be a Matrix-spec compliant [MXC URI].
14///
15/// [MXC URI]: https://spec.matrix.org/v1.2/client-server-api/#matrix-content-mxc-uris
16
17#[repr(transparent)]
18#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub struct MxcUri(str);
20
21opaque_identifier!(MxcUri);
22
23impl MxcUri {
24    /// If this is a valid MXC URI, returns the media ID.
25    pub fn media_id(&self) -> Result<&str> {
26        self.parts().map(|(_, s)| s)
27    }
28
29    /// If this is a valid MXC URI, returns the server name.
30    pub fn server_name(&self) -> Result<&ServerName> {
31        self.parts().map(|(s, _)| s)
32    }
33
34    /// If this is a valid MXC URI, returns a `(server_name, media_id)` tuple, else it returns the
35    /// error.
36    pub fn parts(&self) -> Result<(&ServerName, &str)> {
37        self.extract_slash_idx().map(|idx| {
38            (
39                ServerName::from_borrowed(&self.as_str()[6..idx.get() as usize]),
40                &self.as_str()[idx.get() as usize + 1..],
41            )
42        })
43    }
44
45    /// Validates the URI and returns an error if it failed.
46    pub fn validate(&self) -> Result<()> {
47        self.extract_slash_idx().map(|_| ())
48    }
49
50    /// Convenience method for `.validate().is_ok()`.
51    #[inline(always)]
52    pub fn is_valid(&self) -> bool {
53        self.validate().is_ok()
54    }
55
56    // convenience method for calling validate(self)
57    #[inline(always)]
58    fn extract_slash_idx(&self) -> Result<NonZeroU8> {
59        validate(self.as_str())
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use std::convert::TryInto;
66
67    use ruma_identifiers_validation::error::MxcUriError;
68
69    use super::MxcUri;
70
71    #[test]
72    fn parse_mxc_uri() {
73        let mxc = Box::<MxcUri>::from("mxc://127.0.0.1/asd32asdfasdsd");
74
75        assert!(mxc.is_valid());
76        assert_eq!(
77            mxc.parts(),
78            Ok(("127.0.0.1".try_into().expect("Failed to create ServerName"), "asd32asdfasdsd"))
79        );
80    }
81
82    #[test]
83    fn parse_mxc_uri_without_media_id() {
84        let mxc = Box::<MxcUri>::from("mxc://127.0.0.1");
85
86        assert!(!mxc.is_valid());
87        assert_eq!(mxc.parts(), Err(MxcUriError::MissingSlash));
88    }
89
90    #[test]
91    fn parse_mxc_uri_without_protocol() {
92        assert!(!Box::<MxcUri>::from("127.0.0.1/asd32asdfasdsd").is_valid());
93    }
94
95    #[cfg(feature = "serde")]
96    #[test]
97    fn serialize_mxc_uri() {
98        assert_eq!(
99            serde_json::to_string(&Box::<MxcUri>::from("mxc://server/1234id"))
100                .expect("Failed to convert MxcUri to JSON."),
101            r#""mxc://server/1234id""#
102        );
103    }
104
105    #[cfg(feature = "serde")]
106    #[test]
107    fn deserialize_mxc_uri() {
108        let mxc = serde_json::from_str::<Box<MxcUri>>(r#""mxc://server/1234id""#)
109            .expect("Failed to convert JSON to MxcUri");
110
111        assert_eq!(mxc.as_str(), "mxc://server/1234id");
112        assert!(mxc.is_valid());
113        assert_eq!(
114            mxc.parts(),
115            Ok(("server".try_into().expect("Failed to create ServerName"), "1234id"))
116        );
117    }
118}