1use std::{convert::TryFrom, fmt};
4
5use percent_encoding::{percent_decode_str, percent_encode, AsciiSet, CONTROLS};
6use ruma_identifiers_validation::{
7 error::{MatrixIdError, MatrixToError, MatrixUriError},
8 Error,
9};
10use url::Url;
11
12use crate::{EventId, PrivOwnedStr, RoomAliasId, RoomId, RoomOrAliasId, ServerName, UserId};
13
14const MATRIX_TO_BASE_URL: &str = "https://matrix.to/#/";
15const MATRIX_SCHEME: &str = "matrix";
16const NON_PATH: &AsciiSet = &CONTROLS.add(b'/').add(b'?').add(b'#').add(b'[').add(b']');
20const RESERVED: &AsciiSet = &CONTROLS
24 .add(b':')
25 .add(b'/')
26 .add(b'?')
27 .add(b'#')
28 .add(b'[')
29 .add(b']')
30 .add(b'@')
31 .add(b'!')
32 .add(b'$')
33 .add(b'&')
34 .add(b'\'')
35 .add(b'(')
36 .add(b')')
37 .add(b'*')
38 .add(b'+')
39 .add(b',')
40 .add(b';')
41 .add(b'=');
42
43#[derive(Debug, PartialEq, Eq)]
45#[non_exhaustive]
46pub enum MatrixId {
47 Room(Box<RoomId>),
49
50 RoomAlias(Box<RoomAliasId>),
52
53 User(Box<UserId>),
55
56 Event(Box<RoomOrAliasId>, Box<EventId>),
58}
59
60impl MatrixId {
61 pub(crate) fn parse_with_sigil(s: &str) -> Result<Self, Error> {
69 let s = if let Some(stripped) = s.strip_prefix('/') { stripped } else { s };
70 let s = if let Some(stripped) = s.strip_suffix('/') { stripped } else { s };
71 if s.is_empty() {
72 return Err(MatrixIdError::NoIdentifier.into());
73 }
74
75 if s.matches('/').count() > 1 {
76 return Err(MatrixIdError::TooManyIdentifiers.into());
77 }
78
79 if let Some((first_raw, second_raw)) = s.split_once('/') {
80 let first = percent_decode_str(first_raw).decode_utf8()?;
81 let second = percent_decode_str(second_raw).decode_utf8()?;
82
83 match first.as_bytes()[0] {
84 b'!' | b'#' if second.as_bytes()[0] == b'$' => {
85 let room_id = <&RoomOrAliasId>::try_from(first.as_ref())?;
86 let event_id = <&EventId>::try_from(second.as_ref())?;
87 Ok((room_id, event_id).into())
88 }
89 b'$' if matches!(second.as_bytes()[0], b'!' | b'#') => {
90 let room_id = <&RoomOrAliasId>::try_from(second.as_ref())?;
91 let event_id = <&EventId>::try_from(first.as_ref())?;
92 Ok((room_id, event_id).into())
93 }
94 _ => Err(MatrixIdError::UnknownIdentifierPair.into()),
95 }
96 } else {
97 let id = percent_decode_str(s).decode_utf8()?;
98
99 match id.as_bytes()[0] {
100 b'@' => Ok(<&UserId>::try_from(id.as_ref())?.into()),
101 b'!' => Ok(<&RoomId>::try_from(id.as_ref())?.into()),
102 b'#' => Ok(<&RoomAliasId>::try_from(id.as_ref())?.into()),
103 b'$' => Err(MatrixIdError::MissingRoom.into()),
104 _ => Err(MatrixIdError::UnknownIdentifier.into()),
105 }
106 }
107 }
108
109 pub(crate) fn parse_with_type(s: &str) -> Result<Self, Error> {
118 let s = if let Some(stripped) = s.strip_prefix('/') { stripped } else { s };
119 let s = if let Some(stripped) = s.strip_suffix('/') { stripped } else { s };
120 if s.is_empty() {
121 return Err(MatrixIdError::NoIdentifier.into());
122 }
123
124 if ![1, 3].contains(&s.matches('/').count()) {
125 return Err(MatrixIdError::InvalidPartsNumber.into());
126 }
127
128 let mut id = String::new();
129 let mut split = s.split('/');
130 while let (Some(type_), Some(id_without_sigil)) = (split.next(), split.next()) {
131 let sigil = match type_ {
132 "u" | "user" => '@',
133 "r" | "room" => '#',
134 "e" | "event" => '$',
135 "roomid" => '!',
136 _ => return Err(MatrixIdError::UnknownType.into()),
137 };
138 id = format!("{}/{}{}", id, sigil, id_without_sigil);
139 }
140
141 Self::parse_with_sigil(&id)
142 }
143
144 pub(crate) fn to_string_with_sigil(&self) -> String {
151 match self {
152 Self::Room(room_id) => percent_encode(room_id.as_bytes(), RESERVED).to_string(),
153 Self::RoomAlias(room_alias) => {
154 percent_encode(room_alias.as_bytes(), RESERVED).to_string()
155 }
156 Self::User(user_id) => percent_encode(user_id.as_bytes(), RESERVED).to_string(),
157 Self::Event(room_id, event_id) => format!(
158 "{}/{}",
159 percent_encode(room_id.as_bytes(), RESERVED),
160 percent_encode(event_id.as_bytes(), RESERVED),
161 ),
162 }
163 }
164
165 pub(crate) fn to_string_with_type(&self) -> String {
173 match self {
174 Self::Room(room_id) => {
175 format!("roomid/{}", percent_encode(&room_id.as_bytes()[1..], NON_PATH))
176 }
177 Self::RoomAlias(room_alias) => {
178 format!("r/{}", percent_encode(&room_alias.as_bytes()[1..], NON_PATH))
179 }
180 Self::User(user_id) => {
181 format!("u/{}", percent_encode(&user_id.as_bytes()[1..], NON_PATH))
182 }
183 Self::Event(room_id, event_id) => {
184 let room_type = if room_id.is_room_id() { "roomid" } else { "r" };
185 format!(
186 "{}/{}/e/{}",
187 room_type,
188 percent_encode(&room_id.as_bytes()[1..], NON_PATH),
189 percent_encode(&event_id.as_bytes()[1..], NON_PATH),
190 )
191 }
192 }
193 }
194}
195
196impl From<&RoomId> for MatrixId {
197 fn from(room_id: &RoomId) -> Self {
198 Self::Room(room_id.into())
199 }
200}
201
202impl From<&RoomAliasId> for MatrixId {
203 fn from(room_alias: &RoomAliasId) -> Self {
204 Self::RoomAlias(room_alias.into())
205 }
206}
207
208impl From<&UserId> for MatrixId {
209 fn from(user_id: &UserId) -> Self {
210 Self::User(user_id.into())
211 }
212}
213
214impl From<(&RoomOrAliasId, &EventId)> for MatrixId {
215 fn from(ids: (&RoomOrAliasId, &EventId)) -> Self {
216 Self::Event(ids.0.into(), ids.1.into())
217 }
218}
219
220impl From<(&RoomId, &EventId)> for MatrixId {
221 fn from(ids: (&RoomId, &EventId)) -> Self {
222 Self::Event(<&RoomOrAliasId>::from(ids.0).into(), ids.1.into())
223 }
224}
225
226impl From<(&RoomAliasId, &EventId)> for MatrixId {
227 fn from(ids: (&RoomAliasId, &EventId)) -> Self {
228 Self::Event(<&RoomOrAliasId>::from(ids.0).into(), ids.1.into())
229 }
230}
231
232#[derive(Debug, PartialEq, Eq)]
239pub struct MatrixToUri {
240 id: MatrixId,
241 via: Vec<Box<ServerName>>,
242}
243
244impl MatrixToUri {
245 pub(crate) fn new(id: MatrixId, via: Vec<&ServerName>) -> Self {
246 Self { id, via: via.into_iter().map(ToOwned::to_owned).collect() }
247 }
248
249 pub fn id(&self) -> &MatrixId {
251 &self.id
252 }
253
254 pub fn via(&self) -> &[Box<ServerName>] {
256 &self.via
257 }
258
259 pub fn parse(s: &str) -> Result<Self, Error> {
261 let without_base = if let Some(stripped) = s.strip_prefix(MATRIX_TO_BASE_URL) {
262 stripped
263 } else {
264 return Err(MatrixToError::WrongBaseUrl.into());
265 };
266
267 let url = Url::parse(MATRIX_TO_BASE_URL.trim_end_matches("#/"))?.join(without_base)?;
268
269 let id = MatrixId::parse_with_sigil(url.path())?;
270 let mut via = vec![];
271
272 for (key, value) in url.query_pairs() {
273 if key.as_ref() == "via" {
274 via.push(ServerName::parse(value)?);
275 } else {
276 return Err(MatrixToError::UnknownArgument.into());
277 }
278 }
279
280 Ok(Self { id, via })
281 }
282}
283
284impl fmt::Display for MatrixToUri {
285 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286 f.write_str(MATRIX_TO_BASE_URL)?;
287 write!(f, "{}", self.id().to_string_with_sigil())?;
288
289 let mut first = true;
290 for server_name in &self.via {
291 f.write_str(if first { "?via=" } else { "&via=" })?;
292 f.write_str(server_name.as_str())?;
293
294 first = false;
295 }
296
297 Ok(())
298 }
299}
300
301impl TryFrom<&str> for MatrixToUri {
302 type Error = Error;
303
304 fn try_from(s: &str) -> Result<Self, Self::Error> {
305 Self::parse(s)
306 }
307}
308
309#[derive(Clone, Debug, PartialEq, Eq)]
311#[non_exhaustive]
312pub enum UriAction {
313 Join,
318
319 Chat,
326
327 #[doc(hidden)]
328 _Custom(PrivOwnedStr),
329}
330
331impl UriAction {
332 pub fn as_str(&self) -> &str {
334 self.as_ref()
335 }
336
337 fn from<T>(s: T) -> Self
338 where
339 T: AsRef<str> + Into<Box<str>>,
340 {
341 match s.as_ref() {
342 "join" => UriAction::Join,
343 "chat" => UriAction::Chat,
344 _ => UriAction::_Custom(PrivOwnedStr(s.into())),
345 }
346 }
347}
348
349impl AsRef<str> for UriAction {
350 fn as_ref(&self) -> &str {
351 match self {
352 UriAction::Join => "join",
353 UriAction::Chat => "chat",
354 UriAction::_Custom(s) => s.0.as_ref(),
355 }
356 }
357}
358
359impl fmt::Display for UriAction {
360 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
361 write!(f, "{}", self.as_ref())?;
362 Ok(())
363 }
364}
365
366impl From<&str> for UriAction {
367 fn from(s: &str) -> Self {
368 Self::from(s)
369 }
370}
371
372impl From<String> for UriAction {
373 fn from(s: String) -> Self {
374 Self::from(s)
375 }
376}
377
378impl From<Box<str>> for UriAction {
379 fn from(s: Box<str>) -> Self {
380 Self::from(s)
381 }
382}
383
384#[derive(Debug, PartialEq, Eq)]
391pub struct MatrixUri {
392 id: MatrixId,
393 via: Vec<Box<ServerName>>,
394 action: Option<UriAction>,
395}
396
397impl MatrixUri {
398 pub(crate) fn new(id: MatrixId, via: Vec<&ServerName>, action: Option<UriAction>) -> Self {
399 Self { id, via: via.into_iter().map(ToOwned::to_owned).collect(), action }
400 }
401
402 pub fn id(&self) -> &MatrixId {
404 &self.id
405 }
406
407 pub fn via(&self) -> &[Box<ServerName>] {
409 &self.via
410 }
411
412 pub fn action(&self) -> Option<&UriAction> {
414 self.action.as_ref()
415 }
416
417 pub fn parse(s: &str) -> Result<Self, Error> {
419 let url = Url::parse(s)?;
420
421 if url.scheme() != MATRIX_SCHEME {
422 return Err(MatrixUriError::WrongScheme.into());
423 }
424
425 let id = MatrixId::parse_with_type(url.path())?;
426
427 let mut via = vec![];
428 let mut action = None;
429
430 for (key, value) in url.query_pairs() {
431 if key.as_ref() == "via" {
432 via.push(ServerName::parse(value)?);
433 } else if key.as_ref() == "action" {
434 if action.is_some() {
435 return Err(MatrixUriError::TooManyActions.into());
436 };
437
438 action = Some(value.as_ref().into());
439 } else {
440 return Err(MatrixUriError::UnknownQueryItem.into());
441 }
442 }
443
444 Ok(Self { id, via, action })
445 }
446}
447
448impl fmt::Display for MatrixUri {
449 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
450 write!(f, "{}:{}", MATRIX_SCHEME, self.id().to_string_with_type())?;
451
452 let mut first = true;
453 for server_name in &self.via {
454 f.write_str(if first { "?via=" } else { "&via=" })?;
455 f.write_str(server_name.as_str())?;
456
457 first = false;
458 }
459
460 if let Some(action) = self.action() {
461 f.write_str(if first { "?action=" } else { "&action=" })?;
462 f.write_str(action.as_str())?;
463 }
464
465 Ok(())
466 }
467}
468
469impl TryFrom<&str> for MatrixUri {
470 type Error = Error;
471
472 fn try_from(s: &str) -> Result<Self, Self::Error> {
473 Self::parse(s)
474 }
475}
476
477#[cfg(test)]
478mod tests {
479 use matches::assert_matches;
480 use ruma_identifiers_validation::{
481 error::{MatrixIdError, MatrixToError, MatrixUriError},
482 Error,
483 };
484
485 use super::{MatrixId, MatrixToUri, MatrixUri};
486 use crate::{
487 event_id, matrix_uri::UriAction, room_alias_id, room_id, server_name, user_id,
488 RoomOrAliasId,
489 };
490
491 #[test]
492 fn display_matrixtouri() {
493 assert_eq!(
494 user_id!("@jplatte:notareal.hs").matrix_to_uri().to_string(),
495 "https://matrix.to/#/%40jplatte%3Anotareal.hs"
496 );
497 assert_eq!(
498 room_alias_id!("#ruma:notareal.hs").matrix_to_uri().to_string(),
499 "https://matrix.to/#/%23ruma%3Anotareal.hs"
500 );
501 assert_eq!(
502 room_id!("!ruma:notareal.hs")
503 .matrix_to_uri(vec![server_name!("notareal.hs")])
504 .to_string(),
505 "https://matrix.to/#/%21ruma%3Anotareal.hs?via=notareal.hs"
506 );
507 assert_eq!(
508 room_alias_id!("#ruma:notareal.hs")
509 .matrix_to_event_uri(event_id!("$event:notareal.hs"))
510 .to_string(),
511 "https://matrix.to/#/%23ruma%3Anotareal.hs/%24event%3Anotareal.hs"
512 );
513 assert_eq!(
514 room_id!("!ruma:notareal.hs")
515 .matrix_to_event_uri(event_id!("$event:notareal.hs"))
516 .to_string(),
517 "https://matrix.to/#/%21ruma%3Anotareal.hs/%24event%3Anotareal.hs"
518 );
519 }
520
521 #[test]
522 fn parse_valid_matrixid_with_sigil() {
523 assert_eq!(
524 MatrixId::parse_with_sigil("@user:imaginary.hs").expect("Failed to create MatrixId."),
525 MatrixId::User(user_id!("@user:imaginary.hs").into())
526 );
527 assert_eq!(
528 MatrixId::parse_with_sigil("!roomid:imaginary.hs").expect("Failed to create MatrixId."),
529 MatrixId::Room(room_id!("!roomid:imaginary.hs").into())
530 );
531 assert_eq!(
532 MatrixId::parse_with_sigil("#roomalias:imaginary.hs")
533 .expect("Failed to create MatrixId."),
534 MatrixId::RoomAlias(room_alias_id!("#roomalias:imaginary.hs").into())
535 );
536 assert_eq!(
537 MatrixId::parse_with_sigil("!roomid:imaginary.hs/$event:imaginary.hs")
538 .expect("Failed to create MatrixId."),
539 MatrixId::Event(
540 <&RoomOrAliasId>::from(room_id!("!roomid:imaginary.hs")).into(),
541 event_id!("$event:imaginary.hs").into()
542 )
543 );
544 assert_eq!(
545 MatrixId::parse_with_sigil("#roomalias:imaginary.hs/$event:imaginary.hs")
546 .expect("Failed to create MatrixId."),
547 MatrixId::Event(
548 <&RoomOrAliasId>::from(room_alias_id!("#roomalias:imaginary.hs")).into(),
549 event_id!("$event:imaginary.hs").into()
550 )
551 );
552 assert_eq!(
554 MatrixId::parse_with_sigil("$event:imaginary.hs/!roomid:imaginary.hs")
555 .expect("Failed to create MatrixId."),
556 MatrixId::Event(
557 <&RoomOrAliasId>::from(room_id!("!roomid:imaginary.hs")).into(),
558 event_id!("$event:imaginary.hs").into()
559 )
560 );
561 assert_eq!(
562 MatrixId::parse_with_sigil("$event:imaginary.hs/#roomalias:imaginary.hs")
563 .expect("Failed to create MatrixId."),
564 MatrixId::Event(
565 <&RoomOrAliasId>::from(room_alias_id!("#roomalias:imaginary.hs")).into(),
566 event_id!("$event:imaginary.hs").into()
567 )
568 );
569 assert_eq!(
571 MatrixId::parse_with_sigil("/@user:imaginary.hs").expect("Failed to create MatrixId."),
572 MatrixId::User(user_id!("@user:imaginary.hs").into())
573 );
574 assert_eq!(
576 MatrixId::parse_with_sigil("!roomid:imaginary.hs/")
577 .expect("Failed to create MatrixId."),
578 MatrixId::Room(room_id!("!roomid:imaginary.hs").into())
579 );
580 assert_eq!(
582 MatrixId::parse_with_sigil("/#roomalias:imaginary.hs/")
583 .expect("Failed to create MatrixId."),
584 MatrixId::RoomAlias(room_alias_id!("#roomalias:imaginary.hs").into())
585 );
586 }
587
588 #[test]
589 fn parse_matrixid_no_identifier() {
590 assert_eq!(MatrixId::parse_with_sigil("").unwrap_err(), MatrixIdError::NoIdentifier.into());
591 assert_eq!(
592 MatrixId::parse_with_sigil("/").unwrap_err(),
593 MatrixIdError::NoIdentifier.into()
594 );
595 }
596
597 #[test]
598 fn parse_matrixid_too_many_identifiers() {
599 assert_eq!(
600 MatrixId::parse_with_sigil(
601 "@user:imaginary.hs/#room:imaginary.hs/$event1:imaginary.hs"
602 )
603 .unwrap_err(),
604 MatrixIdError::TooManyIdentifiers.into()
605 );
606 }
607
608 #[test]
609 fn parse_matrixid_unknown_identifier_pair() {
610 assert_eq!(
611 MatrixId::parse_with_sigil("!roomid:imaginary.hs/@user:imaginary.hs").unwrap_err(),
612 MatrixIdError::UnknownIdentifierPair.into()
613 );
614 assert_eq!(
615 MatrixId::parse_with_sigil("#roomalias:imaginary.hs/notanidentifier").unwrap_err(),
616 MatrixIdError::UnknownIdentifierPair.into()
617 );
618 assert_eq!(
619 MatrixId::parse_with_sigil("$event:imaginary.hs/$otherevent:imaginary.hs").unwrap_err(),
620 MatrixIdError::UnknownIdentifierPair.into()
621 );
622 assert_eq!(
623 MatrixId::parse_with_sigil("notanidentifier/neitheristhis").unwrap_err(),
624 MatrixIdError::UnknownIdentifierPair.into()
625 );
626 }
627
628 #[test]
629 fn parse_matrixid_missing_room() {
630 assert_eq!(
631 MatrixId::parse_with_sigil("$event:imaginary.hs").unwrap_err(),
632 MatrixIdError::MissingRoom.into()
633 );
634 }
635
636 #[test]
637 fn parse_matrixid_unknown_identifier() {
638 assert_eq!(
639 MatrixId::parse_with_sigil("event:imaginary.hs").unwrap_err(),
640 MatrixIdError::UnknownIdentifier.into()
641 );
642 assert_eq!(
643 MatrixId::parse_with_sigil("notanidentifier").unwrap_err(),
644 MatrixIdError::UnknownIdentifier.into()
645 );
646 }
647
648 #[test]
649 fn parse_matrixtouri_valid_uris() {
650 let matrix_to = MatrixToUri::parse("https://matrix.to/#/%40jplatte%3Anotareal.hs")
651 .expect("Failed to create MatrixToUri.");
652 assert_eq!(matrix_to.id(), &user_id!("@jplatte:notareal.hs").into());
653
654 let matrix_to = MatrixToUri::parse("https://matrix.to/#/%23ruma%3Anotareal.hs")
655 .expect("Failed to create MatrixToUri.");
656 assert_eq!(matrix_to.id(), &room_alias_id!("#ruma:notareal.hs").into());
657
658 let matrix_to =
659 MatrixToUri::parse("https://matrix.to/#/%21ruma%3Anotareal.hs?via=notareal.hs")
660 .expect("Failed to create MatrixToUri.");
661 assert_eq!(matrix_to.id(), &room_id!("!ruma:notareal.hs").into());
662 assert_eq!(matrix_to.via(), &vec![server_name!("notareal.hs").to_owned()]);
663
664 let matrix_to =
665 MatrixToUri::parse("https://matrix.to/#/%23ruma%3Anotareal.hs/%24event%3Anotareal.hs")
666 .expect("Failed to create MatrixToUri.");
667 assert_eq!(
668 matrix_to.id(),
669 &(room_alias_id!("#ruma:notareal.hs"), event_id!("$event:notareal.hs")).into()
670 );
671
672 let matrix_to =
673 MatrixToUri::parse("https://matrix.to/#/%21ruma%3Anotareal.hs/%24event%3Anotareal.hs")
674 .expect("Failed to create MatrixToUri.");
675 assert_eq!(
676 matrix_to.id(),
677 &(room_id!("!ruma:notareal.hs"), event_id!("$event:notareal.hs")).into()
678 );
679 assert!(matrix_to.via().is_empty());
680 }
681
682 #[test]
683 fn parse_matrixtouri_wrong_base_url() {
684 assert_eq!(MatrixToUri::parse("").unwrap_err(), MatrixToError::WrongBaseUrl.into());
685 assert_eq!(
686 MatrixToUri::parse("https://notreal.to/#/").unwrap_err(),
687 MatrixToError::WrongBaseUrl.into()
688 );
689 }
690
691 #[test]
692 fn parse_matrixtouri_wrong_identifier() {
693 assert_matches!(
694 MatrixToUri::parse("https://matrix.to/#/notanidentifier").unwrap_err(),
695 Error::InvalidMatrixId(_)
696 );
697 assert_matches!(
698 MatrixToUri::parse("https://matrix.to/#/").unwrap_err(),
699 Error::InvalidMatrixId(_)
700 );
701 assert_matches!(
702 MatrixToUri::parse(
703 "https://matrix.to/#/%40jplatte%3Anotareal.hs/%24event%3Anotareal.hs"
704 )
705 .unwrap_err(),
706 Error::InvalidMatrixId(_)
707 );
708 }
709
710 #[test]
711 fn parse_matrixtouri_unknown_arguments() {
712 assert_eq!(
713 MatrixToUri::parse(
714 "https://matrix.to/#/%21ruma%3Anotareal.hs?via=notareal.hs&custom=data"
715 )
716 .unwrap_err(),
717 MatrixToError::UnknownArgument.into()
718 )
719 }
720
721 #[test]
722 fn display_matrixuri() {
723 assert_eq!(
724 user_id!("@jplatte:notareal.hs").matrix_uri(false).to_string(),
725 "matrix:u/jplatte:notareal.hs"
726 );
727 assert_eq!(
728 user_id!("@jplatte:notareal.hs").matrix_uri(true).to_string(),
729 "matrix:u/jplatte:notareal.hs?action=chat"
730 );
731 assert_eq!(
732 room_alias_id!("#ruma:notareal.hs").matrix_uri(false).to_string(),
733 "matrix:r/ruma:notareal.hs"
734 );
735 assert_eq!(
736 room_alias_id!("#ruma:notareal.hs").matrix_uri(true).to_string(),
737 "matrix:r/ruma:notareal.hs?action=join"
738 );
739 assert_eq!(
740 room_id!("!ruma:notareal.hs")
741 .matrix_uri(vec![server_name!("notareal.hs")], false)
742 .to_string(),
743 "matrix:roomid/ruma:notareal.hs?via=notareal.hs"
744 );
745 assert_eq!(
746 room_id!("!ruma:notareal.hs")
747 .matrix_uri(
748 vec![server_name!("notareal.hs"), server_name!("anotherunreal.hs")],
749 true
750 )
751 .to_string(),
752 "matrix:roomid/ruma:notareal.hs?via=notareal.hs&via=anotherunreal.hs&action=join"
753 );
754 assert_eq!(
755 room_alias_id!("#ruma:notareal.hs")
756 .matrix_event_uri(event_id!("$event:notareal.hs"))
757 .to_string(),
758 "matrix:r/ruma:notareal.hs/e/event:notareal.hs"
759 );
760 assert_eq!(
761 room_id!("!ruma:notareal.hs")
762 .matrix_event_uri(event_id!("$event:notareal.hs"), vec![])
763 .to_string(),
764 "matrix:roomid/ruma:notareal.hs/e/event:notareal.hs"
765 );
766 }
767
768 #[test]
769 fn parse_valid_matrixid_with_type() {
770 assert_eq!(
771 MatrixId::parse_with_type("u/user:imaginary.hs").expect("Failed to create MatrixId."),
772 MatrixId::User(user_id!("@user:imaginary.hs").into())
773 );
774 assert_eq!(
775 MatrixId::parse_with_type("user/user:imaginary.hs")
776 .expect("Failed to create MatrixId."),
777 MatrixId::User(user_id!("@user:imaginary.hs").into())
778 );
779 assert_eq!(
780 MatrixId::parse_with_type("roomid/roomid:imaginary.hs")
781 .expect("Failed to create MatrixId."),
782 MatrixId::Room(room_id!("!roomid:imaginary.hs").into())
783 );
784 assert_eq!(
785 MatrixId::parse_with_type("r/roomalias:imaginary.hs")
786 .expect("Failed to create MatrixId."),
787 MatrixId::RoomAlias(room_alias_id!("#roomalias:imaginary.hs").into())
788 );
789 assert_eq!(
790 MatrixId::parse_with_type("room/roomalias:imaginary.hs")
791 .expect("Failed to create MatrixId."),
792 MatrixId::RoomAlias(room_alias_id!("#roomalias:imaginary.hs").into())
793 );
794 assert_eq!(
795 MatrixId::parse_with_type("roomid/roomid:imaginary.hs/e/event:imaginary.hs")
796 .expect("Failed to create MatrixId."),
797 MatrixId::Event(
798 <&RoomOrAliasId>::from(room_id!("!roomid:imaginary.hs")).into(),
799 event_id!("$event:imaginary.hs").into()
800 )
801 );
802 assert_eq!(
803 MatrixId::parse_with_type("r/roomalias:imaginary.hs/e/event:imaginary.hs")
804 .expect("Failed to create MatrixId."),
805 MatrixId::Event(
806 <&RoomOrAliasId>::from(room_alias_id!("#roomalias:imaginary.hs")).into(),
807 event_id!("$event:imaginary.hs").into()
808 )
809 );
810 assert_eq!(
811 MatrixId::parse_with_type("room/roomalias:imaginary.hs/event/event:imaginary.hs")
812 .expect("Failed to create MatrixId."),
813 MatrixId::Event(
814 <&RoomOrAliasId>::from(room_alias_id!("#roomalias:imaginary.hs")).into(),
815 event_id!("$event:imaginary.hs").into()
816 )
817 );
818 assert_eq!(
820 MatrixId::parse_with_type("e/event:imaginary.hs/roomid/roomid:imaginary.hs")
821 .expect("Failed to create MatrixId."),
822 MatrixId::Event(
823 <&RoomOrAliasId>::from(room_id!("!roomid:imaginary.hs")).into(),
824 event_id!("$event:imaginary.hs").into()
825 )
826 );
827 assert_eq!(
828 MatrixId::parse_with_type("e/event:imaginary.hs/r/roomalias:imaginary.hs")
829 .expect("Failed to create MatrixId."),
830 MatrixId::Event(
831 <&RoomOrAliasId>::from(room_alias_id!("#roomalias:imaginary.hs")).into(),
832 event_id!("$event:imaginary.hs").into()
833 )
834 );
835 assert_eq!(
837 MatrixId::parse_with_type("/u/user:imaginary.hs").expect("Failed to create MatrixId."),
838 MatrixId::User(user_id!("@user:imaginary.hs").into())
839 );
840 assert_eq!(
842 MatrixId::parse_with_type("roomid/roomid:imaginary.hs/")
843 .expect("Failed to create MatrixId."),
844 MatrixId::Room(room_id!("!roomid:imaginary.hs").into())
845 );
846 assert_eq!(
848 MatrixId::parse_with_type("/r/roomalias:imaginary.hs/")
849 .expect("Failed to create MatrixId."),
850 MatrixId::RoomAlias(room_alias_id!("#roomalias:imaginary.hs").into())
851 );
852 }
853
854 #[test]
855 fn parse_matrixid_type_no_identifier() {
856 assert_eq!(MatrixId::parse_with_type("").unwrap_err(), MatrixIdError::NoIdentifier.into());
857 assert_eq!(MatrixId::parse_with_type("/").unwrap_err(), MatrixIdError::NoIdentifier.into());
858 }
859
860 #[test]
861 fn parse_matrixid_invalid_parts_number() {
862 assert_eq!(
863 MatrixId::parse_with_type("u/user:imaginary.hs/r/room:imaginary.hs/e").unwrap_err(),
864 MatrixIdError::InvalidPartsNumber.into()
865 );
866 }
867
868 #[test]
869 fn parse_matrixid_unknown_type() {
870 assert_eq!(
871 MatrixId::parse_with_type("notatype/fake:notareal.hs").unwrap_err(),
872 MatrixIdError::UnknownType.into()
873 );
874 }
875
876 #[test]
877 fn parse_matrixuri_valid_uris() {
878 let matrix_uri =
879 MatrixUri::parse("matrix:u/jplatte:notareal.hs").expect("Failed to create MatrixUri.");
880 assert_eq!(matrix_uri.id(), &user_id!("@jplatte:notareal.hs").into());
881 assert!(matrix_uri.action().is_none());
882
883 let matrix_uri = MatrixUri::parse("matrix:u/jplatte:notareal.hs?action=chat")
884 .expect("Failed to create MatrixUri.");
885 assert_eq!(matrix_uri.id(), &user_id!("@jplatte:notareal.hs").into());
886 assert_eq!(matrix_uri.action(), Some(&UriAction::Chat));
887
888 let matrix_uri =
889 MatrixUri::parse("matrix:r/ruma:notareal.hs").expect("Failed to create MatrixToUri.");
890 assert_eq!(matrix_uri.id(), &room_alias_id!("#ruma:notareal.hs").into());
891
892 let matrix_uri = MatrixUri::parse("matrix:roomid/ruma:notareal.hs?via=notareal.hs")
893 .expect("Failed to create MatrixToUri.");
894 assert_eq!(matrix_uri.id(), &room_id!("!ruma:notareal.hs").into());
895 assert_eq!(matrix_uri.via(), &vec![server_name!("notareal.hs").to_owned()]);
896 assert!(matrix_uri.action().is_none());
897
898 let matrix_uri = MatrixUri::parse("matrix:r/ruma:notareal.hs/e/event:notareal.hs")
899 .expect("Failed to create MatrixToUri.");
900 assert_eq!(
901 matrix_uri.id(),
902 &(room_alias_id!("#ruma:notareal.hs"), event_id!("$event:notareal.hs")).into()
903 );
904
905 let matrix_uri = MatrixUri::parse("matrix:roomid/ruma:notareal.hs/e/event:notareal.hs")
906 .expect("Failed to create MatrixToUri.");
907 assert_eq!(
908 matrix_uri.id(),
909 &(room_id!("!ruma:notareal.hs"), event_id!("$event:notareal.hs")).into()
910 );
911 assert!(matrix_uri.via().is_empty());
912 assert!(matrix_uri.action().is_none());
913
914 let matrix_uri =
915 MatrixUri::parse("matrix:roomid/ruma:notareal.hs/e/event:notareal.hs?via=notareal.hs&action=join&via=anotherinexistant.hs")
916 .expect("Failed to create MatrixToUri.");
917 assert_eq!(
918 matrix_uri.id(),
919 &(room_id!("!ruma:notareal.hs"), event_id!("$event:notareal.hs")).into()
920 );
921 assert_eq!(
922 matrix_uri.via(),
923 &vec![
924 server_name!("notareal.hs").to_owned(),
925 server_name!("anotherinexistant.hs").to_owned()
926 ]
927 );
928 assert_eq!(matrix_uri.action(), Some(&UriAction::Join));
929 }
930
931 #[test]
932 fn parse_matrixuri_invalid_uri() {
933 assert_eq!(MatrixUri::parse("").unwrap_err(), Error::InvalidUri);
934 }
935
936 #[test]
937 fn parse_matrixuri_wrong_scheme() {
938 assert_eq!(
939 MatrixUri::parse("unknown:u/user:notareal.hs").unwrap_err(),
940 MatrixUriError::WrongScheme.into()
941 );
942 }
943
944 #[test]
945 fn parse_matrixuri_too_many_actions() {
946 assert_eq!(
947 MatrixUri::parse("matrix:u/user:notareal.hs?action=chat&action=join").unwrap_err(),
948 MatrixUriError::TooManyActions.into()
949 );
950 }
951
952 #[test]
953 fn parse_matrixuri_unknown_query_item() {
954 assert_eq!(
955 MatrixUri::parse("matrix:roomid/roomid:notareal.hs?via=notareal.hs&fake=data")
956 .unwrap_err(),
957 MatrixUriError::UnknownQueryItem.into()
958 );
959 }
960
961 #[test]
962 fn parse_matrixuri_wrong_identifier() {
963 assert_matches!(
964 MatrixUri::parse("matrix:notanidentifier").unwrap_err(),
965 Error::InvalidMatrixId(_)
966 );
967 assert_matches!(MatrixUri::parse("matrix:").unwrap_err(), Error::InvalidMatrixId(_));
968 assert_matches!(
969 MatrixUri::parse("matrix:u/jplatte:notareal.hs/e/event:notareal.hs").unwrap_err(),
970 Error::InvalidMatrixId(_)
971 );
972 }
973}