1use std::{fmt, str::FromStr};
4
5use percent_encoding::{percent_decode_str, percent_encode};
6use ruma_identifiers_validation::{
7 error::{MatrixIdError, MatrixToError, MatrixUriError},
8 Error,
9};
10use url::Url;
11
12use super::{
13 EventId, OwnedEventId, OwnedRoomAliasId, OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName,
14 OwnedUserId, RoomAliasId, RoomId, RoomOrAliasId, UserId,
15};
16use crate::{percent_encode::PATH_PERCENT_ENCODE_SET, PrivOwnedStr, ServerName};
17
18const MATRIX_TO_BASE_URL: &str = "https://matrix.to/#/";
19const MATRIX_SCHEME: &str = "matrix";
20
21#[derive(Clone, Debug, PartialEq, Eq)]
23#[non_exhaustive]
24pub enum MatrixId {
25 Room(OwnedRoomId),
27
28 RoomAlias(OwnedRoomAliasId),
30
31 User(OwnedUserId),
33
34 Event(OwnedRoomOrAliasId, OwnedEventId),
39}
40
41impl MatrixId {
42 pub(crate) fn parse_with_sigil(s: &str) -> Result<Self, Error> {
50 let s = if let Some(stripped) = s.strip_prefix('/') { stripped } else { s };
51 let s = if let Some(stripped) = s.strip_suffix('/') { stripped } else { s };
52 if s.is_empty() {
53 return Err(MatrixIdError::NoIdentifier.into());
54 }
55
56 if s.matches('/').count() > 1 {
57 return Err(MatrixIdError::TooManyIdentifiers.into());
58 }
59
60 if let Some((first_raw, second_raw)) = s.split_once('/') {
61 let first = percent_decode_str(first_raw).decode_utf8()?;
62 let second = percent_decode_str(second_raw).decode_utf8()?;
63
64 match first.as_bytes()[0] {
65 b'!' | b'#' if second.as_bytes()[0] == b'$' => {
66 let room_id = <&RoomOrAliasId>::try_from(first.as_ref())?;
67 let event_id = <&EventId>::try_from(second.as_ref())?;
68 Ok((room_id, event_id).into())
69 }
70 b'$' if matches!(second.as_bytes()[0], b'!' | b'#') => {
71 let room_id = <&RoomOrAliasId>::try_from(second.as_ref())?;
72 let event_id = <&EventId>::try_from(first.as_ref())?;
73 Ok((room_id, event_id).into())
74 }
75 _ => Err(MatrixIdError::UnknownIdentifierPair.into()),
76 }
77 } else {
78 let id = percent_decode_str(s).decode_utf8()?;
79
80 match id.as_bytes()[0] {
81 b'@' => Ok(<&UserId>::try_from(id.as_ref())?.into()),
82 b'!' => Ok(<&RoomId>::try_from(id.as_ref())?.into()),
83 b'#' => Ok(<&RoomAliasId>::try_from(id.as_ref())?.into()),
84 b'$' => Err(MatrixIdError::MissingRoom.into()),
85 _ => Err(MatrixIdError::UnknownIdentifier.into()),
86 }
87 }
88 }
89
90 pub(crate) fn parse_with_type(s: &str) -> Result<Self, Error> {
99 let s = if let Some(stripped) = s.strip_prefix('/') { stripped } else { s };
100 let s = if let Some(stripped) = s.strip_suffix('/') { stripped } else { s };
101 if s.is_empty() {
102 return Err(MatrixIdError::NoIdentifier.into());
103 }
104
105 if ![1, 3].contains(&s.matches('/').count()) {
106 return Err(MatrixIdError::InvalidPartsNumber.into());
107 }
108
109 let mut id = String::new();
110 let mut split = s.split('/');
111 while let (Some(type_), Some(id_without_sigil)) = (split.next(), split.next()) {
112 let sigil = match type_ {
113 "u" | "user" => '@',
114 "r" | "room" => '#',
115 "e" | "event" => '$',
116 "roomid" => '!',
117 _ => return Err(MatrixIdError::UnknownType.into()),
118 };
119 id = format!("{id}/{sigil}{id_without_sigil}");
120 }
121
122 Self::parse_with_sigil(&id)
123 }
124
125 pub(crate) fn to_string_with_sigil(&self) -> String {
132 match self {
133 Self::Room(room_id) => {
134 percent_encode(room_id.as_bytes(), PATH_PERCENT_ENCODE_SET).to_string()
135 }
136 Self::RoomAlias(room_alias) => {
137 percent_encode(room_alias.as_bytes(), PATH_PERCENT_ENCODE_SET).to_string()
138 }
139 Self::User(user_id) => {
140 percent_encode(user_id.as_bytes(), PATH_PERCENT_ENCODE_SET).to_string()
141 }
142 Self::Event(room_id, event_id) => format!(
143 "{}/{}",
144 percent_encode(room_id.as_bytes(), PATH_PERCENT_ENCODE_SET),
145 percent_encode(event_id.as_bytes(), PATH_PERCENT_ENCODE_SET),
146 ),
147 }
148 }
149
150 pub(crate) fn to_string_with_type(&self) -> String {
158 match self {
159 Self::Room(room_id) => {
160 format!(
161 "roomid/{}",
162 percent_encode(&room_id.as_bytes()[1..], PATH_PERCENT_ENCODE_SET)
163 )
164 }
165 Self::RoomAlias(room_alias) => {
166 format!(
167 "r/{}",
168 percent_encode(&room_alias.as_bytes()[1..], PATH_PERCENT_ENCODE_SET)
169 )
170 }
171 Self::User(user_id) => {
172 format!("u/{}", percent_encode(&user_id.as_bytes()[1..], PATH_PERCENT_ENCODE_SET))
173 }
174 Self::Event(room_id, event_id) => {
175 let room_type = if room_id.is_room_id() { "roomid" } else { "r" };
176 format!(
177 "{}/{}/e/{}",
178 room_type,
179 percent_encode(&room_id.as_bytes()[1..], PATH_PERCENT_ENCODE_SET),
180 percent_encode(&event_id.as_bytes()[1..], PATH_PERCENT_ENCODE_SET),
181 )
182 }
183 }
184 }
185}
186
187impl From<OwnedRoomId> for MatrixId {
188 fn from(room_id: OwnedRoomId) -> Self {
189 Self::Room(room_id)
190 }
191}
192
193impl From<&RoomId> for MatrixId {
194 fn from(room_id: &RoomId) -> Self {
195 room_id.to_owned().into()
196 }
197}
198
199impl From<OwnedRoomAliasId> for MatrixId {
200 fn from(room_alias: OwnedRoomAliasId) -> Self {
201 Self::RoomAlias(room_alias)
202 }
203}
204
205impl From<&RoomAliasId> for MatrixId {
206 fn from(room_alias: &RoomAliasId) -> Self {
207 room_alias.to_owned().into()
208 }
209}
210
211impl From<OwnedUserId> for MatrixId {
212 fn from(user_id: OwnedUserId) -> Self {
213 Self::User(user_id)
214 }
215}
216
217impl From<&UserId> for MatrixId {
218 fn from(user_id: &UserId) -> Self {
219 user_id.to_owned().into()
220 }
221}
222
223impl From<(OwnedRoomOrAliasId, OwnedEventId)> for MatrixId {
224 fn from(ids: (OwnedRoomOrAliasId, OwnedEventId)) -> Self {
225 Self::Event(ids.0, ids.1)
226 }
227}
228
229impl From<(&RoomOrAliasId, &EventId)> for MatrixId {
230 fn from(ids: (&RoomOrAliasId, &EventId)) -> Self {
231 (ids.0.to_owned(), ids.1.to_owned()).into()
232 }
233}
234
235impl From<(OwnedRoomId, OwnedEventId)> for MatrixId {
236 fn from(ids: (OwnedRoomId, OwnedEventId)) -> Self {
237 Self::Event(ids.0.into(), ids.1)
238 }
239}
240
241impl From<(&RoomId, &EventId)> for MatrixId {
242 fn from(ids: (&RoomId, &EventId)) -> Self {
243 (ids.0.to_owned(), ids.1.to_owned()).into()
244 }
245}
246
247impl From<(OwnedRoomAliasId, OwnedEventId)> for MatrixId {
248 fn from(ids: (OwnedRoomAliasId, OwnedEventId)) -> Self {
249 Self::Event(ids.0.into(), ids.1)
250 }
251}
252
253impl From<(&RoomAliasId, &EventId)> for MatrixId {
254 fn from(ids: (&RoomAliasId, &EventId)) -> Self {
255 (ids.0.to_owned(), ids.1.to_owned()).into()
256 }
257}
258
259#[derive(Debug, Clone, PartialEq, Eq)]
266pub struct MatrixToUri {
267 id: MatrixId,
268 via: Vec<OwnedServerName>,
269}
270
271impl MatrixToUri {
272 pub(crate) fn new(id: MatrixId, via: Vec<OwnedServerName>) -> Self {
273 Self { id, via }
274 }
275
276 pub fn id(&self) -> &MatrixId {
278 &self.id
279 }
280
281 pub fn via(&self) -> &[OwnedServerName] {
283 &self.via
284 }
285
286 pub fn parse(s: &str) -> Result<Self, Error> {
288 let s = s.strip_prefix(MATRIX_TO_BASE_URL).ok_or(MatrixToError::WrongBaseUrl)?;
300 let s = s.strip_suffix('/').unwrap_or(s);
301
302 let mut parts = s.split('?');
304
305 let ids_part = parts.next().expect("a split iterator yields at least one value");
306 let id = MatrixId::parse_with_sigil(ids_part)?;
307
308 let via = parts
310 .next()
311 .map(|query| {
312 let query_parts = form_urlencoded::parse(query.as_bytes());
314
315 query_parts
316 .map(|(key, value)| {
317 (key == "via")
318 .then(|| ServerName::parse(&value))
319 .unwrap_or_else(|| Err(MatrixToError::UnknownArgument.into()))
320 })
321 .collect::<Result<Vec<_>, _>>()
322 })
323 .transpose()?
324 .unwrap_or_default();
325
326 if parts.next().is_some() {
328 return Err(MatrixToError::InvalidUrl.into());
329 }
330
331 Ok(Self { id, via })
332 }
333}
334
335impl fmt::Display for MatrixToUri {
336 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
337 f.write_str(MATRIX_TO_BASE_URL)?;
338 write!(f, "{}", self.id().to_string_with_sigil())?;
339
340 let mut first = true;
341 for server_name in &self.via {
342 f.write_str(if first { "?via=" } else { "&via=" })?;
343 f.write_str(server_name.as_str())?;
344
345 first = false;
346 }
347
348 Ok(())
349 }
350}
351
352impl TryFrom<&str> for MatrixToUri {
353 type Error = Error;
354
355 fn try_from(s: &str) -> Result<Self, Self::Error> {
356 Self::parse(s)
357 }
358}
359
360impl FromStr for MatrixToUri {
361 type Err = Error;
362
363 fn from_str(s: &str) -> Result<Self, Self::Err> {
364 Self::parse(s)
365 }
366}
367
368#[derive(Clone, Debug, PartialEq, Eq)]
370#[non_exhaustive]
371pub enum UriAction {
372 Join,
377
378 Chat,
385
386 #[doc(hidden)]
387 _Custom(PrivOwnedStr),
388}
389
390impl UriAction {
391 pub fn as_str(&self) -> &str {
393 self.as_ref()
394 }
395
396 fn from<T>(s: T) -> Self
397 where
398 T: AsRef<str> + Into<Box<str>>,
399 {
400 match s.as_ref() {
401 "join" => UriAction::Join,
402 "chat" => UriAction::Chat,
403 _ => UriAction::_Custom(PrivOwnedStr(s.into())),
404 }
405 }
406}
407
408impl AsRef<str> for UriAction {
409 fn as_ref(&self) -> &str {
410 match self {
411 UriAction::Join => "join",
412 UriAction::Chat => "chat",
413 UriAction::_Custom(s) => s.0.as_ref(),
414 }
415 }
416}
417
418impl fmt::Display for UriAction {
419 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420 write!(f, "{}", self.as_ref())?;
421 Ok(())
422 }
423}
424
425impl From<&str> for UriAction {
426 fn from(s: &str) -> Self {
427 Self::from(s)
428 }
429}
430
431impl From<String> for UriAction {
432 fn from(s: String) -> Self {
433 Self::from(s)
434 }
435}
436
437impl From<Box<str>> for UriAction {
438 fn from(s: Box<str>) -> Self {
439 Self::from(s)
440 }
441}
442
443#[derive(Debug, Clone, PartialEq, Eq)]
450pub struct MatrixUri {
451 id: MatrixId,
452 via: Vec<OwnedServerName>,
453 action: Option<UriAction>,
454}
455
456impl MatrixUri {
457 pub(crate) fn new(id: MatrixId, via: Vec<OwnedServerName>, action: Option<UriAction>) -> Self {
458 Self { id, via, action }
459 }
460
461 pub fn id(&self) -> &MatrixId {
463 &self.id
464 }
465
466 pub fn via(&self) -> &[OwnedServerName] {
468 &self.via
469 }
470
471 pub fn action(&self) -> Option<&UriAction> {
473 self.action.as_ref()
474 }
475
476 pub fn parse(s: &str) -> Result<Self, Error> {
478 let url = Url::parse(s).map_err(|_| MatrixToError::InvalidUrl)?;
479
480 if url.scheme() != MATRIX_SCHEME {
481 return Err(MatrixUriError::WrongScheme.into());
482 }
483
484 let id = MatrixId::parse_with_type(url.path())?;
485
486 let mut via = vec![];
487 let mut action = None;
488
489 for (key, value) in url.query_pairs() {
490 if key.as_ref() == "via" {
491 via.push(value.parse()?);
492 } else if key.as_ref() == "action" {
493 if action.is_some() {
494 return Err(MatrixUriError::TooManyActions.into());
495 };
496
497 action = Some(value.as_ref().into());
498 } else {
499 return Err(MatrixUriError::UnknownQueryItem.into());
500 }
501 }
502
503 Ok(Self { id, via, action })
504 }
505}
506
507impl fmt::Display for MatrixUri {
508 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
509 write!(f, "{MATRIX_SCHEME}:{}", self.id().to_string_with_type())?;
510
511 let mut first = true;
512 for server_name in &self.via {
513 f.write_str(if first { "?via=" } else { "&via=" })?;
514 f.write_str(server_name.as_str())?;
515
516 first = false;
517 }
518
519 if let Some(action) = self.action() {
520 f.write_str(if first { "?action=" } else { "&action=" })?;
521 f.write_str(action.as_str())?;
522 }
523
524 Ok(())
525 }
526}
527
528impl TryFrom<&str> for MatrixUri {
529 type Error = Error;
530
531 fn try_from(s: &str) -> Result<Self, Self::Error> {
532 Self::parse(s)
533 }
534}
535
536impl FromStr for MatrixUri {
537 type Err = Error;
538
539 fn from_str(s: &str) -> Result<Self, Self::Err> {
540 Self::parse(s)
541 }
542}
543
544#[cfg(test)]
545mod tests {
546 use assert_matches2::assert_matches;
547 use ruma_identifiers_validation::{
548 error::{MatrixIdError, MatrixToError, MatrixUriError},
549 Error,
550 };
551
552 use super::{MatrixId, MatrixToUri, MatrixUri};
553 use crate::{
554 event_id, matrix_uri::UriAction, room_alias_id, room_id, server_name, user_id,
555 RoomOrAliasId,
556 };
557
558 #[test]
559 fn display_matrixtouri() {
560 assert_eq!(
561 user_id!("@jplatte:notareal.hs").matrix_to_uri().to_string(),
562 "https://matrix.to/#/@jplatte:notareal.hs"
563 );
564 assert_eq!(
565 room_alias_id!("#ruma:notareal.hs").matrix_to_uri().to_string(),
566 "https://matrix.to/#/%23ruma:notareal.hs"
567 );
568 assert_eq!(
569 room_id!("!ruma:notareal.hs").matrix_to_uri().to_string(),
570 "https://matrix.to/#/!ruma:notareal.hs"
571 );
572 assert_eq!(
573 room_id!("!ruma:notareal.hs")
574 .matrix_to_uri_via(vec![server_name!("notareal.hs")])
575 .to_string(),
576 "https://matrix.to/#/!ruma:notareal.hs?via=notareal.hs"
577 );
578 #[allow(deprecated)]
579 let uri = room_alias_id!("#ruma:notareal.hs")
580 .matrix_to_event_uri(event_id!("$event:notareal.hs"))
581 .to_string();
582 assert_eq!(uri, "https://matrix.to/#/%23ruma:notareal.hs/$event:notareal.hs");
583 assert_eq!(
584 room_id!("!ruma:notareal.hs")
585 .matrix_to_event_uri(event_id!("$event:notareal.hs"))
586 .to_string(),
587 "https://matrix.to/#/!ruma:notareal.hs/$event:notareal.hs"
588 );
589 assert_eq!(
590 room_id!("!ruma:notareal.hs")
591 .matrix_to_event_uri_via(
592 event_id!("$event:notareal.hs"),
593 vec![server_name!("notareal.hs")]
594 )
595 .to_string(),
596 "https://matrix.to/#/!ruma:notareal.hs/$event:notareal.hs?via=notareal.hs"
597 );
598 }
599
600 #[test]
601 fn parse_valid_matrixid_with_sigil() {
602 assert_eq!(
603 MatrixId::parse_with_sigil("@user:imaginary.hs").expect("Failed to create MatrixId."),
604 MatrixId::User(user_id!("@user:imaginary.hs").into())
605 );
606 assert_eq!(
607 MatrixId::parse_with_sigil("!roomid:imaginary.hs").expect("Failed to create MatrixId."),
608 MatrixId::Room(room_id!("!roomid:imaginary.hs").into())
609 );
610 assert_eq!(
611 MatrixId::parse_with_sigil("#roomalias:imaginary.hs")
612 .expect("Failed to create MatrixId."),
613 MatrixId::RoomAlias(room_alias_id!("#roomalias:imaginary.hs").into())
614 );
615 assert_eq!(
616 MatrixId::parse_with_sigil("!roomid:imaginary.hs/$event:imaginary.hs")
617 .expect("Failed to create MatrixId."),
618 MatrixId::Event(
619 <&RoomOrAliasId>::from(room_id!("!roomid:imaginary.hs")).into(),
620 event_id!("$event:imaginary.hs").into()
621 )
622 );
623 assert_eq!(
624 MatrixId::parse_with_sigil("#roomalias:imaginary.hs/$event:imaginary.hs")
625 .expect("Failed to create MatrixId."),
626 MatrixId::Event(
627 <&RoomOrAliasId>::from(room_alias_id!("#roomalias:imaginary.hs")).into(),
628 event_id!("$event:imaginary.hs").into()
629 )
630 );
631 assert_eq!(
633 MatrixId::parse_with_sigil("$event:imaginary.hs/!roomid:imaginary.hs")
634 .expect("Failed to create MatrixId."),
635 MatrixId::Event(
636 <&RoomOrAliasId>::from(room_id!("!roomid:imaginary.hs")).into(),
637 event_id!("$event:imaginary.hs").into()
638 )
639 );
640 assert_eq!(
641 MatrixId::parse_with_sigil("$event:imaginary.hs/#roomalias:imaginary.hs")
642 .expect("Failed to create MatrixId."),
643 MatrixId::Event(
644 <&RoomOrAliasId>::from(room_alias_id!("#roomalias:imaginary.hs")).into(),
645 event_id!("$event:imaginary.hs").into()
646 )
647 );
648 assert_eq!(
650 MatrixId::parse_with_sigil("/@user:imaginary.hs").expect("Failed to create MatrixId."),
651 MatrixId::User(user_id!("@user:imaginary.hs").into())
652 );
653 assert_eq!(
655 MatrixId::parse_with_sigil("!roomid:imaginary.hs/")
656 .expect("Failed to create MatrixId."),
657 MatrixId::Room(room_id!("!roomid:imaginary.hs").into())
658 );
659 assert_eq!(
661 MatrixId::parse_with_sigil("/#roomalias:imaginary.hs/")
662 .expect("Failed to create MatrixId."),
663 MatrixId::RoomAlias(room_alias_id!("#roomalias:imaginary.hs").into())
664 );
665 }
666
667 #[test]
668 fn parse_matrixid_no_identifier() {
669 assert_eq!(MatrixId::parse_with_sigil("").unwrap_err(), MatrixIdError::NoIdentifier.into());
670 assert_eq!(
671 MatrixId::parse_with_sigil("/").unwrap_err(),
672 MatrixIdError::NoIdentifier.into()
673 );
674 }
675
676 #[test]
677 fn parse_matrixid_too_many_identifiers() {
678 assert_eq!(
679 MatrixId::parse_with_sigil(
680 "@user:imaginary.hs/#room:imaginary.hs/$event1:imaginary.hs"
681 )
682 .unwrap_err(),
683 MatrixIdError::TooManyIdentifiers.into()
684 );
685 }
686
687 #[test]
688 fn parse_matrixid_unknown_identifier_pair() {
689 assert_eq!(
690 MatrixId::parse_with_sigil("!roomid:imaginary.hs/@user:imaginary.hs").unwrap_err(),
691 MatrixIdError::UnknownIdentifierPair.into()
692 );
693 assert_eq!(
694 MatrixId::parse_with_sigil("#roomalias:imaginary.hs/notanidentifier").unwrap_err(),
695 MatrixIdError::UnknownIdentifierPair.into()
696 );
697 assert_eq!(
698 MatrixId::parse_with_sigil("$event:imaginary.hs/$otherevent:imaginary.hs").unwrap_err(),
699 MatrixIdError::UnknownIdentifierPair.into()
700 );
701 assert_eq!(
702 MatrixId::parse_with_sigil("notanidentifier/neitheristhis").unwrap_err(),
703 MatrixIdError::UnknownIdentifierPair.into()
704 );
705 }
706
707 #[test]
708 fn parse_matrixid_missing_room() {
709 assert_eq!(
710 MatrixId::parse_with_sigil("$event:imaginary.hs").unwrap_err(),
711 MatrixIdError::MissingRoom.into()
712 );
713 }
714
715 #[test]
716 fn parse_matrixid_unknown_identifier() {
717 assert_eq!(
718 MatrixId::parse_with_sigil("event:imaginary.hs").unwrap_err(),
719 MatrixIdError::UnknownIdentifier.into()
720 );
721 assert_eq!(
722 MatrixId::parse_with_sigil("notanidentifier").unwrap_err(),
723 MatrixIdError::UnknownIdentifier.into()
724 );
725 }
726
727 #[test]
728 fn parse_matrixtouri_valid_uris() {
729 let matrix_to = MatrixToUri::parse("https://matrix.to/#/%40jplatte%3Anotareal.hs")
730 .expect("Failed to create MatrixToUri.");
731 assert_eq!(matrix_to.id(), &user_id!("@jplatte:notareal.hs").into());
732
733 let matrix_to = MatrixToUri::parse("https://matrix.to/#/%23ruma%3Anotareal.hs")
734 .expect("Failed to create MatrixToUri.");
735 assert_eq!(matrix_to.id(), &room_alias_id!("#ruma:notareal.hs").into());
736
737 let matrix_to = MatrixToUri::parse(
738 "https://matrix.to/#/%21ruma%3Anotareal.hs?via=notareal.hs&via=anotherunreal.hs",
739 )
740 .expect("Failed to create MatrixToUri.");
741 assert_eq!(matrix_to.id(), &room_id!("!ruma:notareal.hs").into());
742 assert_eq!(
743 matrix_to.via(),
744 &[server_name!("notareal.hs").to_owned(), server_name!("anotherunreal.hs").to_owned(),]
745 );
746
747 let matrix_to =
748 MatrixToUri::parse("https://matrix.to/#/%23ruma%3Anotareal.hs/%24event%3Anotareal.hs")
749 .expect("Failed to create MatrixToUri.");
750 assert_eq!(
751 matrix_to.id(),
752 &(room_alias_id!("#ruma:notareal.hs"), event_id!("$event:notareal.hs")).into()
753 );
754
755 let matrix_to =
756 MatrixToUri::parse("https://matrix.to/#/%21ruma%3Anotareal.hs/%24event%3Anotareal.hs")
757 .expect("Failed to create MatrixToUri.");
758 assert_eq!(
759 matrix_to.id(),
760 &(room_id!("!ruma:notareal.hs"), event_id!("$event:notareal.hs")).into()
761 );
762 assert_eq!(matrix_to.via().len(), 0);
763 }
764
765 #[test]
766 fn parse_matrixtouri_valid_uris_not_urlencoded() {
767 let matrix_to = MatrixToUri::parse("https://matrix.to/#/@jplatte:notareal.hs")
768 .expect("Failed to create MatrixToUri.");
769 assert_eq!(matrix_to.id(), &user_id!("@jplatte:notareal.hs").into());
770
771 let matrix_to = MatrixToUri::parse("https://matrix.to/#/#ruma:notareal.hs")
772 .expect("Failed to create MatrixToUri.");
773 assert_eq!(matrix_to.id(), &room_alias_id!("#ruma:notareal.hs").into());
774
775 let matrix_to = MatrixToUri::parse("https://matrix.to/#/!ruma:notareal.hs?via=notareal.hs")
776 .expect("Failed to create MatrixToUri.");
777 assert_eq!(matrix_to.id(), &room_id!("!ruma:notareal.hs").into());
778 assert_eq!(matrix_to.via(), &[server_name!("notareal.hs").to_owned()]);
779
780 let matrix_to =
781 MatrixToUri::parse("https://matrix.to/#/#ruma:notareal.hs/$event:notareal.hs")
782 .expect("Failed to create MatrixToUri.");
783 assert_eq!(
784 matrix_to.id(),
785 &(room_alias_id!("#ruma:notareal.hs"), event_id!("$event:notareal.hs")).into()
786 );
787
788 let matrix_to =
789 MatrixToUri::parse("https://matrix.to/#/!ruma:notareal.hs/$event:notareal.hs")
790 .expect("Failed to create MatrixToUri.");
791 assert_eq!(
792 matrix_to.id(),
793 &(room_id!("!ruma:notareal.hs"), event_id!("$event:notareal.hs")).into()
794 );
795 assert_eq!(matrix_to.via().len(), 0);
796 }
797
798 #[test]
799 fn parse_matrixtouri_wrong_base_url() {
800 assert_eq!(MatrixToUri::parse("").unwrap_err(), MatrixToError::WrongBaseUrl.into());
801 assert_eq!(
802 MatrixToUri::parse("https://notreal.to/#/").unwrap_err(),
803 MatrixToError::WrongBaseUrl.into()
804 );
805 }
806
807 #[test]
808 fn parse_matrixtouri_wrong_identifier() {
809 assert_matches!(
810 MatrixToUri::parse("https://matrix.to/#/notanidentifier").unwrap_err(),
811 Error::InvalidMatrixId(_)
812 );
813 assert_matches!(
814 MatrixToUri::parse("https://matrix.to/#/").unwrap_err(),
815 Error::InvalidMatrixId(_)
816 );
817 assert_matches!(
818 MatrixToUri::parse(
819 "https://matrix.to/#/%40jplatte%3Anotareal.hs/%24event%3Anotareal.hs"
820 )
821 .unwrap_err(),
822 Error::InvalidMatrixId(_)
823 );
824 }
825
826 #[test]
827 fn parse_matrixtouri_unknown_arguments() {
828 assert_eq!(
829 MatrixToUri::parse(
830 "https://matrix.to/#/%21ruma%3Anotareal.hs?via=notareal.hs&custom=data"
831 )
832 .unwrap_err(),
833 MatrixToError::UnknownArgument.into()
834 );
835 }
836
837 #[test]
838 fn display_matrixuri() {
839 assert_eq!(
840 user_id!("@jplatte:notareal.hs").matrix_uri(false).to_string(),
841 "matrix:u/jplatte:notareal.hs"
842 );
843 assert_eq!(
844 user_id!("@jplatte:notareal.hs").matrix_uri(true).to_string(),
845 "matrix:u/jplatte:notareal.hs?action=chat"
846 );
847 assert_eq!(
848 room_alias_id!("#ruma:notareal.hs").matrix_uri(false).to_string(),
849 "matrix:r/ruma:notareal.hs"
850 );
851 assert_eq!(
852 room_alias_id!("#ruma:notareal.hs").matrix_uri(true).to_string(),
853 "matrix:r/ruma:notareal.hs?action=join"
854 );
855 assert_eq!(
856 room_id!("!ruma:notareal.hs").matrix_uri(false).to_string(),
857 "matrix:roomid/ruma:notareal.hs"
858 );
859 assert_eq!(
860 room_id!("!ruma:notareal.hs")
861 .matrix_uri_via(vec![server_name!("notareal.hs")], false)
862 .to_string(),
863 "matrix:roomid/ruma:notareal.hs?via=notareal.hs"
864 );
865 assert_eq!(
866 room_id!("!ruma:notareal.hs")
867 .matrix_uri_via(
868 vec![server_name!("notareal.hs"), server_name!("anotherunreal.hs")],
869 true
870 )
871 .to_string(),
872 "matrix:roomid/ruma:notareal.hs?via=notareal.hs&via=anotherunreal.hs&action=join"
873 );
874 #[allow(deprecated)]
875 let uri = room_alias_id!("#ruma:notareal.hs")
876 .matrix_event_uri(event_id!("$event:notareal.hs"))
877 .to_string();
878 assert_eq!(uri, "matrix:r/ruma:notareal.hs/e/event:notareal.hs");
879 assert_eq!(
880 room_id!("!ruma:notareal.hs")
881 .matrix_event_uri(event_id!("$event:notareal.hs"))
882 .to_string(),
883 "matrix:roomid/ruma:notareal.hs/e/event:notareal.hs"
884 );
885 assert_eq!(
886 room_id!("!ruma:notareal.hs")
887 .matrix_event_uri_via(
888 event_id!("$event:notareal.hs"),
889 vec![server_name!("notareal.hs")]
890 )
891 .to_string(),
892 "matrix:roomid/ruma:notareal.hs/e/event:notareal.hs?via=notareal.hs"
893 );
894 }
895
896 #[test]
897 fn parse_valid_matrixid_with_type() {
898 assert_eq!(
899 MatrixId::parse_with_type("u/user:imaginary.hs").expect("Failed to create MatrixId."),
900 MatrixId::User(user_id!("@user:imaginary.hs").into())
901 );
902 assert_eq!(
903 MatrixId::parse_with_type("user/user:imaginary.hs")
904 .expect("Failed to create MatrixId."),
905 MatrixId::User(user_id!("@user:imaginary.hs").into())
906 );
907 assert_eq!(
908 MatrixId::parse_with_type("roomid/roomid:imaginary.hs")
909 .expect("Failed to create MatrixId."),
910 MatrixId::Room(room_id!("!roomid:imaginary.hs").into())
911 );
912 assert_eq!(
913 MatrixId::parse_with_type("r/roomalias:imaginary.hs")
914 .expect("Failed to create MatrixId."),
915 MatrixId::RoomAlias(room_alias_id!("#roomalias:imaginary.hs").into())
916 );
917 assert_eq!(
918 MatrixId::parse_with_type("room/roomalias:imaginary.hs")
919 .expect("Failed to create MatrixId."),
920 MatrixId::RoomAlias(room_alias_id!("#roomalias:imaginary.hs").into())
921 );
922 assert_eq!(
923 MatrixId::parse_with_type("roomid/roomid:imaginary.hs/e/event:imaginary.hs")
924 .expect("Failed to create MatrixId."),
925 MatrixId::Event(
926 <&RoomOrAliasId>::from(room_id!("!roomid:imaginary.hs")).into(),
927 event_id!("$event:imaginary.hs").into()
928 )
929 );
930 assert_eq!(
931 MatrixId::parse_with_type("r/roomalias:imaginary.hs/e/event:imaginary.hs")
932 .expect("Failed to create MatrixId."),
933 MatrixId::Event(
934 <&RoomOrAliasId>::from(room_alias_id!("#roomalias:imaginary.hs")).into(),
935 event_id!("$event:imaginary.hs").into()
936 )
937 );
938 assert_eq!(
939 MatrixId::parse_with_type("room/roomalias:imaginary.hs/event/event:imaginary.hs")
940 .expect("Failed to create MatrixId."),
941 MatrixId::Event(
942 <&RoomOrAliasId>::from(room_alias_id!("#roomalias:imaginary.hs")).into(),
943 event_id!("$event:imaginary.hs").into()
944 )
945 );
946 assert_eq!(
948 MatrixId::parse_with_type("e/event:imaginary.hs/roomid/roomid:imaginary.hs")
949 .expect("Failed to create MatrixId."),
950 MatrixId::Event(
951 <&RoomOrAliasId>::from(room_id!("!roomid:imaginary.hs")).into(),
952 event_id!("$event:imaginary.hs").into()
953 )
954 );
955 assert_eq!(
956 MatrixId::parse_with_type("e/event:imaginary.hs/r/roomalias:imaginary.hs")
957 .expect("Failed to create MatrixId."),
958 MatrixId::Event(
959 <&RoomOrAliasId>::from(room_alias_id!("#roomalias:imaginary.hs")).into(),
960 event_id!("$event:imaginary.hs").into()
961 )
962 );
963 assert_eq!(
965 MatrixId::parse_with_type("/u/user:imaginary.hs").expect("Failed to create MatrixId."),
966 MatrixId::User(user_id!("@user:imaginary.hs").into())
967 );
968 assert_eq!(
970 MatrixId::parse_with_type("roomid/roomid:imaginary.hs/")
971 .expect("Failed to create MatrixId."),
972 MatrixId::Room(room_id!("!roomid:imaginary.hs").into())
973 );
974 assert_eq!(
976 MatrixId::parse_with_type("/r/roomalias:imaginary.hs/")
977 .expect("Failed to create MatrixId."),
978 MatrixId::RoomAlias(room_alias_id!("#roomalias:imaginary.hs").into())
979 );
980 }
981
982 #[test]
983 fn parse_matrixid_type_no_identifier() {
984 assert_eq!(MatrixId::parse_with_type("").unwrap_err(), MatrixIdError::NoIdentifier.into());
985 assert_eq!(MatrixId::parse_with_type("/").unwrap_err(), MatrixIdError::NoIdentifier.into());
986 }
987
988 #[test]
989 fn parse_matrixid_invalid_parts_number() {
990 assert_eq!(
991 MatrixId::parse_with_type("u/user:imaginary.hs/r/room:imaginary.hs/e").unwrap_err(),
992 MatrixIdError::InvalidPartsNumber.into()
993 );
994 }
995
996 #[test]
997 fn parse_matrixid_unknown_type() {
998 assert_eq!(
999 MatrixId::parse_with_type("notatype/fake:notareal.hs").unwrap_err(),
1000 MatrixIdError::UnknownType.into()
1001 );
1002 }
1003
1004 #[test]
1005 fn parse_matrixuri_valid_uris() {
1006 let matrix_uri =
1007 MatrixUri::parse("matrix:u/jplatte:notareal.hs").expect("Failed to create MatrixUri.");
1008 assert_eq!(matrix_uri.id(), &user_id!("@jplatte:notareal.hs").into());
1009 assert_eq!(matrix_uri.action(), None);
1010
1011 let matrix_uri = MatrixUri::parse("matrix:u/jplatte:notareal.hs?action=chat")
1012 .expect("Failed to create MatrixUri.");
1013 assert_eq!(matrix_uri.id(), &user_id!("@jplatte:notareal.hs").into());
1014 assert_eq!(matrix_uri.action(), Some(&UriAction::Chat));
1015
1016 let matrix_uri =
1017 MatrixUri::parse("matrix:r/ruma:notareal.hs").expect("Failed to create MatrixToUri.");
1018 assert_eq!(matrix_uri.id(), &room_alias_id!("#ruma:notareal.hs").into());
1019
1020 let matrix_uri = MatrixUri::parse("matrix:roomid/ruma:notareal.hs?via=notareal.hs")
1021 .expect("Failed to create MatrixToUri.");
1022 assert_eq!(matrix_uri.id(), &room_id!("!ruma:notareal.hs").into());
1023 assert_eq!(matrix_uri.via(), &[server_name!("notareal.hs").to_owned()]);
1024 assert_eq!(matrix_uri.action(), None);
1025
1026 let matrix_uri = MatrixUri::parse("matrix:r/ruma:notareal.hs/e/event:notareal.hs")
1027 .expect("Failed to create MatrixToUri.");
1028 assert_eq!(
1029 matrix_uri.id(),
1030 &(room_alias_id!("#ruma:notareal.hs"), event_id!("$event:notareal.hs")).into()
1031 );
1032
1033 let matrix_uri = MatrixUri::parse("matrix:roomid/ruma:notareal.hs/e/event:notareal.hs")
1034 .expect("Failed to create MatrixToUri.");
1035 assert_eq!(
1036 matrix_uri.id(),
1037 &(room_id!("!ruma:notareal.hs"), event_id!("$event:notareal.hs")).into()
1038 );
1039 assert_eq!(matrix_uri.via().len(), 0);
1040 assert_eq!(matrix_uri.action(), None);
1041
1042 let matrix_uri =
1043 MatrixUri::parse("matrix:roomid/ruma:notareal.hs/e/event:notareal.hs?via=notareal.hs&action=join&via=anotherinexistant.hs")
1044 .expect("Failed to create MatrixToUri.");
1045 assert_eq!(
1046 matrix_uri.id(),
1047 &(room_id!("!ruma:notareal.hs"), event_id!("$event:notareal.hs")).into()
1048 );
1049 assert_eq!(
1050 matrix_uri.via(),
1051 &vec![
1052 server_name!("notareal.hs").to_owned(),
1053 server_name!("anotherinexistant.hs").to_owned()
1054 ]
1055 );
1056 assert_eq!(matrix_uri.action(), Some(&UriAction::Join));
1057 }
1058
1059 #[test]
1060 fn parse_matrixuri_invalid_uri() {
1061 assert_eq!(
1062 MatrixUri::parse("").unwrap_err(),
1063 Error::InvalidMatrixToUri(MatrixToError::InvalidUrl)
1064 );
1065 }
1066
1067 #[test]
1068 fn parse_matrixuri_wrong_scheme() {
1069 assert_eq!(
1070 MatrixUri::parse("unknown:u/user:notareal.hs").unwrap_err(),
1071 MatrixUriError::WrongScheme.into()
1072 );
1073 }
1074
1075 #[test]
1076 fn parse_matrixuri_too_many_actions() {
1077 assert_eq!(
1078 MatrixUri::parse("matrix:u/user:notareal.hs?action=chat&action=join").unwrap_err(),
1079 MatrixUriError::TooManyActions.into()
1080 );
1081 }
1082
1083 #[test]
1084 fn parse_matrixuri_unknown_query_item() {
1085 assert_eq!(
1086 MatrixUri::parse("matrix:roomid/roomid:notareal.hs?via=notareal.hs&fake=data")
1087 .unwrap_err(),
1088 MatrixUriError::UnknownQueryItem.into()
1089 );
1090 }
1091
1092 #[test]
1093 fn parse_matrixuri_wrong_identifier() {
1094 assert_matches!(
1095 MatrixUri::parse("matrix:notanidentifier").unwrap_err(),
1096 Error::InvalidMatrixId(_)
1097 );
1098 assert_matches!(MatrixUri::parse("matrix:").unwrap_err(), Error::InvalidMatrixId(_));
1099 assert_matches!(
1100 MatrixUri::parse("matrix:u/jplatte:notareal.hs/e/event:notareal.hs").unwrap_err(),
1101 Error::InvalidMatrixId(_)
1102 );
1103 }
1104}