identifiers: Create MatrixId type for MatrixToUri

This commit is contained in:
Kévin Commaille 2022-02-18 14:51:42 +01:00 committed by Jonas Platte
parent 52268b5dc2
commit 1c23cd25b5
4 changed files with 138 additions and 42 deletions

View File

@ -4,9 +4,9 @@ use std::fmt;
use percent_encoding::{percent_encode, AsciiSet, CONTROLS}; 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 // Controls + Space + reserved characters from RFC 3986. In practice only the
// reserved characters will be encountered most likely, but better be safe. // reserved characters will be encountered most likely, but better be safe.
// https://datatracker.ietf.org/doc/html/rfc3986/#page-13 // https://datatracker.ietf.org/doc/html/rfc3986/#page-13
@ -30,35 +30,112 @@ const TO_ENCODE: &AsciiSet = &CONTROLS
.add(b';') .add(b';')
.add(b'='); .add(b'=');
/// A reference to a user, room or event. /// All Matrix Identifiers that can be represented as a Matrix URI.
///
/// Turn it into a `matrix.to` URL through its `Display` implementation (i.e. by
/// interpolating it in a formatting macro or via `.to_string()`).
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct MatrixToUri<'a> { #[non_exhaustive]
id: &'a str, pub enum MatrixId {
event_id: Option<&'a EventId>, /// A room ID.
via: Vec<&'a ServerName>, Room(Box<RoomId>),
/// A room alias.
RoomAlias(Box<RoomAliasId>),
/// A user ID.
User(Box<UserId>),
/// An event ID.
Event(Box<RoomOrAliasId>, Box<EventId>),
} }
impl<'a> MatrixToUri<'a> { impl MatrixId {
pub(crate) fn new(id: &'a str, via: Vec<&'a ServerName>) -> Self { /// Construct a string with sigils from `self`.
Self { id, event_id: None, via } ///
/// 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),
),
} }
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> { 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<Box<ServerName>>,
}
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<ServerName>] {
&self.via
}
}
impl fmt::Display for MatrixToUri {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(BASE_URL)?; f.write_str(MATRIX_TO_BASE_URL)?;
write!(f, "{}", percent_encode(self.id.as_bytes(), TO_ENCODE))?; write!(f, "{}", self.id().to_string_with_sigil())?;
if let Some(ev_id) = self.event_id {
write!(f, "/{}", percent_encode(ev_id.as_bytes(), TO_ENCODE))?;
}
let mut first = true; let mut first = true;
for server_name in &self.via { for server_name in &self.via {
@ -74,13 +151,35 @@ impl<'a> fmt::Display for MatrixToUri<'a> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::user_id; use crate::{event_id, room_alias_id, room_id, server_name, user_id};
#[test] #[test]
fn matrix_to_uri() { fn display_matrixtouri() {
assert_eq!( assert_eq!(
user_id!("@jplatte:notareal.hs").matrix_to_url().to_string(), user_id!("@jplatte:notareal.hs").matrix_to_url().to_string(),
"https://matrix.to/#/%40jplatte%3Anotareal.hs" "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"
);
} }
} }

View File

@ -30,13 +30,13 @@ impl RoomAliasId {
} }
/// Create a `matrix.to` reference for this room alias ID. /// Create a `matrix.to` reference for this room alias ID.
pub fn matrix_to_url(&self) -> MatrixToUri<'_> { pub fn matrix_to_url(&self) -> MatrixToUri {
MatrixToUri::new(self.as_str(), Vec::new()) MatrixToUri::new(self.into(), Vec::new())
} }
/// Create a `matrix.to` reference for an event scoped under this room alias ID. /// 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> { pub fn matrix_to_event_url(&self, ev_id: &EventId) -> MatrixToUri {
MatrixToUri::event(self.as_str(), ev_id, Vec::new()) MatrixToUri::new((self, ev_id).into(), Vec::new())
} }
fn colon_idx(&self) -> usize { fn colon_idx(&self) -> usize {

View File

@ -52,16 +52,13 @@ impl RoomId {
/// "https://matrix.to/#/%21somewhere%3Aexample.org?via=example.org&via=alt.example.org" /// "https://matrix.to/#/%21somewhere%3Aexample.org?via=example.org&via=alt.example.org"
/// ); /// );
/// ``` /// ```
pub fn matrix_to_url<'a>( pub fn matrix_to_url<'a>(&self, via: impl IntoIterator<Item = &'a ServerName>) -> MatrixToUri {
&'a self, MatrixToUri::new(self.into(), via.into_iter().collect())
via: impl IntoIterator<Item = &'a ServerName>,
) -> MatrixToUri<'a> {
MatrixToUri::new(self.as_str(), via.into_iter().collect())
} }
/// Create a `matrix.to` reference for an event scoped under this room ID. /// 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> { pub fn matrix_to_event_url(&self, ev_id: &EventId) -> MatrixToUri {
MatrixToUri::event(self.as_str(), ev_id, Vec::new()) MatrixToUri::new((self, ev_id).into(), Vec::new())
} }
fn colon_idx(&self) -> usize { fn colon_idx(&self) -> usize {

View File

@ -116,8 +116,8 @@ impl UserId {
/// display_name = "jplatte", /// display_name = "jplatte",
/// ); /// );
/// ``` /// ```
pub fn matrix_to_url(&self) -> MatrixToUri<'_> { pub fn matrix_to_url(&self) -> MatrixToUri {
MatrixToUri::new(self.as_str(), Vec::new()) MatrixToUri::new(self.into(), Vec::new())
} }
fn colon_idx(&self) -> usize { fn colon_idx(&self) -> usize {