Skip to main content

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