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