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    use crate::server_name;
68
69    #[test]
70    fn parse_mxc_uri() {
71        let mxc = <&MxcUri>::from("mxc://127.0.0.1/asd32asdfasdsd");
72
73        assert!(mxc.is_valid());
74        assert_eq!(mxc.parts(), Ok((server_name!("127.0.0.1"), "asd32asdfasdsd")));
75    }
76
77    #[test]
78    fn parse_mxc_uri_without_media_id() {
79        let mxc = <&MxcUri>::from("mxc://127.0.0.1");
80
81        assert!(!mxc.is_valid());
82        assert_eq!(mxc.parts(), Err(MxcUriError::MissingSlash));
83    }
84
85    #[test]
86    fn parse_mxc_uri_without_protocol() {
87        assert!(!<&MxcUri>::from("127.0.0.1/asd32asdfasdsd").is_valid());
88    }
89
90    #[test]
91    fn serialize_mxc_uri() {
92        assert_eq!(
93            serde_json::to_string(<&MxcUri>::from("mxc://server/1234id"))
94                .expect("Failed to convert MxcUri to JSON."),
95            r#""mxc://server/1234id""#
96        );
97    }
98
99    #[test]
100    fn deserialize_mxc_uri() {
101        let mxc = serde_json::from_str::<OwnedMxcUri>(r#""mxc://server/1234id""#)
102            .expect("Failed to convert JSON to MxcUri");
103
104        assert_eq!(mxc, "mxc://server/1234id");
105        assert!(mxc.is_valid());
106        assert_eq!(mxc.parts(), Ok((server_name!("server"), "1234id")));
107    }
108}