ruma_identifiers/
user_id.rs1use std::{rc::Rc, sync::Arc};
4
5use crate::{matrix_uri::UriAction, MatrixToUri, MatrixUri, ServerName};
6
7#[repr(transparent)]
20#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
21pub struct UserId(str);
22
23opaque_identifier_validated!(UserId, ruma_identifiers_validation::user_id::validate);
24
25impl UserId {
26 #[cfg(feature = "rand")]
29 pub fn new(server_name: &ServerName) -> Box<Self> {
30 Self::from_owned(
31 format!("@{}:{}", crate::generate_localpart(12).to_lowercase(), server_name).into(),
32 )
33 }
34
35 pub fn parse_with_server_name(
43 id: impl AsRef<str> + Into<Box<str>>,
44 server_name: &ServerName,
45 ) -> Result<Box<Self>, crate::Error> {
46 let id_str = id.as_ref();
47
48 if id_str.starts_with('@') {
49 Self::parse(id)
50 } else {
51 let _ = localpart_is_fully_conforming(id_str)?;
52 Ok(Self::from_owned(format!("@{}:{}", id_str, server_name).into()))
53 }
54 }
55
56 pub fn parse_with_server_name_rc(
60 id: impl AsRef<str> + Into<Rc<str>>,
61 server_name: &ServerName,
62 ) -> Result<Rc<Self>, crate::Error> {
63 let id_str = id.as_ref();
64
65 if id_str.starts_with('@') {
66 Self::parse_rc(id)
67 } else {
68 let _ = localpart_is_fully_conforming(id_str)?;
69 Ok(Self::from_rc(format!("@{}:{}", id_str, server_name).into()))
70 }
71 }
72
73 pub fn parse_with_server_name_arc(
77 id: impl AsRef<str> + Into<Arc<str>>,
78 server_name: &ServerName,
79 ) -> Result<Arc<Self>, crate::Error> {
80 let id_str = id.as_ref();
81
82 if id_str.starts_with('@') {
83 Self::parse_arc(id)
84 } else {
85 let _ = localpart_is_fully_conforming(id_str)?;
86 Ok(Self::from_arc(format!("@{}:{}", id_str, server_name).into()))
87 }
88 }
89
90 pub fn localpart(&self) -> &str {
92 &self.as_str()[1..self.colon_idx() as usize]
93 }
94
95 pub fn server_name(&self) -> &ServerName {
97 ServerName::from_borrowed(&self.as_str()[self.colon_idx() + 1..])
98 }
99
100 pub fn is_historical(&self) -> bool {
105 !localpart_is_fully_conforming(self.localpart()).unwrap()
106 }
107
108 pub fn matrix_to_uri(&self) -> MatrixToUri {
122 MatrixToUri::new(self.into(), Vec::new())
123 }
124
125 pub fn matrix_uri(&self, chat: bool) -> MatrixUri {
142 MatrixUri::new(self.into(), Vec::new(), Some(UriAction::Chat).filter(|_| chat))
143 }
144
145 fn colon_idx(&self) -> usize {
146 self.as_str().find(':').unwrap()
147 }
148}
149
150pub use ruma_identifiers_validation::user_id::localpart_is_fully_conforming;
151
152#[cfg(test)]
153mod tests {
154 use std::convert::TryFrom;
155
156 use super::UserId;
157 use crate::{server_name, Error};
158
159 #[test]
160 fn valid_user_id_from_str() {
161 let user_id = <&UserId>::try_from("@carl:example.com").expect("Failed to create UserId.");
162 assert_eq!(user_id.as_str(), "@carl:example.com");
163 assert_eq!(user_id.localpart(), "carl");
164 assert_eq!(user_id.server_name(), "example.com");
165 assert!(!user_id.is_historical());
166 }
167
168 #[test]
169 fn parse_valid_user_id() {
170 let server_name = server_name!("example.com");
171 let user_id = UserId::parse_with_server_name("@carl:example.com", server_name)
172 .expect("Failed to create UserId.");
173 assert_eq!(user_id.as_str(), "@carl:example.com");
174 assert_eq!(user_id.localpart(), "carl");
175 assert_eq!(user_id.server_name(), "example.com");
176 assert!(!user_id.is_historical());
177 }
178
179 #[test]
180 fn parse_valid_user_id_parts() {
181 let server_name = server_name!("example.com");
182 let user_id =
183 UserId::parse_with_server_name("carl", server_name).expect("Failed to create UserId.");
184 assert_eq!(user_id.as_str(), "@carl:example.com");
185 assert_eq!(user_id.localpart(), "carl");
186 assert_eq!(user_id.server_name(), "example.com");
187 assert!(!user_id.is_historical());
188 }
189
190 #[test]
191 fn invalid_user_id() {
192 let localpart = "τ";
193 let user_id = "@τ:example.com";
194 let server_name = server_name!("example.com");
195
196 assert!(<&UserId>::try_from(user_id).is_err());
197 assert!(UserId::parse_with_server_name(user_id, server_name).is_err());
198 assert!(UserId::parse_with_server_name(localpart, server_name).is_err());
199 assert!(UserId::parse_with_server_name_rc(user_id, server_name).is_err());
200 assert!(UserId::parse_with_server_name_rc(localpart, server_name).is_err());
201 assert!(UserId::parse_with_server_name_arc(user_id, server_name).is_err());
202 assert!(UserId::parse_with_server_name_arc(localpart, server_name).is_err());
203 assert!(UserId::parse_rc(user_id).is_err());
204 assert!(UserId::parse_arc(user_id).is_err());
205 }
206
207 #[test]
208 fn valid_historical_user_id() {
209 let user_id =
210 <&UserId>::try_from("@a%b[irc]:example.com").expect("Failed to create UserId.");
211 assert_eq!(user_id.as_str(), "@a%b[irc]:example.com");
212 assert_eq!(user_id.localpart(), "a%b[irc]");
213 assert_eq!(user_id.server_name(), "example.com");
214 assert!(user_id.is_historical());
215 }
216
217 #[test]
218 fn parse_valid_historical_user_id() {
219 let server_name = server_name!("example.com");
220 let user_id = UserId::parse_with_server_name("@a%b[irc]:example.com", server_name)
221 .expect("Failed to create UserId.");
222 assert_eq!(user_id.as_str(), "@a%b[irc]:example.com");
223 assert_eq!(user_id.localpart(), "a%b[irc]");
224 assert_eq!(user_id.server_name(), "example.com");
225 assert!(user_id.is_historical());
226 }
227
228 #[test]
229 fn parse_valid_historical_user_id_parts() {
230 let server_name = server_name!("example.com");
231 let user_id = UserId::parse_with_server_name("a%b[irc]", server_name)
232 .expect("Failed to create UserId.");
233 assert_eq!(user_id.as_str(), "@a%b[irc]:example.com");
234 assert_eq!(user_id.localpart(), "a%b[irc]");
235 assert_eq!(user_id.server_name(), "example.com");
236 assert!(user_id.is_historical());
237 }
238
239 #[test]
240 fn uppercase_user_id() {
241 let user_id = <&UserId>::try_from("@CARL:example.com").expect("Failed to create UserId.");
242 assert_eq!(user_id.as_str(), "@CARL:example.com");
243 assert!(user_id.is_historical());
244 }
245
246 #[cfg(feature = "rand")]
247 #[test]
248 fn generate_random_valid_user_id() {
249 let server_name = server_name!("example.com");
250 let user_id = UserId::new(server_name);
251 assert_eq!(user_id.localpart().len(), 12);
252 assert_eq!(user_id.server_name(), "example.com");
253
254 let id_str = user_id.as_str();
255
256 assert!(id_str.starts_with('@'));
257 assert_eq!(id_str.len(), 25);
258 }
259
260 #[cfg(feature = "serde")]
261 #[test]
262 fn serialize_valid_user_id() {
263 assert_eq!(
264 serde_json::to_string(
265 <&UserId>::try_from("@carl:example.com").expect("Failed to create UserId.")
266 )
267 .expect("Failed to convert UserId to JSON."),
268 r#""@carl:example.com""#
269 );
270 }
271
272 #[cfg(feature = "serde")]
273 #[test]
274 fn deserialize_valid_user_id() {
275 assert_eq!(
276 serde_json::from_str::<Box<UserId>>(r#""@carl:example.com""#)
277 .expect("Failed to convert JSON to UserId"),
278 <&UserId>::try_from("@carl:example.com").expect("Failed to create UserId.")
279 );
280 }
281
282 #[test]
283 fn valid_user_id_with_explicit_standard_port() {
284 assert_eq!(
285 <&UserId>::try_from("@carl:example.com:443")
286 .expect("Failed to create UserId.")
287 .as_ref(),
288 "@carl:example.com:443"
289 );
290 }
291
292 #[test]
293 fn valid_user_id_with_non_standard_port() {
294 let user_id =
295 <&UserId>::try_from("@carl:example.com:5000").expect("Failed to create UserId.");
296 assert_eq!(user_id.as_str(), "@carl:example.com:5000");
297 assert!(!user_id.is_historical());
298 }
299
300 #[test]
301 #[cfg(not(feature = "compat"))]
302 fn invalid_characters_in_user_id_localpart() {
303 assert_eq!(
304 <&UserId>::try_from("@te\nst:example.com").unwrap_err(),
305 Error::InvalidCharacters
306 );
307 }
308
309 #[test]
310 fn missing_user_id_sigil() {
311 assert_eq!(
312 <&UserId>::try_from("carl:example.com").unwrap_err(),
313 Error::MissingLeadingSigil
314 );
315 }
316
317 #[test]
318 fn missing_user_id_delimiter() {
319 assert_eq!(<&UserId>::try_from("@carl").unwrap_err(), Error::MissingDelimiter);
320 }
321
322 #[test]
323 fn invalid_user_id_host() {
324 assert_eq!(<&UserId>::try_from("@carl:/").unwrap_err(), Error::InvalidServerName);
325 }
326
327 #[test]
328 fn invalid_user_id_port() {
329 assert_eq!(
330 <&UserId>::try_from("@carl:example.com:notaport").unwrap_err(),
331 Error::InvalidServerName
332 );
333 }
334}