From 1c23cd25b5a7b5360db48c9105a2264ca91a61da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Fri, 18 Feb 2022 14:51:42 +0100 Subject: [PATCH] identifiers: Create MatrixId type for MatrixToUri --- crates/ruma-identifiers/src/matrix_uri.rs | 157 +++++++++++++++---- crates/ruma-identifiers/src/room_alias_id.rs | 8 +- crates/ruma-identifiers/src/room_id.rs | 11 +- crates/ruma-identifiers/src/user_id.rs | 4 +- 4 files changed, 138 insertions(+), 42 deletions(-) diff --git a/crates/ruma-identifiers/src/matrix_uri.rs b/crates/ruma-identifiers/src/matrix_uri.rs index e95d8fbf..ec9a383f 100644 --- a/crates/ruma-identifiers/src/matrix_uri.rs +++ b/crates/ruma-identifiers/src/matrix_uri.rs @@ -4,9 +4,9 @@ use std::fmt; use percent_encoding::{percent_encode, AsciiSet, CONTROLS}; -use crate::{EventId, ServerName}; +use crate::{EventId, RoomAliasId, RoomId, RoomOrAliasId, ServerName, UserId}; -const BASE_URL: &str = "https://matrix.to/#/"; +const MATRIX_TO_BASE_URL: &str = "https://matrix.to/#/"; // Controls + Space + reserved characters from RFC 3986. In practice only the // reserved characters will be encountered most likely, but better be safe. // https://datatracker.ietf.org/doc/html/rfc3986/#page-13 @@ -30,35 +30,112 @@ const TO_ENCODE: &AsciiSet = &CONTROLS .add(b';') .add(b'='); -/// A reference to a user, room or event. -/// -/// Turn it into a `matrix.to` URL through its `Display` implementation (i.e. by -/// interpolating it in a formatting macro or via `.to_string()`). +/// All Matrix Identifiers that can be represented as a Matrix URI. #[derive(Debug, PartialEq, Eq)] -pub struct MatrixToUri<'a> { - id: &'a str, - event_id: Option<&'a EventId>, - via: Vec<&'a ServerName>, +#[non_exhaustive] +pub enum MatrixId { + /// A room ID. + Room(Box), + + /// A room alias. + RoomAlias(Box), + + /// A user ID. + User(Box), + + /// An event ID. + Event(Box, Box), } -impl<'a> MatrixToUri<'a> { - pub(crate) fn new(id: &'a str, via: Vec<&'a ServerName>) -> Self { - Self { id, event_id: None, via } - } - - pub(crate) fn event(id: &'a str, event_id: &'a EventId, via: Vec<&'a ServerName>) -> Self { - Self { id, event_id: Some(event_id), via } - } -} - -impl<'a> fmt::Display for MatrixToUri<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(BASE_URL)?; - write!(f, "{}", percent_encode(self.id.as_bytes(), TO_ENCODE))?; - - if let Some(ev_id) = self.event_id { - write!(f, "/{}", percent_encode(ev_id.as_bytes(), TO_ENCODE))?; +impl MatrixId { + /// Construct a string with sigils from `self`. + /// + /// The identifiers will start with a sigil and be percent encoded. + /// + /// For events, the room ID or alias and the event ID will be separated by + /// a slash. + pub(crate) fn to_string_with_sigil(&self) -> String { + match self { + Self::Room(room_id) => percent_encode(room_id.as_bytes(), TO_ENCODE).to_string(), + Self::RoomAlias(room_alias) => { + percent_encode(room_alias.as_bytes(), TO_ENCODE).to_string() + } + Self::User(user_id) => percent_encode(user_id.as_bytes(), TO_ENCODE).to_string(), + Self::Event(room_id, event_id) => format!( + "{}/{}", + percent_encode(room_id.as_bytes(), TO_ENCODE), + percent_encode(event_id.as_bytes(), TO_ENCODE), + ), } + } +} + +impl From<&RoomId> for MatrixId { + fn from(room_id: &RoomId) -> Self { + Self::Room(room_id.into()) + } +} + +impl From<&RoomAliasId> for MatrixId { + fn from(room_alias: &RoomAliasId) -> Self { + Self::RoomAlias(room_alias.into()) + } +} + +impl From<&UserId> for MatrixId { + fn from(user_id: &UserId) -> Self { + Self::User(user_id.into()) + } +} + +impl From<(&RoomOrAliasId, &EventId)> for MatrixId { + fn from(ids: (&RoomOrAliasId, &EventId)) -> Self { + Self::Event(ids.0.into(), ids.1.into()) + } +} + +impl From<(&RoomId, &EventId)> for MatrixId { + fn from(ids: (&RoomId, &EventId)) -> Self { + Self::Event(<&RoomOrAliasId>::from(ids.0).into(), ids.1.into()) + } +} + +impl From<(&RoomAliasId, &EventId)> for MatrixId { + fn from(ids: (&RoomAliasId, &EventId)) -> Self { + Self::Event(<&RoomOrAliasId>::from(ids.0).into(), ids.1.into()) + } +} + +/// The `matrix.to` URI representation of a user, room or event. +/// +/// Get the URI through its `Display` implementation (i.e. by interpolating it +/// in a formatting macro or via `.to_string()`). +#[derive(Debug, PartialEq, Eq)] +pub struct MatrixToUri { + id: MatrixId, + via: Vec>, +} + +impl MatrixToUri { + pub(crate) fn new(id: MatrixId, via: Vec<&ServerName>) -> Self { + Self { id, via: via.into_iter().map(ToOwned::to_owned).collect() } + } + + /// The identifier represented by this `matrix.to` URI. + pub fn id(&self) -> &MatrixId { + &self.id + } + + /// Matrix servers usable to route a `RoomId`. + pub fn via(&self) -> &[Box] { + &self.via + } +} + +impl fmt::Display for MatrixToUri { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(MATRIX_TO_BASE_URL)?; + write!(f, "{}", self.id().to_string_with_sigil())?; let mut first = true; for server_name in &self.via { @@ -74,13 +151,35 @@ impl<'a> fmt::Display for MatrixToUri<'a> { #[cfg(test)] mod tests { - use crate::user_id; + use crate::{event_id, room_alias_id, room_id, server_name, user_id}; #[test] - fn matrix_to_uri() { + fn display_matrixtouri() { assert_eq!( user_id!("@jplatte:notareal.hs").matrix_to_url().to_string(), "https://matrix.to/#/%40jplatte%3Anotareal.hs" ); + assert_eq!( + room_alias_id!("#ruma:notareal.hs").matrix_to_url().to_string(), + "https://matrix.to/#/%23ruma%3Anotareal.hs" + ); + assert_eq!( + room_id!("!ruma:notareal.hs") + .matrix_to_url(vec![server_name!("notareal.hs")]) + .to_string(), + "https://matrix.to/#/%21ruma%3Anotareal.hs?via=notareal.hs" + ); + assert_eq!( + room_alias_id!("#ruma:notareal.hs") + .matrix_to_event_url(event_id!("$event:notareal.hs")) + .to_string(), + "https://matrix.to/#/%23ruma%3Anotareal.hs/%24event%3Anotareal.hs" + ); + assert_eq!( + room_id!("!ruma:notareal.hs") + .matrix_to_event_url(event_id!("$event:notareal.hs")) + .to_string(), + "https://matrix.to/#/%21ruma%3Anotareal.hs/%24event%3Anotareal.hs" + ); } } diff --git a/crates/ruma-identifiers/src/room_alias_id.rs b/crates/ruma-identifiers/src/room_alias_id.rs index a0764e13..763a9af4 100644 --- a/crates/ruma-identifiers/src/room_alias_id.rs +++ b/crates/ruma-identifiers/src/room_alias_id.rs @@ -30,13 +30,13 @@ impl RoomAliasId { } /// Create a `matrix.to` reference for this room alias ID. - pub fn matrix_to_url(&self) -> MatrixToUri<'_> { - MatrixToUri::new(self.as_str(), Vec::new()) + pub fn matrix_to_url(&self) -> MatrixToUri { + MatrixToUri::new(self.into(), Vec::new()) } /// Create a `matrix.to` reference for an event scoped under this room alias ID. - pub fn matrix_to_event_url<'a>(&'a self, ev_id: &'a EventId) -> MatrixToUri<'a> { - MatrixToUri::event(self.as_str(), ev_id, Vec::new()) + pub fn matrix_to_event_url(&self, ev_id: &EventId) -> MatrixToUri { + MatrixToUri::new((self, ev_id).into(), Vec::new()) } fn colon_idx(&self) -> usize { diff --git a/crates/ruma-identifiers/src/room_id.rs b/crates/ruma-identifiers/src/room_id.rs index 7a71fb00..a53bd0c7 100644 --- a/crates/ruma-identifiers/src/room_id.rs +++ b/crates/ruma-identifiers/src/room_id.rs @@ -52,16 +52,13 @@ impl RoomId { /// "https://matrix.to/#/%21somewhere%3Aexample.org?via=example.org&via=alt.example.org" /// ); /// ``` - pub fn matrix_to_url<'a>( - &'a self, - via: impl IntoIterator, - ) -> MatrixToUri<'a> { - MatrixToUri::new(self.as_str(), via.into_iter().collect()) + pub fn matrix_to_url<'a>(&self, via: impl IntoIterator) -> MatrixToUri { + MatrixToUri::new(self.into(), via.into_iter().collect()) } /// Create a `matrix.to` reference for an event scoped under this room ID. - pub fn matrix_to_event_url<'a>(&'a self, ev_id: &'a EventId) -> MatrixToUri<'a> { - MatrixToUri::event(self.as_str(), ev_id, Vec::new()) + pub fn matrix_to_event_url(&self, ev_id: &EventId) -> MatrixToUri { + MatrixToUri::new((self, ev_id).into(), Vec::new()) } fn colon_idx(&self) -> usize { diff --git a/crates/ruma-identifiers/src/user_id.rs b/crates/ruma-identifiers/src/user_id.rs index a6f62dc1..e07c1d7f 100644 --- a/crates/ruma-identifiers/src/user_id.rs +++ b/crates/ruma-identifiers/src/user_id.rs @@ -116,8 +116,8 @@ impl UserId { /// display_name = "jplatte", /// ); /// ``` - pub fn matrix_to_url(&self) -> MatrixToUri<'_> { - MatrixToUri::new(self.as_str(), Vec::new()) + pub fn matrix_to_url(&self) -> MatrixToUri { + MatrixToUri::new(self.into(), Vec::new()) } fn colon_idx(&self) -> usize {