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!(
752 matrix_to.via(),
753 &[owned_server_name!("notareal.hs"), owned_server_name!("anotherunreal.hs"),]
754 );
755
756 let matrix_to =
757 MatrixToUri::parse("https://matrix.to/#/%23ruma%3Anotareal.hs/%24event%3Anotareal.hs")
758 .expect("Failed to create MatrixToUri.");
759 assert_eq!(
760 *matrix_to.id(),
761 (owned_room_alias_id!("#ruma:notareal.hs"), owned_event_id!("$event:notareal.hs"))
762 .into()
763 );
764
765 let matrix_to =
766 MatrixToUri::parse("https://matrix.to/#/%21ruma%3Anotareal.hs/%24event%3Anotareal.hs")
767 .expect("Failed to create MatrixToUri.");
768 assert_eq!(
769 *matrix_to.id(),
770 (owned_room_id!("!ruma:notareal.hs"), owned_event_id!("$event:notareal.hs")).into()
771 );
772 assert_eq!(matrix_to.via().len(), 0);
773 }
774
775 #[test]
776 fn parse_matrixtouri_valid_uris_not_urlencoded() {
777 let matrix_to = MatrixToUri::parse("https://matrix.to/#/@jplatte:notareal.hs")
778 .expect("Failed to create MatrixToUri.");
779 assert_eq!(*matrix_to.id(), owned_user_id!("@jplatte:notareal.hs").into());
780
781 let matrix_to = MatrixToUri::parse("https://matrix.to/#/#ruma:notareal.hs")
782 .expect("Failed to create MatrixToUri.");
783 assert_eq!(*matrix_to.id(), owned_room_alias_id!("#ruma:notareal.hs").into());
784
785 let matrix_to = MatrixToUri::parse("https://matrix.to/#/!ruma:notareal.hs?via=notareal.hs")
786 .expect("Failed to create MatrixToUri.");
787 assert_eq!(*matrix_to.id(), owned_room_id!("!ruma:notareal.hs").into());
788 assert_eq!(matrix_to.via(), &[owned_server_name!("notareal.hs")]);
789
790 let matrix_to =
791 MatrixToUri::parse("https://matrix.to/#/#ruma:notareal.hs/$event:notareal.hs")
792 .expect("Failed to create MatrixToUri.");
793 assert_eq!(
794 *matrix_to.id(),
795 (owned_room_alias_id!("#ruma:notareal.hs"), owned_event_id!("$event:notareal.hs"))
796 .into()
797 );
798
799 let matrix_to =
800 MatrixToUri::parse("https://matrix.to/#/!ruma:notareal.hs/$event:notareal.hs")
801 .expect("Failed to create MatrixToUri.");
802 assert_eq!(
803 *matrix_to.id(),
804 (owned_room_id!("!ruma:notareal.hs"), owned_event_id!("$event:notareal.hs")).into()
805 );
806 assert_eq!(matrix_to.via().len(), 0);
807 }
808
809 #[test]
810 fn parse_matrixtouri_wrong_base_url() {
811 assert_eq!(MatrixToUri::parse("").unwrap_err(), MatrixToError::WrongBaseUrl.into());
812 assert_eq!(
813 MatrixToUri::parse("https://notreal.to/#/").unwrap_err(),
814 MatrixToError::WrongBaseUrl.into()
815 );
816 }
817
818 #[test]
819 fn parse_matrixtouri_wrong_identifier() {
820 assert_matches!(
821 MatrixToUri::parse("https://matrix.to/#/notanidentifier").unwrap_err(),
822 Error::InvalidMatrixId(_)
823 );
824 assert_matches!(
825 MatrixToUri::parse("https://matrix.to/#/").unwrap_err(),
826 Error::InvalidMatrixId(_)
827 );
828 assert_matches!(
829 MatrixToUri::parse(
830 "https://matrix.to/#/%40jplatte%3Anotareal.hs/%24event%3Anotareal.hs"
831 )
832 .unwrap_err(),
833 Error::InvalidMatrixId(_)
834 );
835 }
836
837 #[test]
838 fn parse_matrixtouri_unknown_arguments() {
839 assert_eq!(
840 MatrixToUri::parse(
841 "https://matrix.to/#/%21ruma%3Anotareal.hs?via=notareal.hs&custom=data"
842 )
843 .unwrap_err(),
844 MatrixToError::UnknownArgument.into()
845 );
846 }
847
848 #[test]
849 fn display_matrixuri() {
850 assert_eq!(
851 user_id!("@jplatte:notareal.hs").matrix_uri(false).to_string(),
852 "matrix:u/jplatte:notareal.hs"
853 );
854 assert_eq!(
855 user_id!("@jplatte:notareal.hs").matrix_uri(true).to_string(),
856 "matrix:u/jplatte:notareal.hs?action=chat"
857 );
858 assert_eq!(
859 room_alias_id!("#ruma:notareal.hs").matrix_uri(false).to_string(),
860 "matrix:r/ruma:notareal.hs"
861 );
862 assert_eq!(
863 room_alias_id!("#ruma:notareal.hs").matrix_uri(true).to_string(),
864 "matrix:r/ruma:notareal.hs?action=join"
865 );
866 assert_eq!(
867 room_id!("!ruma:notareal.hs").matrix_uri(false).to_string(),
868 "matrix:roomid/ruma:notareal.hs"
869 );
870 assert_eq!(
871 room_id!("!ruma:notareal.hs")
872 .matrix_uri_via(vec![owned_server_name!("notareal.hs")], false)
873 .to_string(),
874 "matrix:roomid/ruma:notareal.hs?via=notareal.hs"
875 );
876 assert_eq!(
877 room_id!("!ruma:notareal.hs")
878 .matrix_uri_via(
879 vec![owned_server_name!("notareal.hs"), owned_server_name!("anotherunreal.hs")],
880 true
881 )
882 .to_string(),
883 "matrix:roomid/ruma:notareal.hs?via=notareal.hs&via=anotherunreal.hs&action=join"
884 );
885 #[allow(deprecated)]
886 let uri = room_alias_id!("#ruma:notareal.hs")
887 .matrix_event_uri(owned_event_id!("$event:notareal.hs"))
888 .to_string();
889 assert_eq!(uri, "matrix:r/ruma:notareal.hs/e/event:notareal.hs");
890 assert_eq!(
891 room_id!("!ruma:notareal.hs")
892 .matrix_event_uri(owned_event_id!("$event:notareal.hs"))
893 .to_string(),
894 "matrix:roomid/ruma:notareal.hs/e/event:notareal.hs"
895 );
896 assert_eq!(
897 room_id!("!ruma:notareal.hs")
898 .matrix_event_uri_via(
899 owned_event_id!("$event:notareal.hs"),
900 vec![owned_server_name!("notareal.hs")]
901 )
902 .to_string(),
903 "matrix:roomid/ruma:notareal.hs/e/event:notareal.hs?via=notareal.hs"
904 );
905 }
906
907 #[test]
908 fn parse_valid_matrixid_with_type() {
909 assert_eq!(
910 MatrixId::parse_with_type("u/user:imaginary.hs").expect("Failed to create MatrixId."),
911 MatrixId::User(owned_user_id!("@user:imaginary.hs"))
912 );
913 assert_eq!(
914 MatrixId::parse_with_type("user/user:imaginary.hs")
915 .expect("Failed to create MatrixId."),
916 MatrixId::User(owned_user_id!("@user:imaginary.hs"))
917 );
918 assert_eq!(
919 MatrixId::parse_with_type("roomid/roomid:imaginary.hs")
920 .expect("Failed to create MatrixId."),
921 MatrixId::Room(owned_room_id!("!roomid:imaginary.hs"))
922 );
923 assert_eq!(
924 MatrixId::parse_with_type("r/roomalias:imaginary.hs")
925 .expect("Failed to create MatrixId."),
926 MatrixId::RoomAlias(owned_room_alias_id!("#roomalias:imaginary.hs"))
927 );
928 assert_eq!(
929 MatrixId::parse_with_type("room/roomalias:imaginary.hs")
930 .expect("Failed to create MatrixId."),
931 MatrixId::RoomAlias(owned_room_alias_id!("#roomalias:imaginary.hs"))
932 );
933 assert_eq!(
934 MatrixId::parse_with_type("roomid/roomid:imaginary.hs/e/event:imaginary.hs")
935 .expect("Failed to create MatrixId."),
936 MatrixId::Event(
937 owned_room_id!("!roomid:imaginary.hs").into(),
938 owned_event_id!("$event:imaginary.hs")
939 )
940 );
941 assert_eq!(
942 MatrixId::parse_with_type("r/roomalias:imaginary.hs/e/event:imaginary.hs")
943 .expect("Failed to create MatrixId."),
944 MatrixId::Event(
945 owned_room_alias_id!("#roomalias:imaginary.hs").into(),
946 owned_event_id!("$event:imaginary.hs")
947 )
948 );
949 assert_eq!(
950 MatrixId::parse_with_type("room/roomalias:imaginary.hs/event/event:imaginary.hs")
951 .expect("Failed to create MatrixId."),
952 MatrixId::Event(
953 owned_room_alias_id!("#roomalias:imaginary.hs").into(),
954 owned_event_id!("$event:imaginary.hs")
955 )
956 );
957 assert_eq!(
959 MatrixId::parse_with_type("e/event:imaginary.hs/roomid/roomid:imaginary.hs")
960 .expect("Failed to create MatrixId."),
961 MatrixId::Event(
962 owned_room_id!("!roomid:imaginary.hs").into(),
963 owned_event_id!("$event:imaginary.hs")
964 )
965 );
966 assert_eq!(
967 MatrixId::parse_with_type("e/event:imaginary.hs/r/roomalias:imaginary.hs")
968 .expect("Failed to create MatrixId."),
969 MatrixId::Event(
970 owned_room_alias_id!("#roomalias:imaginary.hs").into(),
971 owned_event_id!("$event:imaginary.hs")
972 )
973 );
974 assert_eq!(
976 MatrixId::parse_with_type("/u/user:imaginary.hs").expect("Failed to create MatrixId."),
977 MatrixId::User(owned_user_id!("@user:imaginary.hs"))
978 );
979 assert_eq!(
981 MatrixId::parse_with_type("roomid/roomid:imaginary.hs/")
982 .expect("Failed to create MatrixId."),
983 MatrixId::Room(owned_room_id!("!roomid:imaginary.hs"))
984 );
985 assert_eq!(
987 MatrixId::parse_with_type("/r/roomalias:imaginary.hs/")
988 .expect("Failed to create MatrixId."),
989 MatrixId::RoomAlias(owned_room_alias_id!("#roomalias:imaginary.hs"))
990 );
991 }
992
993 #[test]
994 fn parse_matrixid_type_no_identifier() {
995 assert_eq!(MatrixId::parse_with_type("").unwrap_err(), MatrixIdError::NoIdentifier.into());
996 assert_eq!(MatrixId::parse_with_type("/").unwrap_err(), MatrixIdError::NoIdentifier.into());
997 }
998
999 #[test]
1000 fn parse_matrixid_invalid_parts_number() {
1001 assert_eq!(
1002 MatrixId::parse_with_type("u/user:imaginary.hs/r/room:imaginary.hs/e").unwrap_err(),
1003 MatrixIdError::InvalidPartsNumber.into()
1004 );
1005 }
1006
1007 #[test]
1008 fn parse_matrixid_unknown_type() {
1009 assert_eq!(
1010 MatrixId::parse_with_type("notatype/fake:notareal.hs").unwrap_err(),
1011 MatrixIdError::UnknownType.into()
1012 );
1013 }
1014
1015 #[test]
1016 fn parse_matrixuri_valid_uris() {
1017 let matrix_uri =
1018 MatrixUri::parse("matrix:u/jplatte:notareal.hs").expect("Failed to create MatrixUri.");
1019 assert_eq!(*matrix_uri.id(), owned_user_id!("@jplatte:notareal.hs").into());
1020 assert_eq!(matrix_uri.action(), None);
1021
1022 let matrix_uri = MatrixUri::parse("matrix:u/jplatte:notareal.hs?action=chat")
1023 .expect("Failed to create MatrixUri.");
1024 assert_eq!(*matrix_uri.id(), owned_user_id!("@jplatte:notareal.hs").into());
1025 assert_eq!(matrix_uri.action(), Some(&UriAction::Chat));
1026
1027 let matrix_uri =
1028 MatrixUri::parse("matrix:r/ruma:notareal.hs").expect("Failed to create MatrixToUri.");
1029 assert_eq!(*matrix_uri.id(), owned_room_alias_id!("#ruma:notareal.hs").into());
1030
1031 let matrix_uri = MatrixUri::parse("matrix:roomid/ruma:notareal.hs?via=notareal.hs")
1032 .expect("Failed to create MatrixToUri.");
1033 assert_eq!(*matrix_uri.id(), owned_room_id!("!ruma:notareal.hs").into());
1034 assert_eq!(matrix_uri.via(), &[owned_server_name!("notareal.hs")]);
1035 assert_eq!(matrix_uri.action(), None);
1036
1037 let matrix_uri = MatrixUri::parse("matrix:r/ruma:notareal.hs/e/event:notareal.hs")
1038 .expect("Failed to create MatrixToUri.");
1039 assert_eq!(
1040 *matrix_uri.id(),
1041 (owned_room_alias_id!("#ruma:notareal.hs"), owned_event_id!("$event:notareal.hs"))
1042 .into()
1043 );
1044
1045 let matrix_uri = MatrixUri::parse("matrix:roomid/ruma:notareal.hs/e/event:notareal.hs")
1046 .expect("Failed to create MatrixToUri.");
1047 assert_eq!(
1048 *matrix_uri.id(),
1049 (owned_room_id!("!ruma:notareal.hs"), owned_event_id!("$event:notareal.hs")).into()
1050 );
1051 assert_eq!(matrix_uri.via().len(), 0);
1052 assert_eq!(matrix_uri.action(), None);
1053
1054 let matrix_uri =
1055 MatrixUri::parse("matrix:roomid/ruma:notareal.hs/e/event:notareal.hs?via=notareal.hs&action=join&via=anotherinexistant.hs")
1056 .expect("Failed to create MatrixToUri.");
1057 assert_eq!(
1058 *matrix_uri.id(),
1059 (owned_room_id!("!ruma:notareal.hs"), owned_event_id!("$event:notareal.hs")).into()
1060 );
1061 assert_eq!(
1062 matrix_uri.via(),
1063 &vec![owned_server_name!("notareal.hs"), owned_server_name!("anotherinexistant.hs")]
1064 );
1065 assert_eq!(matrix_uri.action(), Some(&UriAction::Join));
1066 }
1067
1068 #[test]
1069 fn parse_matrixuri_invalid_uri() {
1070 assert_eq!(
1071 MatrixUri::parse("").unwrap_err(),
1072 Error::InvalidMatrixToUri(MatrixToError::InvalidUrl)
1073 );
1074 }
1075
1076 #[test]
1077 fn parse_matrixuri_wrong_scheme() {
1078 assert_eq!(
1079 MatrixUri::parse("unknown:u/user:notareal.hs").unwrap_err(),
1080 MatrixUriError::WrongScheme.into()
1081 );
1082 }
1083
1084 #[test]
1085 fn parse_matrixuri_too_many_actions() {
1086 assert_eq!(
1087 MatrixUri::parse("matrix:u/user:notareal.hs?action=chat&action=join").unwrap_err(),
1088 MatrixUriError::TooManyActions.into()
1089 );
1090 }
1091
1092 #[test]
1093 fn parse_matrixuri_unknown_query_item() {
1094 assert_eq!(
1095 MatrixUri::parse("matrix:roomid/roomid:notareal.hs?via=notareal.hs&fake=data")
1096 .unwrap_err(),
1097 MatrixUriError::UnknownQueryItem.into()
1098 );
1099 }
1100
1101 #[test]
1102 fn parse_matrixuri_wrong_identifier() {
1103 assert_matches!(
1104 MatrixUri::parse("matrix:notanidentifier").unwrap_err(),
1105 Error::InvalidMatrixId(_)
1106 );
1107 assert_matches!(MatrixUri::parse("matrix:").unwrap_err(), Error::InvalidMatrixId(_));
1108 assert_matches!(
1109 MatrixUri::parse("matrix:u/jplatte:notareal.hs/e/event:notareal.hs").unwrap_err(),
1110 Error::InvalidMatrixId(_)
1111 );
1112 }
1113}