1use std::{rc::Rc, sync::Arc};
4
5pub use ruma_identifiers_validation::user_id::localpart_is_fully_conforming;
6use ruma_identifiers_validation::{localpart_is_backwards_compatible, ID_MAX_BYTES};
7use ruma_macros::IdDst;
8
9use super::{matrix_uri::UriAction, IdParseError, MatrixToUri, MatrixUri, ServerName};
10
11#[repr(transparent)]
23#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, IdDst)]
24#[ruma_id(validate = ruma_identifiers_validation::user_id::validate)]
25pub struct UserId(str);
26
27impl UserId {
28    #[cfg(feature = "rand")]
33    #[allow(clippy::new_ret_no_self)]
34    pub fn new(server_name: &ServerName) -> OwnedUserId {
35        Self::from_borrowed(&format!(
36            "@{}:{}",
37            super::generate_localpart(12).to_lowercase(),
38            server_name
39        ))
40        .to_owned()
41    }
42
43    pub fn parse_with_server_name(
51        id: impl AsRef<str> + Into<Box<str>>,
52        server_name: &ServerName,
53    ) -> Result<OwnedUserId, IdParseError> {
54        let id_str = id.as_ref();
55
56        if id_str.starts_with('@') {
57            Self::parse(id)
58        } else {
59            localpart_is_backwards_compatible(id_str)?;
60            Ok(Self::from_borrowed(&format!("@{id_str}:{server_name}")).to_owned())
61        }
62    }
63
64    pub fn parse_with_server_name_rc(
68        id: impl AsRef<str> + Into<Rc<str>>,
69        server_name: &ServerName,
70    ) -> Result<Rc<Self>, IdParseError> {
71        let id_str = id.as_ref();
72
73        if id_str.starts_with('@') {
74            Self::parse_rc(id)
75        } else {
76            localpart_is_backwards_compatible(id_str)?;
77            Ok(Self::from_rc(format!("@{id_str}:{server_name}").into()))
78        }
79    }
80
81    pub fn parse_with_server_name_arc(
85        id: impl AsRef<str> + Into<Arc<str>>,
86        server_name: &ServerName,
87    ) -> Result<Arc<Self>, IdParseError> {
88        let id_str = id.as_ref();
89
90        if id_str.starts_with('@') {
91            Self::parse_arc(id)
92        } else {
93            localpart_is_backwards_compatible(id_str)?;
94            Ok(Self::from_arc(format!("@{id_str}:{server_name}").into()))
95        }
96    }
97
98    pub fn localpart(&self) -> &str {
100        &self.as_str()[1..self.colon_idx()]
101    }
102
103    pub fn server_name(&self) -> &ServerName {
105        ServerName::from_borrowed(&self.as_str()[self.colon_idx() + 1..])
106    }
107
108    fn validate_fully_conforming(&self) -> Result<bool, IdParseError> {
113        if self.as_bytes().len() > ID_MAX_BYTES {
116            return Err(IdParseError::MaximumLengthExceeded);
117        }
118
119        localpart_is_fully_conforming(self.localpart())
120    }
121
122    pub fn validate_strict(&self) -> Result<(), IdParseError> {
129        let is_fully_conforming = self.validate_fully_conforming()?;
130
131        if is_fully_conforming {
132            Ok(())
133        } else {
134            Err(IdParseError::InvalidCharacters)
135        }
136    }
137
138    pub fn validate_historical(&self) -> Result<(), IdParseError> {
148        self.validate_fully_conforming()?;
149        Ok(())
150    }
151
152    pub fn is_historical(&self) -> bool {
159        self.validate_fully_conforming().is_ok_and(|is_fully_conforming| !is_fully_conforming)
160    }
161
162    pub fn matrix_to_uri(&self) -> MatrixToUri {
176        MatrixToUri::new(self.into(), Vec::new())
177    }
178
179    pub fn matrix_uri(&self, chat: bool) -> MatrixUri {
196        MatrixUri::new(self.into(), Vec::new(), Some(UriAction::Chat).filter(|_| chat))
197    }
198
199    fn colon_idx(&self) -> usize {
200        self.as_str().find(':').unwrap()
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::{OwnedUserId, UserId};
207    use crate::{server_name, IdParseError};
208
209    #[test]
210    fn valid_user_id_from_str() {
211        let user_id = <&UserId>::try_from("@carl:example.com").expect("Failed to create UserId.");
212        assert_eq!(user_id.as_str(), "@carl:example.com");
213        assert_eq!(user_id.localpart(), "carl");
214        assert_eq!(user_id.server_name(), "example.com");
215        assert!(!user_id.is_historical());
216        user_id.validate_historical().unwrap();
217        user_id.validate_strict().unwrap();
218    }
219
220    #[test]
221    fn parse_valid_user_id() {
222        let server_name = server_name!("example.com");
223        let user_id = UserId::parse_with_server_name("@carl:example.com", server_name)
224            .expect("Failed to create UserId.");
225        assert_eq!(user_id.as_str(), "@carl:example.com");
226        assert_eq!(user_id.localpart(), "carl");
227        assert_eq!(user_id.server_name(), "example.com");
228        assert!(!user_id.is_historical());
229        user_id.validate_historical().unwrap();
230        user_id.validate_strict().unwrap();
231    }
232
233    #[test]
234    fn parse_valid_user_id_parts() {
235        let server_name = server_name!("example.com");
236        let user_id =
237            UserId::parse_with_server_name("carl", server_name).expect("Failed to create UserId.");
238        assert_eq!(user_id.as_str(), "@carl:example.com");
239        assert_eq!(user_id.localpart(), "carl");
240        assert_eq!(user_id.server_name(), "example.com");
241        assert!(!user_id.is_historical());
242        user_id.validate_historical().unwrap();
243        user_id.validate_strict().unwrap();
244    }
245
246    #[test]
247    fn backwards_compatible_user_id() {
248        let localpart = "τ";
249        let user_id_str = "@τ:example.com";
250        let server_name = server_name!("example.com");
251
252        let user_id = <&UserId>::try_from(user_id_str).unwrap();
253        assert_eq!(user_id.as_str(), user_id_str);
254        assert_eq!(user_id.localpart(), localpart);
255        assert_eq!(user_id.server_name(), server_name);
256        assert!(!user_id.is_historical());
257        user_id.validate_historical().unwrap_err();
258        user_id.validate_strict().unwrap_err();
259
260        let user_id = UserId::parse_with_server_name(user_id_str, server_name).unwrap();
261        assert_eq!(user_id.as_str(), user_id_str);
262        assert_eq!(user_id.localpart(), localpart);
263        assert_eq!(user_id.server_name(), server_name);
264        assert!(!user_id.is_historical());
265        user_id.validate_historical().unwrap_err();
266        user_id.validate_strict().unwrap_err();
267
268        let user_id = UserId::parse_with_server_name(localpart, server_name).unwrap();
269        assert_eq!(user_id.as_str(), user_id_str);
270        assert_eq!(user_id.localpart(), localpart);
271        assert_eq!(user_id.server_name(), server_name);
272        assert!(!user_id.is_historical());
273        user_id.validate_historical().unwrap_err();
274        user_id.validate_strict().unwrap_err();
275
276        let user_id = UserId::parse_with_server_name_rc(user_id_str, server_name).unwrap();
277        assert_eq!(user_id.as_str(), user_id_str);
278        assert_eq!(user_id.localpart(), localpart);
279        assert_eq!(user_id.server_name(), server_name);
280        assert!(!user_id.is_historical());
281        user_id.validate_historical().unwrap_err();
282        user_id.validate_strict().unwrap_err();
283
284        let user_id = UserId::parse_with_server_name_rc(localpart, server_name).unwrap();
285        assert_eq!(user_id.as_str(), user_id_str);
286        assert_eq!(user_id.localpart(), localpart);
287        assert_eq!(user_id.server_name(), server_name);
288        assert!(!user_id.is_historical());
289        user_id.validate_historical().unwrap_err();
290        user_id.validate_strict().unwrap_err();
291
292        let user_id = UserId::parse_with_server_name_arc(user_id_str, server_name).unwrap();
293        assert_eq!(user_id.as_str(), user_id_str);
294        assert_eq!(user_id.localpart(), localpart);
295        assert_eq!(user_id.server_name(), server_name);
296        assert!(!user_id.is_historical());
297        user_id.validate_historical().unwrap_err();
298        user_id.validate_strict().unwrap_err();
299
300        let user_id = UserId::parse_with_server_name_arc(localpart, server_name).unwrap();
301        assert_eq!(user_id.as_str(), user_id_str);
302        assert_eq!(user_id.localpart(), localpart);
303        assert_eq!(user_id.server_name(), server_name);
304        assert!(!user_id.is_historical());
305        user_id.validate_historical().unwrap_err();
306        user_id.validate_strict().unwrap_err();
307
308        let user_id = UserId::parse_rc(user_id_str).unwrap();
309        assert_eq!(user_id.as_str(), user_id_str);
310        assert_eq!(user_id.localpart(), localpart);
311        assert_eq!(user_id.server_name(), server_name);
312        assert!(!user_id.is_historical());
313        user_id.validate_historical().unwrap_err();
314        user_id.validate_strict().unwrap_err();
315
316        let user_id = UserId::parse_arc(user_id_str).unwrap();
317        assert_eq!(user_id.as_str(), user_id_str);
318        assert_eq!(user_id.localpart(), localpart);
319        assert_eq!(user_id.server_name(), server_name);
320        assert!(!user_id.is_historical());
321        user_id.validate_historical().unwrap_err();
322        user_id.validate_strict().unwrap_err();
323    }
324
325    #[test]
326    fn definitely_invalid_user_id() {
327        UserId::parse_with_server_name("a:b", server_name!("example.com")).unwrap_err();
328    }
329
330    #[test]
331    fn valid_historical_user_id() {
332        let user_id =
333            <&UserId>::try_from("@a%b[irc]:example.com").expect("Failed to create UserId.");
334        assert_eq!(user_id.as_str(), "@a%b[irc]:example.com");
335        assert_eq!(user_id.localpart(), "a%b[irc]");
336        assert_eq!(user_id.server_name(), "example.com");
337        assert!(user_id.is_historical());
338        user_id.validate_historical().unwrap();
339        user_id.validate_strict().unwrap_err();
340    }
341
342    #[test]
343    fn parse_valid_historical_user_id() {
344        let server_name = server_name!("example.com");
345        let user_id = UserId::parse_with_server_name("@a%b[irc]:example.com", server_name)
346            .expect("Failed to create UserId.");
347        assert_eq!(user_id.as_str(), "@a%b[irc]:example.com");
348        assert_eq!(user_id.localpart(), "a%b[irc]");
349        assert_eq!(user_id.server_name(), "example.com");
350        assert!(user_id.is_historical());
351        user_id.validate_historical().unwrap();
352        user_id.validate_strict().unwrap_err();
353    }
354
355    #[test]
356    fn parse_valid_historical_user_id_parts() {
357        let server_name = server_name!("example.com");
358        let user_id = UserId::parse_with_server_name("a%b[irc]", server_name)
359            .expect("Failed to create UserId.");
360        assert_eq!(user_id.as_str(), "@a%b[irc]:example.com");
361        assert_eq!(user_id.localpart(), "a%b[irc]");
362        assert_eq!(user_id.server_name(), "example.com");
363        assert!(user_id.is_historical());
364        user_id.validate_historical().unwrap();
365        user_id.validate_strict().unwrap_err();
366    }
367
368    #[test]
369    fn uppercase_user_id() {
370        let user_id = <&UserId>::try_from("@CARL:example.com").expect("Failed to create UserId.");
371        assert_eq!(user_id.as_str(), "@CARL:example.com");
372        assert!(user_id.is_historical());
373        user_id.validate_historical().unwrap();
374        user_id.validate_strict().unwrap_err();
375    }
376
377    #[cfg(feature = "rand")]
378    #[test]
379    fn generate_random_valid_user_id() {
380        let server_name = server_name!("example.com");
381        let user_id = UserId::new(server_name);
382        assert_eq!(user_id.localpart().len(), 12);
383        assert_eq!(user_id.server_name(), "example.com");
384        user_id.validate_historical().unwrap();
385        user_id.validate_strict().unwrap();
386
387        let id_str = user_id.as_str();
388
389        assert!(id_str.starts_with('@'));
390        assert_eq!(id_str.len(), 25);
391    }
392
393    #[test]
394    fn serialize_valid_user_id() {
395        assert_eq!(
396            serde_json::to_string(
397                <&UserId>::try_from("@carl:example.com").expect("Failed to create UserId.")
398            )
399            .expect("Failed to convert UserId to JSON."),
400            r#""@carl:example.com""#
401        );
402    }
403
404    #[test]
405    fn deserialize_valid_user_id() {
406        assert_eq!(
407            serde_json::from_str::<OwnedUserId>(r#""@carl:example.com""#)
408                .expect("Failed to convert JSON to UserId"),
409            <&UserId>::try_from("@carl:example.com").expect("Failed to create UserId.")
410        );
411    }
412
413    #[test]
414    fn valid_user_id_with_explicit_standard_port() {
415        assert_eq!(
416            <&UserId>::try_from("@carl:example.com:443")
417                .expect("Failed to create UserId.")
418                .as_str(),
419            "@carl:example.com:443"
420        );
421    }
422
423    #[test]
424    fn valid_user_id_with_non_standard_port() {
425        let user_id =
426            <&UserId>::try_from("@carl:example.com:5000").expect("Failed to create UserId.");
427        assert_eq!(user_id.as_str(), "@carl:example.com:5000");
428        assert!(!user_id.is_historical());
429    }
430
431    #[test]
432    fn invalid_characters_in_user_id_localpart() {
433        let user_id = <&UserId>::try_from("@te\nst:example.com").unwrap();
434        assert_eq!(user_id.validate_historical().unwrap_err(), IdParseError::InvalidCharacters);
435        assert_eq!(user_id.validate_strict().unwrap_err(), IdParseError::InvalidCharacters);
436    }
437
438    #[test]
439    fn missing_user_id_sigil() {
440        assert_eq!(
441            <&UserId>::try_from("carl:example.com").unwrap_err(),
442            IdParseError::MissingLeadingSigil
443        );
444    }
445
446    #[test]
447    fn missing_user_id_delimiter() {
448        assert_eq!(<&UserId>::try_from("@carl").unwrap_err(), IdParseError::MissingColon);
449    }
450
451    #[test]
452    fn invalid_user_id_host() {
453        assert_eq!(<&UserId>::try_from("@carl:/").unwrap_err(), IdParseError::InvalidServerName);
454    }
455
456    #[test]
457    fn invalid_user_id_port() {
458        assert_eq!(
459            <&UserId>::try_from("@carl:example.com:notaport").unwrap_err(),
460            IdParseError::InvalidServerName
461        );
462    }
463}