identifiers: Add matrix.to URL formatting

This commit is contained in:
Jonas Platte 2021-09-22 13:49:39 +02:00
parent 8b44f279c8
commit 7cbffe35da
No known key found for this signature in database
GPG Key ID: CC154DE0E30B7C67
6 changed files with 142 additions and 3 deletions

View File

@ -26,6 +26,7 @@ serde = ["ruma-serde", "serde1"]
[dependencies]
either = { version = "1.6.1", optional = true }
paste = "1.0.5"
percent-encoding = "2.1.0"
rand = { version = "0.8.3", optional = true }
ruma-identifiers-macros = { version = "=0.20.0", path = "../ruma-identifiers-macros" }
ruma-identifiers-validation = { version = "0.5.0", path = "../ruma-identifiers-validation", default-features = false }

View File

@ -28,6 +28,7 @@ pub use crate::{
event_id::EventId,
key_id::{DeviceSigningKeyId, KeyId, ServerSigningKeyId, SigningKeyId},
key_name::{KeyName, KeyNameBox},
matrix_to::MatrixToRef,
mxc_uri::MxcUri,
room_alias_id::RoomAliasId,
room_id::RoomId,
@ -54,6 +55,7 @@ mod device_key_id;
mod event_id;
mod key_id;
mod key_name;
mod matrix_to;
mod mxc_uri;
mod room_alias_id;
mod room_id;

View File

@ -0,0 +1,84 @@
use std::fmt;
use percent_encoding::{percent_encode, AsciiSet, CONTROLS};
use crate::{EventId, ServerName};
const 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
const TO_ENCODE: &AsciiSet = &CONTROLS
.add(b':')
.add(b'/')
.add(b'?')
.add(b'#')
.add(b'[')
.add(b']')
.add(b'@')
.add(b'!')
.add(b'$')
.add(b'&')
.add(b'\'')
.add(b'(')
.add(b')')
.add(b'*')
.add(b'+')
.add(b',')
.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()`).
#[derive(Debug, PartialEq, Eq)]
pub struct MatrixToRef<'a> {
id: &'a str,
event_id: Option<&'a EventId>,
via: Vec<&'a ServerName>,
}
impl<'a> MatrixToRef<'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 MatrixToRef<'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))?;
}
let mut first = true;
for server_name in &self.via {
f.write_str(if first { "?via=" } else { "&via=" })?;
f.write_str(server_name.as_str())?;
first = false;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::user_id;
#[test]
fn matrix_to_ref() {
assert_eq!(
user_id!("@jplatte:notareal.hs").matrix_to_url().to_string(),
"https://matrix.to/#/%40jplatte%3Anotareal.hs"
);
}
}

View File

@ -2,7 +2,7 @@
use std::{convert::TryInto, fmt, num::NonZeroU8};
use crate::server_name::ServerName;
use crate::{server_name::ServerName, EventId, MatrixToRef};
/// A Matrix room alias ID.
///
@ -39,6 +39,16 @@ impl RoomAliasId {
pub fn server_name(&self) -> &ServerName {
self.full_id[self.colon_idx.get() as usize + 1..].try_into().unwrap()
}
/// Create a `matrix.to` reference for this room alias ID.
pub fn matrix_to_url(&self) -> MatrixToRef<'_> {
MatrixToRef::new(&self.full_id, 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) -> MatrixToRef<'a> {
MatrixToRef::event(&self.full_id, ev_id, Vec::new())
}
}
/// Attempts to create a new Matrix room alias ID from a string representation.

View File

@ -2,7 +2,7 @@
use std::{convert::TryInto, fmt, num::NonZeroU8};
use crate::ServerName;
use crate::{EventId, MatrixToRef, ServerName};
/// A Matrix room ID.
///
@ -53,6 +53,32 @@ impl RoomId {
pub fn server_name(&self) -> &ServerName {
self.full_id[self.colon_idx.get() as usize + 1..].try_into().unwrap()
}
/// Create a `matrix.to` reference for this room ID.
///
/// # Example
///
/// ```
/// use ruma_identifiers::{room_id, server_name};
///
/// assert_eq!(
/// room_id!("!somewhere:example.org")
/// .matrix_to_url([&*server_name!("example.org"), &*server_name!("alt.example.org")])
/// .to_string(),
/// "https://matrix.to/#/%21somewhere%3Aexample.org?via=example.org&via=alt.example.org"
/// );
/// ```
pub fn matrix_to_url<'a>(
&'a self,
via: impl IntoIterator<Item = &'a ServerName>,
) -> MatrixToRef<'a> {
MatrixToRef::new(&self.full_id, 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) -> MatrixToRef<'a> {
MatrixToRef::event(&self.full_id, ev_id, Vec::new())
}
}
/// Attempts to create a new Matrix room ID from a string representation.

View File

@ -2,7 +2,7 @@
use std::{convert::TryInto, fmt, num::NonZeroU8};
use crate::ServerName;
use crate::{MatrixToRef, ServerName};
/// A Matrix user ID.
///
@ -92,6 +92,22 @@ impl UserId {
pub fn is_historical(&self) -> bool {
self.is_historical
}
/// Create a `matrix.to` reference for this user ID.
///
/// # Example
///
/// ```
/// use ruma_identifiers::user_id;
///
/// let message = format!(
/// r#"Thanks for the update <a href="{link}">{display_name}</a>."#,
/// link = user_id!("@jplatte:notareal.hs").matrix_to_url(), display_name = "jplatte",
/// );
/// ```
pub fn matrix_to_url(&self) -> MatrixToRef<'_> {
MatrixToRef::new(&self.full_id, Vec::new())
}
}
/// Attempts to create a new Matrix user ID from a string representation.