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
541#[cfg(test)]
542mod tests {
543 use assert_matches2::assert_matches;
544 use ruma_identifiers_validation::{
545 Error,
546 error::{MatrixIdError, MatrixToError, MatrixUriError},
547 };
548
549 use super::{MatrixId, MatrixToUri, MatrixUri};
550 use crate::{
551 matrix_uri::UriAction, owned_event_id, owned_room_alias_id, owned_room_id,
552 owned_server_name, owned_user_id, room_alias_id, room_id, user_id,
553 };
554
555 #[test]
556 fn display_matrixtouri() {
557 assert_eq!(
558 user_id!("@jplatte:notareal.hs").matrix_to_uri().to_string(),
559 "https://matrix.to/#/@jplatte:notareal.hs"
560 );
561 assert_eq!(
562 room_alias_id!("#ruma:notareal.hs").matrix_to_uri().to_string(),
563 "https://matrix.to/#/%23ruma:notareal.hs"
564 );
565 assert_eq!(
566 room_id!("!ruma:notareal.hs").matrix_to_uri().to_string(),
567 "https://matrix.to/#/!ruma:notareal.hs"
568 );
569 assert_eq!(
570 room_id!("!ruma:notareal.hs")
571 .matrix_to_uri_via(vec![owned_server_name!("notareal.hs")])
572 .to_string(),
573 "https://matrix.to/#/!ruma:notareal.hs?via=notareal.hs"
574 );
575 #[allow(deprecated)]
576 let uri = room_alias_id!("#ruma:notareal.hs")
577 .matrix_to_event_uri(owned_event_id!("$event:notareal.hs"))
578 .to_string();
579 assert_eq!(uri, "https://matrix.to/#/%23ruma:notareal.hs/$event:notareal.hs");
580 assert_eq!(
581 room_id!("!ruma:notareal.hs")
582 .matrix_to_event_uri(owned_event_id!("$event:notareal.hs"))
583 .to_string(),
584 "https://matrix.to/#/!ruma:notareal.hs/$event:notareal.hs"
585 );
586 assert_eq!(
587 room_id!("!ruma:notareal.hs")
588 .matrix_to_event_uri_via(
589 owned_event_id!("$event:notareal.hs"),
590 vec![owned_server_name!("notareal.hs")]
591 )
592 .to_string(),
593 "https://matrix.to/#/!ruma:notareal.hs/$event:notareal.hs?via=notareal.hs"
594 );
595 }
596
597 #[test]
598 fn parse_valid_matrixid_with_sigil() {
599 assert_eq!(
600 MatrixId::parse_with_sigil("@user:imaginary.hs").expect("Failed to create MatrixId."),
601 MatrixId::User(owned_user_id!("@user:imaginary.hs"))
602 );
603 assert_eq!(
604 MatrixId::parse_with_sigil("!roomid:imaginary.hs").expect("Failed to create MatrixId."),
605 MatrixId::Room(owned_room_id!("!roomid:imaginary.hs"))
606 );
607 assert_eq!(
608 MatrixId::parse_with_sigil("#roomalias:imaginary.hs")
609 .expect("Failed to create MatrixId."),
610 MatrixId::RoomAlias(owned_room_alias_id!("#roomalias:imaginary.hs"))
611 );
612 assert_eq!(
613 MatrixId::parse_with_sigil("!roomid:imaginary.hs/$event:imaginary.hs")
614 .expect("Failed to create MatrixId."),
615 MatrixId::Event(
616 owned_room_id!("!roomid:imaginary.hs").into(),
617 owned_event_id!("$event:imaginary.hs")
618 )
619 );
620 assert_eq!(
621 MatrixId::parse_with_sigil("#roomalias:imaginary.hs/$event:imaginary.hs")
622 .expect("Failed to create MatrixId."),
623 MatrixId::Event(
624 owned_room_alias_id!("#roomalias:imaginary.hs").into(),
625 owned_event_id!("$event:imaginary.hs")
626 )
627 );
628 assert_eq!(
630 MatrixId::parse_with_sigil("$event:imaginary.hs/!roomid:imaginary.hs")
631 .expect("Failed to create MatrixId."),
632 MatrixId::Event(
633 owned_room_id!("!roomid:imaginary.hs").into(),
634 owned_event_id!("$event:imaginary.hs")
635 )
636 );
637 assert_eq!(
638 MatrixId::parse_with_sigil("$event:imaginary.hs/#roomalias:imaginary.hs")
639 .expect("Failed to create MatrixId."),
640 MatrixId::Event(
641 owned_room_alias_id!("#roomalias:imaginary.hs").into(),
642 owned_event_id!("$event:imaginary.hs")
643 )
644 );
645 assert_eq!(
647 MatrixId::parse_with_sigil("/@user:imaginary.hs").expect("Failed to create MatrixId."),
648 MatrixId::User(owned_user_id!("@user:imaginary.hs"))
649 );
650 assert_eq!(
652 MatrixId::parse_with_sigil("!roomid:imaginary.hs/")
653 .expect("Failed to create MatrixId."),
654 MatrixId::Room(owned_room_id!("!roomid:imaginary.hs"))
655 );
656 assert_eq!(
658 MatrixId::parse_with_sigil("/#roomalias:imaginary.hs/")
659 .expect("Failed to create MatrixId."),
660 MatrixId::RoomAlias(owned_room_alias_id!("#roomalias:imaginary.hs"))
661 );
662 }
663
664 #[test]
665 fn parse_matrixid_no_identifier() {
666 assert_eq!(MatrixId::parse_with_sigil("").unwrap_err(), MatrixIdError::NoIdentifier.into());
667 assert_eq!(
668 MatrixId::parse_with_sigil("/").unwrap_err(),
669 MatrixIdError::NoIdentifier.into()
670 );
671 }
672
673 #[test]
674 fn parse_matrixid_too_many_identifiers() {
675 assert_eq!(
676 MatrixId::parse_with_sigil(
677 "@user:imaginary.hs/#room:imaginary.hs/$event1:imaginary.hs"
678 )
679 .unwrap_err(),
680 MatrixIdError::TooManyIdentifiers.into()
681 );
682 }
683
684 #[test]
685 fn parse_matrixid_unknown_identifier_pair() {
686 assert_eq!(
687 MatrixId::parse_with_sigil("!roomid:imaginary.hs/@user:imaginary.hs").unwrap_err(),
688 MatrixIdError::UnknownIdentifierPair.into()
689 );
690 assert_eq!(
691 MatrixId::parse_with_sigil("#roomalias:imaginary.hs/notanidentifier").unwrap_err(),
692 MatrixIdError::UnknownIdentifierPair.into()
693 );
694 assert_eq!(
695 MatrixId::parse_with_sigil("$event:imaginary.hs/$otherevent:imaginary.hs").unwrap_err(),
696 MatrixIdError::UnknownIdentifierPair.into()
697 );
698 assert_eq!(
699 MatrixId::parse_with_sigil("notanidentifier/neitheristhis").unwrap_err(),
700 MatrixIdError::UnknownIdentifierPair.into()
701 );
702 }
703
704 #[test]
705 fn parse_matrixid_missing_room() {
706 assert_eq!(
707 MatrixId::parse_with_sigil("$event:imaginary.hs").unwrap_err(),
708 MatrixIdError::MissingRoom.into()
709 );
710 }
711
712 #[test]
713 fn parse_matrixid_unknown_identifier() {
714 assert_eq!(
715 MatrixId::parse_with_sigil("event:imaginary.hs").unwrap_err(),
716 MatrixIdError::UnknownIdentifier.into()
717 );
718 assert_eq!(
719 MatrixId::parse_with_sigil("notanidentifier").unwrap_err(),
720 MatrixIdError::UnknownIdentifier.into()
721 );
722 }
723
724 #[test]
725 fn parse_matrixtouri_valid_uris() {
726 let matrix_to = MatrixToUri::parse("https://matrix.to/#/%40jplatte%3Anotareal.hs")
727 .expect("Failed to create MatrixToUri.");
728 assert_eq!(*matrix_to.id(), owned_user_id!("@jplatte:notareal.hs").into());
729
730 let matrix_to = MatrixToUri::parse("https://matrix.to/#/%23ruma%3Anotareal.hs")
731 .expect("Failed to create MatrixToUri.");
732 assert_eq!(*matrix_to.id(), owned_room_alias_id!("#ruma:notareal.hs").into());
733
734 let matrix_to = MatrixToUri::parse(
735 "https://matrix.to/#/%21ruma%3Anotareal.hs?via=notareal.hs&via=anotherunreal.hs",
736 )
737 .expect("Failed to create MatrixToUri.");
738 assert_eq!(*matrix_to.id(), owned_room_id!("!ruma:notareal.hs").into());
739 assert_eq!(
740 matrix_to.via(),
741 &[owned_server_name!("notareal.hs"), owned_server_name!("anotherunreal.hs"),]
742 );
743
744 let matrix_to =
745 MatrixToUri::parse("https://matrix.to/#/%23ruma%3Anotareal.hs/%24event%3Anotareal.hs")
746 .expect("Failed to create MatrixToUri.");
747 assert_eq!(
748 *matrix_to.id(),
749 (owned_room_alias_id!("#ruma:notareal.hs"), owned_event_id!("$event:notareal.hs"))
750 .into()
751 );
752
753 let matrix_to =
754 MatrixToUri::parse("https://matrix.to/#/%21ruma%3Anotareal.hs/%24event%3Anotareal.hs")
755 .expect("Failed to create MatrixToUri.");
756 assert_eq!(
757 *matrix_to.id(),
758 (owned_room_id!("!ruma:notareal.hs"), owned_event_id!("$event:notareal.hs")).into()
759 );
760 assert_eq!(matrix_to.via().len(), 0);
761 }
762
763 #[test]
764 fn parse_matrixtouri_valid_uris_not_urlencoded() {
765 let matrix_to = MatrixToUri::parse("https://matrix.to/#/@jplatte:notareal.hs")
766 .expect("Failed to create MatrixToUri.");
767 assert_eq!(*matrix_to.id(), owned_user_id!("@jplatte:notareal.hs").into());
768
769 let matrix_to = MatrixToUri::parse("https://matrix.to/#/#ruma:notareal.hs")
770 .expect("Failed to create MatrixToUri.");
771 assert_eq!(*matrix_to.id(), owned_room_alias_id!("#ruma:notareal.hs").into());
772
773 let matrix_to = MatrixToUri::parse("https://matrix.to/#/!ruma:notareal.hs?via=notareal.hs")
774 .expect("Failed to create MatrixToUri.");
775 assert_eq!(*matrix_to.id(), owned_room_id!("!ruma:notareal.hs").into());
776 assert_eq!(matrix_to.via(), &[owned_server_name!("notareal.hs")]);
777
778 let matrix_to =
779 MatrixToUri::parse("https://matrix.to/#/#ruma:notareal.hs/$event:notareal.hs")
780 .expect("Failed to create MatrixToUri.");
781 assert_eq!(
782 *matrix_to.id(),
783 (owned_room_alias_id!("#ruma:notareal.hs"), owned_event_id!("$event:notareal.hs"))
784 .into()
785 );
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_id!("!ruma:notareal.hs"), owned_event_id!("$event:notareal.hs")).into()
793 );
794 assert_eq!(matrix_to.via().len(), 0);
795 }
796
797 #[test]
798 fn parse_matrixtouri_wrong_base_url() {
799 assert_eq!(MatrixToUri::parse("").unwrap_err(), MatrixToError::WrongBaseUrl.into());
800 assert_eq!(
801 MatrixToUri::parse("https://notreal.to/#/").unwrap_err(),
802 MatrixToError::WrongBaseUrl.into()
803 );
804 }
805
806 #[test]
807 fn parse_matrixtouri_wrong_identifier() {
808 assert_matches!(
809 MatrixToUri::parse("https://matrix.to/#/notanidentifier").unwrap_err(),
810 Error::InvalidMatrixId(_)
811 );
812 assert_matches!(
813 MatrixToUri::parse("https://matrix.to/#/").unwrap_err(),
814 Error::InvalidMatrixId(_)
815 );
816 assert_matches!(
817 MatrixToUri::parse(
818 "https://matrix.to/#/%40jplatte%3Anotareal.hs/%24event%3Anotareal.hs"
819 )
820 .unwrap_err(),
821 Error::InvalidMatrixId(_)
822 );
823 }
824
825 #[test]
826 fn parse_matrixtouri_unknown_arguments() {
827 assert_eq!(
828 MatrixToUri::parse(
829 "https://matrix.to/#/%21ruma%3Anotareal.hs?via=notareal.hs&custom=data"
830 )
831 .unwrap_err(),
832 MatrixToError::UnknownArgument.into()
833 );
834 }
835
836 #[test]
837 fn display_matrixuri() {
838 assert_eq!(
839 user_id!("@jplatte:notareal.hs").matrix_uri(false).to_string(),
840 "matrix:u/jplatte:notareal.hs"
841 );
842 assert_eq!(
843 user_id!("@jplatte:notareal.hs").matrix_uri(true).to_string(),
844 "matrix:u/jplatte:notareal.hs?action=chat"
845 );
846 assert_eq!(
847 room_alias_id!("#ruma:notareal.hs").matrix_uri(false).to_string(),
848 "matrix:r/ruma:notareal.hs"
849 );
850 assert_eq!(
851 room_alias_id!("#ruma:notareal.hs").matrix_uri(true).to_string(),
852 "matrix:r/ruma:notareal.hs?action=join"
853 );
854 assert_eq!(
855 room_id!("!ruma:notareal.hs").matrix_uri(false).to_string(),
856 "matrix:roomid/ruma:notareal.hs"
857 );
858 assert_eq!(
859 room_id!("!ruma:notareal.hs")
860 .matrix_uri_via(vec![owned_server_name!("notareal.hs")], false)
861 .to_string(),
862 "matrix:roomid/ruma:notareal.hs?via=notareal.hs"
863 );
864 assert_eq!(
865 room_id!("!ruma:notareal.hs")
866 .matrix_uri_via(
867 vec![owned_server_name!("notareal.hs"), owned_server_name!("anotherunreal.hs")],
868 true
869 )
870 .to_string(),
871 "matrix:roomid/ruma:notareal.hs?via=notareal.hs&via=anotherunreal.hs&action=join"
872 );
873 #[allow(deprecated)]
874 let uri = room_alias_id!("#ruma:notareal.hs")
875 .matrix_event_uri(owned_event_id!("$event:notareal.hs"))
876 .to_string();
877 assert_eq!(uri, "matrix:r/ruma:notareal.hs/e/event:notareal.hs");
878 assert_eq!(
879 room_id!("!ruma:notareal.hs")
880 .matrix_event_uri(owned_event_id!("$event:notareal.hs"))
881 .to_string(),
882 "matrix:roomid/ruma:notareal.hs/e/event:notareal.hs"
883 );
884 assert_eq!(
885 room_id!("!ruma:notareal.hs")
886 .matrix_event_uri_via(
887 owned_event_id!("$event:notareal.hs"),
888 vec![owned_server_name!("notareal.hs")]
889 )
890 .to_string(),
891 "matrix:roomid/ruma:notareal.hs/e/event:notareal.hs?via=notareal.hs"
892 );
893 }
894
895 #[test]
896 fn parse_valid_matrixid_with_type() {
897 assert_eq!(
898 MatrixId::parse_with_type("u/user:imaginary.hs").expect("Failed to create MatrixId."),
899 MatrixId::User(owned_user_id!("@user:imaginary.hs"))
900 );
901 assert_eq!(
902 MatrixId::parse_with_type("user/user:imaginary.hs")
903 .expect("Failed to create MatrixId."),
904 MatrixId::User(owned_user_id!("@user:imaginary.hs"))
905 );
906 assert_eq!(
907 MatrixId::parse_with_type("roomid/roomid:imaginary.hs")
908 .expect("Failed to create MatrixId."),
909 MatrixId::Room(owned_room_id!("!roomid:imaginary.hs"))
910 );
911 assert_eq!(
912 MatrixId::parse_with_type("r/roomalias:imaginary.hs")
913 .expect("Failed to create MatrixId."),
914 MatrixId::RoomAlias(owned_room_alias_id!("#roomalias:imaginary.hs"))
915 );
916 assert_eq!(
917 MatrixId::parse_with_type("room/roomalias:imaginary.hs")
918 .expect("Failed to create MatrixId."),
919 MatrixId::RoomAlias(owned_room_alias_id!("#roomalias:imaginary.hs"))
920 );
921 assert_eq!(
922 MatrixId::parse_with_type("roomid/roomid:imaginary.hs/e/event:imaginary.hs")
923 .expect("Failed to create MatrixId."),
924 MatrixId::Event(
925 owned_room_id!("!roomid:imaginary.hs").into(),
926 owned_event_id!("$event:imaginary.hs")
927 )
928 );
929 assert_eq!(
930 MatrixId::parse_with_type("r/roomalias:imaginary.hs/e/event:imaginary.hs")
931 .expect("Failed to create MatrixId."),
932 MatrixId::Event(
933 owned_room_alias_id!("#roomalias:imaginary.hs").into(),
934 owned_event_id!("$event:imaginary.hs")
935 )
936 );
937 assert_eq!(
938 MatrixId::parse_with_type("room/roomalias:imaginary.hs/event/event:imaginary.hs")
939 .expect("Failed to create MatrixId."),
940 MatrixId::Event(
941 owned_room_alias_id!("#roomalias:imaginary.hs").into(),
942 owned_event_id!("$event:imaginary.hs")
943 )
944 );
945 assert_eq!(
947 MatrixId::parse_with_type("e/event:imaginary.hs/roomid/roomid:imaginary.hs")
948 .expect("Failed to create MatrixId."),
949 MatrixId::Event(
950 owned_room_id!("!roomid:imaginary.hs").into(),
951 owned_event_id!("$event:imaginary.hs")
952 )
953 );
954 assert_eq!(
955 MatrixId::parse_with_type("e/event:imaginary.hs/r/roomalias:imaginary.hs")
956 .expect("Failed to create MatrixId."),
957 MatrixId::Event(
958 owned_room_alias_id!("#roomalias:imaginary.hs").into(),
959 owned_event_id!("$event:imaginary.hs")
960 )
961 );
962 assert_eq!(
964 MatrixId::parse_with_type("/u/user:imaginary.hs").expect("Failed to create MatrixId."),
965 MatrixId::User(owned_user_id!("@user:imaginary.hs"))
966 );
967 assert_eq!(
969 MatrixId::parse_with_type("roomid/roomid:imaginary.hs/")
970 .expect("Failed to create MatrixId."),
971 MatrixId::Room(owned_room_id!("!roomid:imaginary.hs"))
972 );
973 assert_eq!(
975 MatrixId::parse_with_type("/r/roomalias:imaginary.hs/")
976 .expect("Failed to create MatrixId."),
977 MatrixId::RoomAlias(owned_room_alias_id!("#roomalias:imaginary.hs"))
978 );
979 }
980
981 #[test]
982 fn parse_matrixid_type_no_identifier() {
983 assert_eq!(MatrixId::parse_with_type("").unwrap_err(), MatrixIdError::NoIdentifier.into());
984 assert_eq!(MatrixId::parse_with_type("/").unwrap_err(), MatrixIdError::NoIdentifier.into());
985 }
986
987 #[test]
988 fn parse_matrixid_invalid_parts_number() {
989 assert_eq!(
990 MatrixId::parse_with_type("u/user:imaginary.hs/r/room:imaginary.hs/e").unwrap_err(),
991 MatrixIdError::InvalidPartsNumber.into()
992 );
993 }
994
995 #[test]
996 fn parse_matrixid_unknown_type() {
997 assert_eq!(
998 MatrixId::parse_with_type("notatype/fake:notareal.hs").unwrap_err(),
999 MatrixIdError::UnknownType.into()
1000 );
1001 }
1002
1003 #[test]
1004 fn parse_matrixuri_valid_uris() {
1005 let matrix_uri =
1006 MatrixUri::parse("matrix:u/jplatte:notareal.hs").expect("Failed to create MatrixUri.");
1007 assert_eq!(*matrix_uri.id(), owned_user_id!("@jplatte:notareal.hs").into());
1008 assert_eq!(matrix_uri.action(), None);
1009
1010 let matrix_uri = MatrixUri::parse("matrix:u/jplatte:notareal.hs?action=chat")
1011 .expect("Failed to create MatrixUri.");
1012 assert_eq!(*matrix_uri.id(), owned_user_id!("@jplatte:notareal.hs").into());
1013 assert_eq!(matrix_uri.action(), Some(&UriAction::Chat));
1014
1015 let matrix_uri =
1016 MatrixUri::parse("matrix:r/ruma:notareal.hs").expect("Failed to create MatrixToUri.");
1017 assert_eq!(*matrix_uri.id(), owned_room_alias_id!("#ruma:notareal.hs").into());
1018
1019 let matrix_uri = MatrixUri::parse("matrix:roomid/ruma:notareal.hs?via=notareal.hs")
1020 .expect("Failed to create MatrixToUri.");
1021 assert_eq!(*matrix_uri.id(), owned_room_id!("!ruma:notareal.hs").into());
1022 assert_eq!(matrix_uri.via(), &[owned_server_name!("notareal.hs")]);
1023 assert_eq!(matrix_uri.action(), None);
1024
1025 let matrix_uri = MatrixUri::parse("matrix:r/ruma:notareal.hs/e/event:notareal.hs")
1026 .expect("Failed to create MatrixToUri.");
1027 assert_eq!(
1028 *matrix_uri.id(),
1029 (owned_room_alias_id!("#ruma:notareal.hs"), owned_event_id!("$event:notareal.hs"))
1030 .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 (owned_room_id!("!ruma:notareal.hs"), owned_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 (owned_room_id!("!ruma:notareal.hs"), owned_event_id!("$event:notareal.hs")).into()
1048 );
1049 assert_eq!(
1050 matrix_uri.via(),
1051 &vec![owned_server_name!("notareal.hs"), owned_server_name!("anotherinexistant.hs")]
1052 );
1053 assert_eq!(matrix_uri.action(), Some(&UriAction::Join));
1054 }
1055
1056 #[test]
1057 fn parse_matrixuri_invalid_uri() {
1058 assert_eq!(
1059 MatrixUri::parse("").unwrap_err(),
1060 Error::InvalidMatrixToUri(MatrixToError::InvalidUrl)
1061 );
1062 }
1063
1064 #[test]
1065 fn parse_matrixuri_wrong_scheme() {
1066 assert_eq!(
1067 MatrixUri::parse("unknown:u/user:notareal.hs").unwrap_err(),
1068 MatrixUriError::WrongScheme.into()
1069 );
1070 }
1071
1072 #[test]
1073 fn parse_matrixuri_too_many_actions() {
1074 assert_eq!(
1075 MatrixUri::parse("matrix:u/user:notareal.hs?action=chat&action=join").unwrap_err(),
1076 MatrixUriError::TooManyActions.into()
1077 );
1078 }
1079
1080 #[test]
1081 fn parse_matrixuri_unknown_query_item() {
1082 assert_eq!(
1083 MatrixUri::parse("matrix:roomid/roomid:notareal.hs?via=notareal.hs&fake=data")
1084 .unwrap_err(),
1085 MatrixUriError::UnknownQueryItem.into()
1086 );
1087 }
1088
1089 #[test]
1090 fn parse_matrixuri_wrong_identifier() {
1091 assert_matches!(
1092 MatrixUri::parse("matrix:notanidentifier").unwrap_err(),
1093 Error::InvalidMatrixId(_)
1094 );
1095 assert_matches!(MatrixUri::parse("matrix:").unwrap_err(), Error::InvalidMatrixId(_));
1096 assert_matches!(
1097 MatrixUri::parse("matrix:u/jplatte:notareal.hs/e/event:notareal.hs").unwrap_err(),
1098 Error::InvalidMatrixId(_)
1099 );
1100 }
1101}