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 if key == "via" {
318 ServerName::parse(&value)
319 } else {
320 Err(MatrixToError::UnknownArgument.into())
321 }
322 })
323 .collect::<Result<Vec<_>, _>>()
324 })
325 .transpose()?
326 .unwrap_or_default();
327
328 if parts.next().is_some() {
330 return Err(MatrixToError::InvalidUrl.into());
331 }
332
333 Ok(Self { id, via })
334 }
335}
336
337impl fmt::Display for MatrixToUri {
338 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
339 f.write_str(MATRIX_TO_BASE_URL)?;
340 write!(f, "{}", self.id().to_string_with_sigil())?;
341
342 let mut first = true;
343 for server_name in &self.via {
344 f.write_str(if first { "?via=" } else { "&via=" })?;
345 f.write_str(server_name.as_str())?;
346
347 first = false;
348 }
349
350 Ok(())
351 }
352}
353
354impl TryFrom<&str> for MatrixToUri {
355 type Error = Error;
356
357 fn try_from(s: &str) -> Result<Self, Self::Error> {
358 Self::parse(s)
359 }
360}
361
362impl FromStr for MatrixToUri {
363 type Err = Error;
364
365 fn from_str(s: &str) -> Result<Self, Self::Err> {
366 Self::parse(s)
367 }
368}
369
370#[derive(Clone, Debug, PartialEq, Eq)]
372#[non_exhaustive]
373pub enum UriAction {
374 Join,
379
380 Chat,
387
388 #[doc(hidden)]
389 _Custom(PrivOwnedStr),
390}
391
392impl UriAction {
393 pub fn as_str(&self) -> &str {
395 self.as_ref()
396 }
397
398 fn from<T>(s: T) -> Self
399 where
400 T: AsRef<str> + Into<Box<str>>,
401 {
402 match s.as_ref() {
403 "join" => UriAction::Join,
404 "chat" => UriAction::Chat,
405 _ => UriAction::_Custom(PrivOwnedStr(s.into())),
406 }
407 }
408}
409
410impl AsRef<str> for UriAction {
411 fn as_ref(&self) -> &str {
412 match self {
413 UriAction::Join => "join",
414 UriAction::Chat => "chat",
415 UriAction::_Custom(s) => s.0.as_ref(),
416 }
417 }
418}
419
420impl fmt::Display for UriAction {
421 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
422 write!(f, "{}", self.as_ref())?;
423 Ok(())
424 }
425}
426
427impl From<&str> for UriAction {
428 fn from(s: &str) -> Self {
429 Self::from(s)
430 }
431}
432
433impl From<String> for UriAction {
434 fn from(s: String) -> Self {
435 Self::from(s)
436 }
437}
438
439impl From<Box<str>> for UriAction {
440 fn from(s: Box<str>) -> Self {
441 Self::from(s)
442 }
443}
444
445#[derive(Debug, Clone, PartialEq, Eq)]
452pub struct MatrixUri {
453 id: MatrixId,
454 via: Vec<OwnedServerName>,
455 action: Option<UriAction>,
456}
457
458impl MatrixUri {
459 pub(crate) fn new(id: MatrixId, via: Vec<OwnedServerName>, action: Option<UriAction>) -> Self {
460 Self { id, via, action }
461 }
462
463 pub fn id(&self) -> &MatrixId {
465 &self.id
466 }
467
468 pub fn via(&self) -> &[OwnedServerName] {
470 &self.via
471 }
472
473 pub fn action(&self) -> Option<&UriAction> {
475 self.action.as_ref()
476 }
477
478 pub fn parse(s: &str) -> Result<Self, Error> {
480 let url = Url::parse(s).map_err(|_| MatrixToError::InvalidUrl)?;
481
482 if url.scheme() != MATRIX_SCHEME {
483 return Err(MatrixUriError::WrongScheme.into());
484 }
485
486 let id = MatrixId::parse_with_type(url.path())?;
487
488 let mut via = vec![];
489 let mut action = None;
490
491 for (key, value) in url.query_pairs() {
492 if key.as_ref() == "via" {
493 via.push(value.parse()?);
494 } else if key.as_ref() == "action" {
495 if action.is_some() {
496 return Err(MatrixUriError::TooManyActions.into());
497 };
498
499 action = Some(value.as_ref().into());
500 } else {
501 return Err(MatrixUriError::UnknownQueryItem.into());
502 }
503 }
504
505 Ok(Self { id, via, action })
506 }
507}
508
509impl fmt::Display for MatrixUri {
510 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
511 write!(f, "{MATRIX_SCHEME}:{}", self.id().to_string_with_type())?;
512
513 let mut first = true;
514 for server_name in &self.via {
515 f.write_str(if first { "?via=" } else { "&via=" })?;
516 f.write_str(server_name.as_str())?;
517
518 first = false;
519 }
520
521 if let Some(action) = self.action() {
522 f.write_str(if first { "?action=" } else { "&action=" })?;
523 f.write_str(action.as_str())?;
524 }
525
526 Ok(())
527 }
528}
529
530impl TryFrom<&str> for MatrixUri {
531 type Error = Error;
532
533 fn try_from(s: &str) -> Result<Self, Self::Error> {
534 Self::parse(s)
535 }
536}
537
538impl FromStr for MatrixUri {
539 type Err = Error;
540
541 fn from_str(s: &str) -> Result<Self, Self::Err> {
542 Self::parse(s)
543 }
544}
545
546#[cfg(test)]
547mod tests {
548 use assert_matches2::assert_matches;
549 use ruma_identifiers_validation::{
550 error::{MatrixIdError, MatrixToError, MatrixUriError},
551 Error,
552 };
553
554 use super::{MatrixId, MatrixToUri, MatrixUri};
555 use crate::{
556 event_id, matrix_uri::UriAction, room_alias_id, room_id, server_name, user_id,
557 RoomOrAliasId,
558 };
559
560 #[test]
561 fn display_matrixtouri() {
562 assert_eq!(
563 user_id!("@jplatte:notareal.hs").matrix_to_uri().to_string(),
564 "https://matrix.to/#/@jplatte:notareal.hs"
565 );
566 assert_eq!(
567 room_alias_id!("#ruma:notareal.hs").matrix_to_uri().to_string(),
568 "https://matrix.to/#/%23ruma:notareal.hs"
569 );
570 assert_eq!(
571 room_id!("!ruma:notareal.hs").matrix_to_uri().to_string(),
572 "https://matrix.to/#/!ruma:notareal.hs"
573 );
574 assert_eq!(
575 room_id!("!ruma:notareal.hs")
576 .matrix_to_uri_via(vec![server_name!("notareal.hs")])
577 .to_string(),
578 "https://matrix.to/#/!ruma:notareal.hs?via=notareal.hs"
579 );
580 #[allow(deprecated)]
581 let uri = room_alias_id!("#ruma:notareal.hs")
582 .matrix_to_event_uri(event_id!("$event:notareal.hs"))
583 .to_string();
584 assert_eq!(uri, "https://matrix.to/#/%23ruma:notareal.hs/$event:notareal.hs");
585 assert_eq!(
586 room_id!("!ruma:notareal.hs")
587 .matrix_to_event_uri(event_id!("$event:notareal.hs"))
588 .to_string(),
589 "https://matrix.to/#/!ruma:notareal.hs/$event:notareal.hs"
590 );
591 assert_eq!(
592 room_id!("!ruma:notareal.hs")
593 .matrix_to_event_uri_via(
594 event_id!("$event:notareal.hs"),
595 vec![server_name!("notareal.hs")]
596 )
597 .to_string(),
598 "https://matrix.to/#/!ruma:notareal.hs/$event:notareal.hs?via=notareal.hs"
599 );
600 }
601
602 #[test]
603 fn parse_valid_matrixid_with_sigil() {
604 assert_eq!(
605 MatrixId::parse_with_sigil("@user:imaginary.hs").expect("Failed to create MatrixId."),
606 MatrixId::User(user_id!("@user:imaginary.hs").into())
607 );
608 assert_eq!(
609 MatrixId::parse_with_sigil("!roomid:imaginary.hs").expect("Failed to create MatrixId."),
610 MatrixId::Room(room_id!("!roomid:imaginary.hs").into())
611 );
612 assert_eq!(
613 MatrixId::parse_with_sigil("#roomalias:imaginary.hs")
614 .expect("Failed to create MatrixId."),
615 MatrixId::RoomAlias(room_alias_id!("#roomalias:imaginary.hs").into())
616 );
617 assert_eq!(
618 MatrixId::parse_with_sigil("!roomid:imaginary.hs/$event:imaginary.hs")
619 .expect("Failed to create MatrixId."),
620 MatrixId::Event(
621 <&RoomOrAliasId>::from(room_id!("!roomid:imaginary.hs")).into(),
622 event_id!("$event:imaginary.hs").into()
623 )
624 );
625 assert_eq!(
626 MatrixId::parse_with_sigil("#roomalias:imaginary.hs/$event:imaginary.hs")
627 .expect("Failed to create MatrixId."),
628 MatrixId::Event(
629 <&RoomOrAliasId>::from(room_alias_id!("#roomalias:imaginary.hs")).into(),
630 event_id!("$event:imaginary.hs").into()
631 )
632 );
633 assert_eq!(
635 MatrixId::parse_with_sigil("$event:imaginary.hs/!roomid:imaginary.hs")
636 .expect("Failed to create MatrixId."),
637 MatrixId::Event(
638 <&RoomOrAliasId>::from(room_id!("!roomid:imaginary.hs")).into(),
639 event_id!("$event:imaginary.hs").into()
640 )
641 );
642 assert_eq!(
643 MatrixId::parse_with_sigil("$event:imaginary.hs/#roomalias:imaginary.hs")
644 .expect("Failed to create MatrixId."),
645 MatrixId::Event(
646 <&RoomOrAliasId>::from(room_alias_id!("#roomalias:imaginary.hs")).into(),
647 event_id!("$event:imaginary.hs").into()
648 )
649 );
650 assert_eq!(
652 MatrixId::parse_with_sigil("/@user:imaginary.hs").expect("Failed to create MatrixId."),
653 MatrixId::User(user_id!("@user:imaginary.hs").into())
654 );
655 assert_eq!(
657 MatrixId::parse_with_sigil("!roomid:imaginary.hs/")
658 .expect("Failed to create MatrixId."),
659 MatrixId::Room(room_id!("!roomid:imaginary.hs").into())
660 );
661 assert_eq!(
663 MatrixId::parse_with_sigil("/#roomalias:imaginary.hs/")
664 .expect("Failed to create MatrixId."),
665 MatrixId::RoomAlias(room_alias_id!("#roomalias:imaginary.hs").into())
666 );
667 }
668
669 #[test]
670 fn parse_matrixid_no_identifier() {
671 assert_eq!(MatrixId::parse_with_sigil("").unwrap_err(), MatrixIdError::NoIdentifier.into());
672 assert_eq!(
673 MatrixId::parse_with_sigil("/").unwrap_err(),
674 MatrixIdError::NoIdentifier.into()
675 );
676 }
677
678 #[test]
679 fn parse_matrixid_too_many_identifiers() {
680 assert_eq!(
681 MatrixId::parse_with_sigil(
682 "@user:imaginary.hs/#room:imaginary.hs/$event1:imaginary.hs"
683 )
684 .unwrap_err(),
685 MatrixIdError::TooManyIdentifiers.into()
686 );
687 }
688
689 #[test]
690 fn parse_matrixid_unknown_identifier_pair() {
691 assert_eq!(
692 MatrixId::parse_with_sigil("!roomid:imaginary.hs/@user:imaginary.hs").unwrap_err(),
693 MatrixIdError::UnknownIdentifierPair.into()
694 );
695 assert_eq!(
696 MatrixId::parse_with_sigil("#roomalias:imaginary.hs/notanidentifier").unwrap_err(),
697 MatrixIdError::UnknownIdentifierPair.into()
698 );
699 assert_eq!(
700 MatrixId::parse_with_sigil("$event:imaginary.hs/$otherevent:imaginary.hs").unwrap_err(),
701 MatrixIdError::UnknownIdentifierPair.into()
702 );
703 assert_eq!(
704 MatrixId::parse_with_sigil("notanidentifier/neitheristhis").unwrap_err(),
705 MatrixIdError::UnknownIdentifierPair.into()
706 );
707 }
708
709 #[test]
710 fn parse_matrixid_missing_room() {
711 assert_eq!(
712 MatrixId::parse_with_sigil("$event:imaginary.hs").unwrap_err(),
713 MatrixIdError::MissingRoom.into()
714 );
715 }
716
717 #[test]
718 fn parse_matrixid_unknown_identifier() {
719 assert_eq!(
720 MatrixId::parse_with_sigil("event:imaginary.hs").unwrap_err(),
721 MatrixIdError::UnknownIdentifier.into()
722 );
723 assert_eq!(
724 MatrixId::parse_with_sigil("notanidentifier").unwrap_err(),
725 MatrixIdError::UnknownIdentifier.into()
726 );
727 }
728
729 #[test]
730 fn parse_matrixtouri_valid_uris() {
731 let matrix_to = MatrixToUri::parse("https://matrix.to/#/%40jplatte%3Anotareal.hs")
732 .expect("Failed to create MatrixToUri.");
733 assert_eq!(matrix_to.id(), &user_id!("@jplatte:notareal.hs").into());
734
735 let matrix_to = MatrixToUri::parse("https://matrix.to/#/%23ruma%3Anotareal.hs")
736 .expect("Failed to create MatrixToUri.");
737 assert_eq!(matrix_to.id(), &room_alias_id!("#ruma:notareal.hs").into());
738
739 let matrix_to = MatrixToUri::parse(
740 "https://matrix.to/#/%21ruma%3Anotareal.hs?via=notareal.hs&via=anotherunreal.hs",
741 )
742 .expect("Failed to create MatrixToUri.");
743 assert_eq!(matrix_to.id(), &room_id!("!ruma:notareal.hs").into());
744 assert_eq!(
745 matrix_to.via(),
746 &[server_name!("notareal.hs").to_owned(), server_name!("anotherunreal.hs").to_owned(),]
747 );
748
749 let matrix_to =
750 MatrixToUri::parse("https://matrix.to/#/%23ruma%3Anotareal.hs/%24event%3Anotareal.hs")
751 .expect("Failed to create MatrixToUri.");
752 assert_eq!(
753 matrix_to.id(),
754 &(room_alias_id!("#ruma:notareal.hs"), event_id!("$event:notareal.hs")).into()
755 );
756
757 let matrix_to =
758 MatrixToUri::parse("https://matrix.to/#/%21ruma%3Anotareal.hs/%24event%3Anotareal.hs")
759 .expect("Failed to create MatrixToUri.");
760 assert_eq!(
761 matrix_to.id(),
762 &(room_id!("!ruma:notareal.hs"), event_id!("$event:notareal.hs")).into()
763 );
764 assert_eq!(matrix_to.via().len(), 0);
765 }
766
767 #[test]
768 fn parse_matrixtouri_valid_uris_not_urlencoded() {
769 let matrix_to = MatrixToUri::parse("https://matrix.to/#/@jplatte:notareal.hs")
770 .expect("Failed to create MatrixToUri.");
771 assert_eq!(matrix_to.id(), &user_id!("@jplatte:notareal.hs").into());
772
773 let matrix_to = MatrixToUri::parse("https://matrix.to/#/#ruma:notareal.hs")
774 .expect("Failed to create MatrixToUri.");
775 assert_eq!(matrix_to.id(), &room_alias_id!("#ruma:notareal.hs").into());
776
777 let matrix_to = MatrixToUri::parse("https://matrix.to/#/!ruma:notareal.hs?via=notareal.hs")
778 .expect("Failed to create MatrixToUri.");
779 assert_eq!(matrix_to.id(), &room_id!("!ruma:notareal.hs").into());
780 assert_eq!(matrix_to.via(), &[server_name!("notareal.hs").to_owned()]);
781
782 let matrix_to =
783 MatrixToUri::parse("https://matrix.to/#/#ruma:notareal.hs/$event:notareal.hs")
784 .expect("Failed to create MatrixToUri.");
785 assert_eq!(
786 matrix_to.id(),
787 &(room_alias_id!("#ruma:notareal.hs"), event_id!("$event:notareal.hs")).into()
788 );
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 &(room_id!("!ruma:notareal.hs"), event_id!("$event:notareal.hs")).into()
796 );
797 assert_eq!(matrix_to.via().len(), 0);
798 }
799
800 #[test]
801 fn parse_matrixtouri_wrong_base_url() {
802 assert_eq!(MatrixToUri::parse("").unwrap_err(), MatrixToError::WrongBaseUrl.into());
803 assert_eq!(
804 MatrixToUri::parse("https://notreal.to/#/").unwrap_err(),
805 MatrixToError::WrongBaseUrl.into()
806 );
807 }
808
809 #[test]
810 fn parse_matrixtouri_wrong_identifier() {
811 assert_matches!(
812 MatrixToUri::parse("https://matrix.to/#/notanidentifier").unwrap_err(),
813 Error::InvalidMatrixId(_)
814 );
815 assert_matches!(
816 MatrixToUri::parse("https://matrix.to/#/").unwrap_err(),
817 Error::InvalidMatrixId(_)
818 );
819 assert_matches!(
820 MatrixToUri::parse(
821 "https://matrix.to/#/%40jplatte%3Anotareal.hs/%24event%3Anotareal.hs"
822 )
823 .unwrap_err(),
824 Error::InvalidMatrixId(_)
825 );
826 }
827
828 #[test]
829 fn parse_matrixtouri_unknown_arguments() {
830 assert_eq!(
831 MatrixToUri::parse(
832 "https://matrix.to/#/%21ruma%3Anotareal.hs?via=notareal.hs&custom=data"
833 )
834 .unwrap_err(),
835 MatrixToError::UnknownArgument.into()
836 );
837 }
838
839 #[test]
840 fn display_matrixuri() {
841 assert_eq!(
842 user_id!("@jplatte:notareal.hs").matrix_uri(false).to_string(),
843 "matrix:u/jplatte:notareal.hs"
844 );
845 assert_eq!(
846 user_id!("@jplatte:notareal.hs").matrix_uri(true).to_string(),
847 "matrix:u/jplatte:notareal.hs?action=chat"
848 );
849 assert_eq!(
850 room_alias_id!("#ruma:notareal.hs").matrix_uri(false).to_string(),
851 "matrix:r/ruma:notareal.hs"
852 );
853 assert_eq!(
854 room_alias_id!("#ruma:notareal.hs").matrix_uri(true).to_string(),
855 "matrix:r/ruma:notareal.hs?action=join"
856 );
857 assert_eq!(
858 room_id!("!ruma:notareal.hs").matrix_uri(false).to_string(),
859 "matrix:roomid/ruma:notareal.hs"
860 );
861 assert_eq!(
862 room_id!("!ruma:notareal.hs")
863 .matrix_uri_via(vec![server_name!("notareal.hs")], false)
864 .to_string(),
865 "matrix:roomid/ruma:notareal.hs?via=notareal.hs"
866 );
867 assert_eq!(
868 room_id!("!ruma:notareal.hs")
869 .matrix_uri_via(
870 vec![server_name!("notareal.hs"), server_name!("anotherunreal.hs")],
871 true
872 )
873 .to_string(),
874 "matrix:roomid/ruma:notareal.hs?via=notareal.hs&via=anotherunreal.hs&action=join"
875 );
876 #[allow(deprecated)]
877 let uri = room_alias_id!("#ruma:notareal.hs")
878 .matrix_event_uri(event_id!("$event:notareal.hs"))
879 .to_string();
880 assert_eq!(uri, "matrix:r/ruma:notareal.hs/e/event:notareal.hs");
881 assert_eq!(
882 room_id!("!ruma:notareal.hs")
883 .matrix_event_uri(event_id!("$event:notareal.hs"))
884 .to_string(),
885 "matrix:roomid/ruma:notareal.hs/e/event:notareal.hs"
886 );
887 assert_eq!(
888 room_id!("!ruma:notareal.hs")
889 .matrix_event_uri_via(
890 event_id!("$event:notareal.hs"),
891 vec![server_name!("notareal.hs")]
892 )
893 .to_string(),
894 "matrix:roomid/ruma:notareal.hs/e/event:notareal.hs?via=notareal.hs"
895 );
896 }
897
898 #[test]
899 fn parse_valid_matrixid_with_type() {
900 assert_eq!(
901 MatrixId::parse_with_type("u/user:imaginary.hs").expect("Failed to create MatrixId."),
902 MatrixId::User(user_id!("@user:imaginary.hs").into())
903 );
904 assert_eq!(
905 MatrixId::parse_with_type("user/user:imaginary.hs")
906 .expect("Failed to create MatrixId."),
907 MatrixId::User(user_id!("@user:imaginary.hs").into())
908 );
909 assert_eq!(
910 MatrixId::parse_with_type("roomid/roomid:imaginary.hs")
911 .expect("Failed to create MatrixId."),
912 MatrixId::Room(room_id!("!roomid:imaginary.hs").into())
913 );
914 assert_eq!(
915 MatrixId::parse_with_type("r/roomalias:imaginary.hs")
916 .expect("Failed to create MatrixId."),
917 MatrixId::RoomAlias(room_alias_id!("#roomalias:imaginary.hs").into())
918 );
919 assert_eq!(
920 MatrixId::parse_with_type("room/roomalias:imaginary.hs")
921 .expect("Failed to create MatrixId."),
922 MatrixId::RoomAlias(room_alias_id!("#roomalias:imaginary.hs").into())
923 );
924 assert_eq!(
925 MatrixId::parse_with_type("roomid/roomid:imaginary.hs/e/event:imaginary.hs")
926 .expect("Failed to create MatrixId."),
927 MatrixId::Event(
928 <&RoomOrAliasId>::from(room_id!("!roomid:imaginary.hs")).into(),
929 event_id!("$event:imaginary.hs").into()
930 )
931 );
932 assert_eq!(
933 MatrixId::parse_with_type("r/roomalias:imaginary.hs/e/event:imaginary.hs")
934 .expect("Failed to create MatrixId."),
935 MatrixId::Event(
936 <&RoomOrAliasId>::from(room_alias_id!("#roomalias:imaginary.hs")).into(),
937 event_id!("$event:imaginary.hs").into()
938 )
939 );
940 assert_eq!(
941 MatrixId::parse_with_type("room/roomalias:imaginary.hs/event/event:imaginary.hs")
942 .expect("Failed to create MatrixId."),
943 MatrixId::Event(
944 <&RoomOrAliasId>::from(room_alias_id!("#roomalias:imaginary.hs")).into(),
945 event_id!("$event:imaginary.hs").into()
946 )
947 );
948 assert_eq!(
950 MatrixId::parse_with_type("e/event:imaginary.hs/roomid/roomid:imaginary.hs")
951 .expect("Failed to create MatrixId."),
952 MatrixId::Event(
953 <&RoomOrAliasId>::from(room_id!("!roomid:imaginary.hs")).into(),
954 event_id!("$event:imaginary.hs").into()
955 )
956 );
957 assert_eq!(
958 MatrixId::parse_with_type("e/event:imaginary.hs/r/roomalias:imaginary.hs")
959 .expect("Failed to create MatrixId."),
960 MatrixId::Event(
961 <&RoomOrAliasId>::from(room_alias_id!("#roomalias:imaginary.hs")).into(),
962 event_id!("$event:imaginary.hs").into()
963 )
964 );
965 assert_eq!(
967 MatrixId::parse_with_type("/u/user:imaginary.hs").expect("Failed to create MatrixId."),
968 MatrixId::User(user_id!("@user:imaginary.hs").into())
969 );
970 assert_eq!(
972 MatrixId::parse_with_type("roomid/roomid:imaginary.hs/")
973 .expect("Failed to create MatrixId."),
974 MatrixId::Room(room_id!("!roomid:imaginary.hs").into())
975 );
976 assert_eq!(
978 MatrixId::parse_with_type("/r/roomalias:imaginary.hs/")
979 .expect("Failed to create MatrixId."),
980 MatrixId::RoomAlias(room_alias_id!("#roomalias:imaginary.hs").into())
981 );
982 }
983
984 #[test]
985 fn parse_matrixid_type_no_identifier() {
986 assert_eq!(MatrixId::parse_with_type("").unwrap_err(), MatrixIdError::NoIdentifier.into());
987 assert_eq!(MatrixId::parse_with_type("/").unwrap_err(), MatrixIdError::NoIdentifier.into());
988 }
989
990 #[test]
991 fn parse_matrixid_invalid_parts_number() {
992 assert_eq!(
993 MatrixId::parse_with_type("u/user:imaginary.hs/r/room:imaginary.hs/e").unwrap_err(),
994 MatrixIdError::InvalidPartsNumber.into()
995 );
996 }
997
998 #[test]
999 fn parse_matrixid_unknown_type() {
1000 assert_eq!(
1001 MatrixId::parse_with_type("notatype/fake:notareal.hs").unwrap_err(),
1002 MatrixIdError::UnknownType.into()
1003 );
1004 }
1005
1006 #[test]
1007 fn parse_matrixuri_valid_uris() {
1008 let matrix_uri =
1009 MatrixUri::parse("matrix:u/jplatte:notareal.hs").expect("Failed to create MatrixUri.");
1010 assert_eq!(matrix_uri.id(), &user_id!("@jplatte:notareal.hs").into());
1011 assert_eq!(matrix_uri.action(), None);
1012
1013 let matrix_uri = MatrixUri::parse("matrix:u/jplatte:notareal.hs?action=chat")
1014 .expect("Failed to create MatrixUri.");
1015 assert_eq!(matrix_uri.id(), &user_id!("@jplatte:notareal.hs").into());
1016 assert_eq!(matrix_uri.action(), Some(&UriAction::Chat));
1017
1018 let matrix_uri =
1019 MatrixUri::parse("matrix:r/ruma:notareal.hs").expect("Failed to create MatrixToUri.");
1020 assert_eq!(matrix_uri.id(), &room_alias_id!("#ruma:notareal.hs").into());
1021
1022 let matrix_uri = MatrixUri::parse("matrix:roomid/ruma:notareal.hs?via=notareal.hs")
1023 .expect("Failed to create MatrixToUri.");
1024 assert_eq!(matrix_uri.id(), &room_id!("!ruma:notareal.hs").into());
1025 assert_eq!(matrix_uri.via(), &[server_name!("notareal.hs").to_owned()]);
1026 assert_eq!(matrix_uri.action(), None);
1027
1028 let matrix_uri = MatrixUri::parse("matrix:r/ruma:notareal.hs/e/event:notareal.hs")
1029 .expect("Failed to create MatrixToUri.");
1030 assert_eq!(
1031 matrix_uri.id(),
1032 &(room_alias_id!("#ruma:notareal.hs"), event_id!("$event:notareal.hs")).into()
1033 );
1034
1035 let matrix_uri = MatrixUri::parse("matrix:roomid/ruma:notareal.hs/e/event:notareal.hs")
1036 .expect("Failed to create MatrixToUri.");
1037 assert_eq!(
1038 matrix_uri.id(),
1039 &(room_id!("!ruma:notareal.hs"), event_id!("$event:notareal.hs")).into()
1040 );
1041 assert_eq!(matrix_uri.via().len(), 0);
1042 assert_eq!(matrix_uri.action(), None);
1043
1044 let matrix_uri =
1045 MatrixUri::parse("matrix:roomid/ruma:notareal.hs/e/event:notareal.hs?via=notareal.hs&action=join&via=anotherinexistant.hs")
1046 .expect("Failed to create MatrixToUri.");
1047 assert_eq!(
1048 matrix_uri.id(),
1049 &(room_id!("!ruma:notareal.hs"), event_id!("$event:notareal.hs")).into()
1050 );
1051 assert_eq!(
1052 matrix_uri.via(),
1053 &vec![
1054 server_name!("notareal.hs").to_owned(),
1055 server_name!("anotherinexistant.hs").to_owned()
1056 ]
1057 );
1058 assert_eq!(matrix_uri.action(), Some(&UriAction::Join));
1059 }
1060
1061 #[test]
1062 fn parse_matrixuri_invalid_uri() {
1063 assert_eq!(
1064 MatrixUri::parse("").unwrap_err(),
1065 Error::InvalidMatrixToUri(MatrixToError::InvalidUrl)
1066 );
1067 }
1068
1069 #[test]
1070 fn parse_matrixuri_wrong_scheme() {
1071 assert_eq!(
1072 MatrixUri::parse("unknown:u/user:notareal.hs").unwrap_err(),
1073 MatrixUriError::WrongScheme.into()
1074 );
1075 }
1076
1077 #[test]
1078 fn parse_matrixuri_too_many_actions() {
1079 assert_eq!(
1080 MatrixUri::parse("matrix:u/user:notareal.hs?action=chat&action=join").unwrap_err(),
1081 MatrixUriError::TooManyActions.into()
1082 );
1083 }
1084
1085 #[test]
1086 fn parse_matrixuri_unknown_query_item() {
1087 assert_eq!(
1088 MatrixUri::parse("matrix:roomid/roomid:notareal.hs?via=notareal.hs&fake=data")
1089 .unwrap_err(),
1090 MatrixUriError::UnknownQueryItem.into()
1091 );
1092 }
1093
1094 #[test]
1095 fn parse_matrixuri_wrong_identifier() {
1096 assert_matches!(
1097 MatrixUri::parse("matrix:notanidentifier").unwrap_err(),
1098 Error::InvalidMatrixId(_)
1099 );
1100 assert_matches!(MatrixUri::parse("matrix:").unwrap_err(), Error::InvalidMatrixId(_));
1101 assert_matches!(
1102 MatrixUri::parse("matrix:u/jplatte:notareal.hs/e/event:notareal.hs").unwrap_err(),
1103 Error::InvalidMatrixId(_)
1104 );
1105 }
1106}