identifiers: Add MatrixUri for matrix scheme URIs
This commit is contained in:
parent
02008ac6e9
commit
be376f5bd6
@ -40,6 +40,10 @@ pub enum Error {
|
|||||||
#[error("invalid matrix.to URI: {0}")]
|
#[error("invalid matrix.to URI: {0}")]
|
||||||
InvalidMatrixToRef(#[from] MatrixToError),
|
InvalidMatrixToRef(#[from] MatrixToError),
|
||||||
|
|
||||||
|
/// The string isn't a valid Matrix URI.
|
||||||
|
#[error("invalid matrix URI: {0}")]
|
||||||
|
InvalidMatrixUri(#[from] MatrixUriError),
|
||||||
|
|
||||||
/// The mxc:// isn't a valid Matrix Content URI.
|
/// The mxc:// isn't a valid Matrix Content URI.
|
||||||
#[error("invalid Matrix Content URI: {0}")]
|
#[error("invalid Matrix Content URI: {0}")]
|
||||||
InvalidMxcUri(#[from] MxcUriError),
|
InvalidMxcUri(#[from] MxcUriError),
|
||||||
@ -110,6 +114,10 @@ pub enum MxcUriError {
|
|||||||
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, thiserror::Error)]
|
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, thiserror::Error)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum MatrixIdError {
|
pub enum MatrixIdError {
|
||||||
|
/// The string contains an invalid number of parts.
|
||||||
|
#[error("invalid number of parts")]
|
||||||
|
InvalidPartsNumber,
|
||||||
|
|
||||||
/// The string is missing a room ID or alias.
|
/// The string is missing a room ID or alias.
|
||||||
#[error("missing room ID or alias")]
|
#[error("missing room ID or alias")]
|
||||||
MissingRoom,
|
MissingRoom,
|
||||||
@ -129,6 +137,10 @@ pub enum MatrixIdError {
|
|||||||
/// The string contains two identifiers that cannot be paired.
|
/// The string contains two identifiers that cannot be paired.
|
||||||
#[error("unknown identifier pair")]
|
#[error("unknown identifier pair")]
|
||||||
UnknownIdentifierPair,
|
UnknownIdentifierPair,
|
||||||
|
|
||||||
|
/// The string contains an unknown identifier type.
|
||||||
|
#[error("unknown identifier type")]
|
||||||
|
UnknownType,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error occurred while validating a `matrix.to` URI.
|
/// An error occurred while validating a `matrix.to` URI.
|
||||||
@ -143,3 +155,20 @@ pub enum MatrixToError {
|
|||||||
#[error("unknown additional argument")]
|
#[error("unknown additional argument")]
|
||||||
UnknownArgument,
|
UnknownArgument,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An error occurred while validating a `MatrixURI`.
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, thiserror::Error)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum MatrixUriError {
|
||||||
|
/// The string does not start with `matrix:`.
|
||||||
|
#[error("scheme is not 'matrix:'")]
|
||||||
|
WrongScheme,
|
||||||
|
|
||||||
|
/// The string contains too many actions.
|
||||||
|
#[error("too many actions")]
|
||||||
|
TooManyActions,
|
||||||
|
|
||||||
|
/// The string contains an unknown query item.
|
||||||
|
#[error("unknown query item")]
|
||||||
|
UnknownQueryItem,
|
||||||
|
}
|
||||||
|
@ -4,6 +4,10 @@ Breaking changes:
|
|||||||
|
|
||||||
* Rename `matrix_to_(event_)url` methods to `matrix_to_(event_)uri`
|
* Rename `matrix_to_(event_)url` methods to `matrix_to_(event_)uri`
|
||||||
|
|
||||||
|
Improvements:
|
||||||
|
|
||||||
|
* Add `MatrixUri` to build `matrix:` URIs
|
||||||
|
|
||||||
# 0.21.0
|
# 0.21.0
|
||||||
|
|
||||||
Breaking changes:
|
Breaking changes:
|
||||||
|
@ -32,7 +32,7 @@ pub use crate::{
|
|||||||
event_id::EventId,
|
event_id::EventId,
|
||||||
key_id::{DeviceSigningKeyId, KeyId, ServerSigningKeyId, SigningKeyId},
|
key_id::{DeviceSigningKeyId, KeyId, ServerSigningKeyId, SigningKeyId},
|
||||||
key_name::KeyName,
|
key_name::KeyName,
|
||||||
matrix_uri::MatrixToUri,
|
matrix_uri::{MatrixToUri, MatrixUri},
|
||||||
mxc_uri::MxcUri,
|
mxc_uri::MxcUri,
|
||||||
room_alias_id::RoomAliasId,
|
room_alias_id::RoomAliasId,
|
||||||
room_id::RoomId,
|
room_id::RoomId,
|
||||||
|
@ -4,18 +4,23 @@ use std::{convert::TryFrom, fmt};
|
|||||||
|
|
||||||
use percent_encoding::{percent_decode_str, percent_encode, AsciiSet, CONTROLS};
|
use percent_encoding::{percent_decode_str, percent_encode, AsciiSet, CONTROLS};
|
||||||
use ruma_identifiers_validation::{
|
use ruma_identifiers_validation::{
|
||||||
error::{MatrixIdError, MatrixToError},
|
error::{MatrixIdError, MatrixToError, MatrixUriError},
|
||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{EventId, RoomAliasId, RoomId, RoomOrAliasId, ServerName, UserId};
|
use crate::{EventId, PrivOwnedStr, RoomAliasId, RoomId, RoomOrAliasId, ServerName, UserId};
|
||||||
|
|
||||||
const MATRIX_TO_BASE_URL: &str = "https://matrix.to/#/";
|
const MATRIX_TO_BASE_URL: &str = "https://matrix.to/#/";
|
||||||
|
const MATRIX_SCHEME: &str = "matrix";
|
||||||
|
// Controls + Space + non-path characters from RFC 3986. In practice only the
|
||||||
|
// non-path characters will be encountered most likely, but better be safe.
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc3986/#page-23
|
||||||
|
const NON_PATH: &AsciiSet = &CONTROLS.add(b'/').add(b'?').add(b'#').add(b'[').add(b']');
|
||||||
// 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
|
||||||
const TO_ENCODE: &AsciiSet = &CONTROLS
|
const RESERVED: &AsciiSet = &CONTROLS
|
||||||
.add(b':')
|
.add(b':')
|
||||||
.add(b'/')
|
.add(b'/')
|
||||||
.add(b'?')
|
.add(b'?')
|
||||||
@ -101,6 +106,41 @@ impl MatrixId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Try parsing a `&str` with types into a `MatrixId`.
|
||||||
|
///
|
||||||
|
/// The identifiers are expected to be in the format
|
||||||
|
/// `type/identifier_without_sigil` and the identifier part is expected to
|
||||||
|
/// be percent encoded. Slashes at the beginning and the end are stripped.
|
||||||
|
///
|
||||||
|
/// For events, the room ID or alias and the event ID should be separated by
|
||||||
|
/// a slash and they can be in any order.
|
||||||
|
pub(crate) fn parse_with_type(s: &str) -> Result<Self, Error> {
|
||||||
|
let s = if let Some(stripped) = s.strip_prefix('/') { stripped } else { s };
|
||||||
|
let s = if let Some(stripped) = s.strip_suffix('/') { stripped } else { s };
|
||||||
|
if s.is_empty() {
|
||||||
|
return Err(MatrixIdError::NoIdentifier.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ![1, 3].contains(&s.matches('/').count()) {
|
||||||
|
return Err(MatrixIdError::InvalidPartsNumber.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut id = String::new();
|
||||||
|
let mut split = s.split('/');
|
||||||
|
while let (Some(type_), Some(id_without_sigil)) = (split.next(), split.next()) {
|
||||||
|
let sigil = match type_ {
|
||||||
|
"u" | "user" => '@',
|
||||||
|
"r" | "room" => '#',
|
||||||
|
"e" | "event" => '$',
|
||||||
|
"roomid" => '!',
|
||||||
|
_ => return Err(MatrixIdError::UnknownType.into()),
|
||||||
|
};
|
||||||
|
id = format!("{}/{}{}", id, sigil, id_without_sigil);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::parse_with_sigil(&id)
|
||||||
|
}
|
||||||
|
|
||||||
/// Construct a string with sigils from `self`.
|
/// Construct a string with sigils from `self`.
|
||||||
///
|
///
|
||||||
/// The identifiers will start with a sigil and be percent encoded.
|
/// The identifiers will start with a sigil and be percent encoded.
|
||||||
@ -109,18 +149,48 @@ impl MatrixId {
|
|||||||
/// a slash.
|
/// a slash.
|
||||||
pub(crate) fn to_string_with_sigil(&self) -> String {
|
pub(crate) fn to_string_with_sigil(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Self::Room(room_id) => percent_encode(room_id.as_bytes(), TO_ENCODE).to_string(),
|
Self::Room(room_id) => percent_encode(room_id.as_bytes(), RESERVED).to_string(),
|
||||||
Self::RoomAlias(room_alias) => {
|
Self::RoomAlias(room_alias) => {
|
||||||
percent_encode(room_alias.as_bytes(), TO_ENCODE).to_string()
|
percent_encode(room_alias.as_bytes(), RESERVED).to_string()
|
||||||
}
|
}
|
||||||
Self::User(user_id) => percent_encode(user_id.as_bytes(), TO_ENCODE).to_string(),
|
Self::User(user_id) => percent_encode(user_id.as_bytes(), RESERVED).to_string(),
|
||||||
Self::Event(room_id, event_id) => format!(
|
Self::Event(room_id, event_id) => format!(
|
||||||
"{}/{}",
|
"{}/{}",
|
||||||
percent_encode(room_id.as_bytes(), TO_ENCODE),
|
percent_encode(room_id.as_bytes(), RESERVED),
|
||||||
percent_encode(event_id.as_bytes(), TO_ENCODE),
|
percent_encode(event_id.as_bytes(), RESERVED),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Construct a string with types from `self`.
|
||||||
|
///
|
||||||
|
/// The identifiers will be in the format `type/identifier_without_sigil`
|
||||||
|
/// and the identifier part will 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_type(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Room(room_id) => {
|
||||||
|
format!("roomid/{}", percent_encode(&room_id.as_bytes()[1..], NON_PATH))
|
||||||
|
}
|
||||||
|
Self::RoomAlias(room_alias) => {
|
||||||
|
format!("r/{}", percent_encode(&room_alias.as_bytes()[1..], NON_PATH))
|
||||||
|
}
|
||||||
|
Self::User(user_id) => {
|
||||||
|
format!("u/{}", percent_encode(&user_id.as_bytes()[1..], NON_PATH))
|
||||||
|
}
|
||||||
|
Self::Event(room_id, event_id) => {
|
||||||
|
let room_type = if room_id.is_room_id() { "roomid" } else { "r" };
|
||||||
|
format!(
|
||||||
|
"{}/{}/e/{}",
|
||||||
|
room_type,
|
||||||
|
percent_encode(&room_id.as_bytes()[1..], NON_PATH),
|
||||||
|
percent_encode(&event_id.as_bytes()[1..], NON_PATH),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&RoomId> for MatrixId {
|
impl From<&RoomId> for MatrixId {
|
||||||
@ -236,16 +306,187 @@ impl TryFrom<&str> for MatrixToUri {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The intent of a Matrix URI.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum UriAction {
|
||||||
|
/// Join the room referenced by the URI.
|
||||||
|
///
|
||||||
|
/// The client should prompt for confirmation prior to joining the room, if
|
||||||
|
/// the user isn’t already part of the room.
|
||||||
|
Join,
|
||||||
|
|
||||||
|
/// Start a direct chat with the user referenced by the URI.
|
||||||
|
///
|
||||||
|
/// Clients supporting a form of Canonical DMs should reuse existing DMs
|
||||||
|
/// instead of creating new ones if available. The client should prompt for
|
||||||
|
/// confirmation prior to creating the DM, if the user isn’t being
|
||||||
|
/// redirected to an existing canonical DM.
|
||||||
|
Chat,
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
_Custom(PrivOwnedStr),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UriAction {
|
||||||
|
/// Creates a string slice from this `UriAction`.
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
self.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from<T>(s: T) -> Self
|
||||||
|
where
|
||||||
|
T: AsRef<str> + Into<Box<str>>,
|
||||||
|
{
|
||||||
|
match s.as_ref() {
|
||||||
|
"join" => UriAction::Join,
|
||||||
|
"chat" => UriAction::Chat,
|
||||||
|
_ => UriAction::_Custom(PrivOwnedStr(s.into())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for UriAction {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
UriAction::Join => "join",
|
||||||
|
UriAction::Chat => "chat",
|
||||||
|
UriAction::_Custom(s) => s.0.as_ref(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for UriAction {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.as_ref())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for UriAction {
|
||||||
|
fn from(s: &str) -> Self {
|
||||||
|
Self::from(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for UriAction {
|
||||||
|
fn from(s: String) -> Self {
|
||||||
|
Self::from(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Box<str>> for UriAction {
|
||||||
|
fn from(s: Box<str>) -> Self {
|
||||||
|
Self::from(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The [`matrix:` 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()`).
|
||||||
|
///
|
||||||
|
/// [`matrix:` URI]: https://spec.matrix.org/v1.2/appendices/#matrix-uri-scheme
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct MatrixUri {
|
||||||
|
id: MatrixId,
|
||||||
|
via: Vec<Box<ServerName>>,
|
||||||
|
action: Option<UriAction>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MatrixUri {
|
||||||
|
pub(crate) fn new(id: MatrixId, via: Vec<&ServerName>, action: Option<UriAction>) -> Self {
|
||||||
|
Self { id, via: via.into_iter().map(ToOwned::to_owned).collect(), action }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The identifier represented by this `matrix:` URI.
|
||||||
|
pub fn id(&self) -> &MatrixId {
|
||||||
|
&self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Matrix servers usable to route a `RoomId`.
|
||||||
|
pub fn via(&self) -> &[Box<ServerName>] {
|
||||||
|
&self.via
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The intent of this URI.
|
||||||
|
pub fn action(&self) -> Option<&UriAction> {
|
||||||
|
self.action.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try parsing a `&str` into a `MatrixUri`.
|
||||||
|
pub fn parse(s: &str) -> Result<Self, Error> {
|
||||||
|
let url = Url::parse(s)?;
|
||||||
|
|
||||||
|
if url.scheme() != MATRIX_SCHEME {
|
||||||
|
return Err(MatrixUriError::WrongScheme.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = MatrixId::parse_with_type(url.path())?;
|
||||||
|
|
||||||
|
let mut via = vec![];
|
||||||
|
let mut action = None;
|
||||||
|
|
||||||
|
for (key, value) in url.query_pairs() {
|
||||||
|
if key.as_ref() == "via" {
|
||||||
|
via.push(ServerName::parse(value)?);
|
||||||
|
} else if key.as_ref() == "action" {
|
||||||
|
if action.is_some() {
|
||||||
|
return Err(MatrixUriError::TooManyActions.into());
|
||||||
|
};
|
||||||
|
|
||||||
|
action = Some(value.as_ref().into());
|
||||||
|
} else {
|
||||||
|
return Err(MatrixUriError::UnknownQueryItem.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { id, via, action })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for MatrixUri {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}:{}", MATRIX_SCHEME, self.id().to_string_with_type())?;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(action) = self.action() {
|
||||||
|
f.write_str(if first { "?action=" } else { "&action=" })?;
|
||||||
|
f.write_str(action.as_str())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for MatrixUri {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(s: &str) -> Result<Self, Self::Error> {
|
||||||
|
Self::parse(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use matches::assert_matches;
|
use matches::assert_matches;
|
||||||
use ruma_identifiers_validation::{
|
use ruma_identifiers_validation::{
|
||||||
error::{MatrixIdError, MatrixToError},
|
error::{MatrixIdError, MatrixToError, MatrixUriError},
|
||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{MatrixId, MatrixToUri};
|
use super::{MatrixId, MatrixToUri, MatrixUri};
|
||||||
use crate::{event_id, room_alias_id, room_id, server_name, user_id, RoomOrAliasId};
|
use crate::{
|
||||||
|
event_id, matrix_uri::UriAction, room_alias_id, room_id, server_name, user_id,
|
||||||
|
RoomOrAliasId,
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn display_matrixtouri() {
|
fn display_matrixtouri() {
|
||||||
@ -476,4 +717,257 @@ mod tests {
|
|||||||
MatrixToError::UnknownArgument.into()
|
MatrixToError::UnknownArgument.into()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn display_matrixuri() {
|
||||||
|
assert_eq!(
|
||||||
|
user_id!("@jplatte:notareal.hs").matrix_uri(false).to_string(),
|
||||||
|
"matrix:u/jplatte:notareal.hs"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
user_id!("@jplatte:notareal.hs").matrix_uri(true).to_string(),
|
||||||
|
"matrix:u/jplatte:notareal.hs?action=chat"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
room_alias_id!("#ruma:notareal.hs").matrix_uri(false).to_string(),
|
||||||
|
"matrix:r/ruma:notareal.hs"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
room_alias_id!("#ruma:notareal.hs").matrix_uri(true).to_string(),
|
||||||
|
"matrix:r/ruma:notareal.hs?action=join"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
room_id!("!ruma:notareal.hs")
|
||||||
|
.matrix_uri(vec![server_name!("notareal.hs")], false)
|
||||||
|
.to_string(),
|
||||||
|
"matrix:roomid/ruma:notareal.hs?via=notareal.hs"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
room_id!("!ruma:notareal.hs")
|
||||||
|
.matrix_uri(
|
||||||
|
vec![server_name!("notareal.hs"), server_name!("anotherunreal.hs")],
|
||||||
|
true
|
||||||
|
)
|
||||||
|
.to_string(),
|
||||||
|
"matrix:roomid/ruma:notareal.hs?via=notareal.hs&via=anotherunreal.hs&action=join"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
room_alias_id!("#ruma:notareal.hs")
|
||||||
|
.matrix_event_uri(event_id!("$event:notareal.hs"))
|
||||||
|
.to_string(),
|
||||||
|
"matrix:r/ruma:notareal.hs/e/event:notareal.hs"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
room_id!("!ruma:notareal.hs")
|
||||||
|
.matrix_event_uri(event_id!("$event:notareal.hs"), vec![])
|
||||||
|
.to_string(),
|
||||||
|
"matrix:roomid/ruma:notareal.hs/e/event:notareal.hs"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_valid_matrixid_with_type() {
|
||||||
|
assert_eq!(
|
||||||
|
MatrixId::parse_with_type("u/user:imaginary.hs").expect("Failed to create MatrixId."),
|
||||||
|
MatrixId::User(user_id!("@user:imaginary.hs").into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
MatrixId::parse_with_type("user/user:imaginary.hs")
|
||||||
|
.expect("Failed to create MatrixId."),
|
||||||
|
MatrixId::User(user_id!("@user:imaginary.hs").into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
MatrixId::parse_with_type("roomid/roomid:imaginary.hs")
|
||||||
|
.expect("Failed to create MatrixId."),
|
||||||
|
MatrixId::Room(room_id!("!roomid:imaginary.hs").into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
MatrixId::parse_with_type("r/roomalias:imaginary.hs")
|
||||||
|
.expect("Failed to create MatrixId."),
|
||||||
|
MatrixId::RoomAlias(room_alias_id!("#roomalias:imaginary.hs").into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
MatrixId::parse_with_type("room/roomalias:imaginary.hs")
|
||||||
|
.expect("Failed to create MatrixId."),
|
||||||
|
MatrixId::RoomAlias(room_alias_id!("#roomalias:imaginary.hs").into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
MatrixId::parse_with_type("roomid/roomid:imaginary.hs/e/event:imaginary.hs")
|
||||||
|
.expect("Failed to create MatrixId."),
|
||||||
|
MatrixId::Event(
|
||||||
|
<&RoomOrAliasId>::from(room_id!("!roomid:imaginary.hs")).into(),
|
||||||
|
event_id!("$event:imaginary.hs").into()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
MatrixId::parse_with_type("r/roomalias:imaginary.hs/e/event:imaginary.hs")
|
||||||
|
.expect("Failed to create MatrixId."),
|
||||||
|
MatrixId::Event(
|
||||||
|
<&RoomOrAliasId>::from(room_alias_id!("#roomalias:imaginary.hs")).into(),
|
||||||
|
event_id!("$event:imaginary.hs").into()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
MatrixId::parse_with_type("room/roomalias:imaginary.hs/event/event:imaginary.hs")
|
||||||
|
.expect("Failed to create MatrixId."),
|
||||||
|
MatrixId::Event(
|
||||||
|
<&RoomOrAliasId>::from(room_alias_id!("#roomalias:imaginary.hs")).into(),
|
||||||
|
event_id!("$event:imaginary.hs").into()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// Invert the order of the event and the room.
|
||||||
|
assert_eq!(
|
||||||
|
MatrixId::parse_with_type("e/event:imaginary.hs/roomid/roomid:imaginary.hs")
|
||||||
|
.expect("Failed to create MatrixId."),
|
||||||
|
MatrixId::Event(
|
||||||
|
<&RoomOrAliasId>::from(room_id!("!roomid:imaginary.hs")).into(),
|
||||||
|
event_id!("$event:imaginary.hs").into()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
MatrixId::parse_with_type("e/event:imaginary.hs/r/roomalias:imaginary.hs")
|
||||||
|
.expect("Failed to create MatrixId."),
|
||||||
|
MatrixId::Event(
|
||||||
|
<&RoomOrAliasId>::from(room_alias_id!("#roomalias:imaginary.hs")).into(),
|
||||||
|
event_id!("$event:imaginary.hs").into()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// Starting with a slash
|
||||||
|
assert_eq!(
|
||||||
|
MatrixId::parse_with_type("/u/user:imaginary.hs").expect("Failed to create MatrixId."),
|
||||||
|
MatrixId::User(user_id!("@user:imaginary.hs").into())
|
||||||
|
);
|
||||||
|
// Ending with a slash
|
||||||
|
assert_eq!(
|
||||||
|
MatrixId::parse_with_type("roomid/roomid:imaginary.hs/")
|
||||||
|
.expect("Failed to create MatrixId."),
|
||||||
|
MatrixId::Room(room_id!("!roomid:imaginary.hs").into())
|
||||||
|
);
|
||||||
|
// Starting and ending with a slash
|
||||||
|
assert_eq!(
|
||||||
|
MatrixId::parse_with_type("/r/roomalias:imaginary.hs/")
|
||||||
|
.expect("Failed to create MatrixId."),
|
||||||
|
MatrixId::RoomAlias(room_alias_id!("#roomalias:imaginary.hs").into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_matrixid_type_no_identifier() {
|
||||||
|
assert_eq!(MatrixId::parse_with_type("").unwrap_err(), MatrixIdError::NoIdentifier.into());
|
||||||
|
assert_eq!(MatrixId::parse_with_type("/").unwrap_err(), MatrixIdError::NoIdentifier.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_matrixid_invalid_parts_number() {
|
||||||
|
assert_eq!(
|
||||||
|
MatrixId::parse_with_type("u/user:imaginary.hs/r/room:imaginary.hs/e").unwrap_err(),
|
||||||
|
MatrixIdError::InvalidPartsNumber.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_matrixid_unknown_type() {
|
||||||
|
assert_eq!(
|
||||||
|
MatrixId::parse_with_type("notatype/fake:notareal.hs").unwrap_err(),
|
||||||
|
MatrixIdError::UnknownType.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_matrixuri_valid_uris() {
|
||||||
|
let matrix_uri =
|
||||||
|
MatrixUri::parse("matrix:u/jplatte:notareal.hs").expect("Failed to create MatrixUri.");
|
||||||
|
assert_eq!(matrix_uri.id(), &user_id!("@jplatte:notareal.hs").into());
|
||||||
|
assert!(matrix_uri.action().is_none());
|
||||||
|
|
||||||
|
let matrix_uri = MatrixUri::parse("matrix:u/jplatte:notareal.hs?action=chat")
|
||||||
|
.expect("Failed to create MatrixUri.");
|
||||||
|
assert_eq!(matrix_uri.id(), &user_id!("@jplatte:notareal.hs").into());
|
||||||
|
assert_eq!(matrix_uri.action(), Some(&UriAction::Chat));
|
||||||
|
|
||||||
|
let matrix_uri =
|
||||||
|
MatrixUri::parse("matrix:r/ruma:notareal.hs").expect("Failed to create MatrixToUri.");
|
||||||
|
assert_eq!(matrix_uri.id(), &room_alias_id!("#ruma:notareal.hs").into());
|
||||||
|
|
||||||
|
let matrix_uri = MatrixUri::parse("matrix:roomid/ruma:notareal.hs?via=notareal.hs")
|
||||||
|
.expect("Failed to create MatrixToUri.");
|
||||||
|
assert_eq!(matrix_uri.id(), &room_id!("!ruma:notareal.hs").into());
|
||||||
|
assert_eq!(matrix_uri.via(), &vec![server_name!("notareal.hs").to_owned()]);
|
||||||
|
assert!(matrix_uri.action().is_none());
|
||||||
|
|
||||||
|
let matrix_uri = MatrixUri::parse("matrix:r/ruma:notareal.hs/e/event:notareal.hs")
|
||||||
|
.expect("Failed to create MatrixToUri.");
|
||||||
|
assert_eq!(
|
||||||
|
matrix_uri.id(),
|
||||||
|
&(room_alias_id!("#ruma:notareal.hs"), event_id!("$event:notareal.hs")).into()
|
||||||
|
);
|
||||||
|
|
||||||
|
let matrix_uri = MatrixUri::parse("matrix:roomid/ruma:notareal.hs/e/event:notareal.hs")
|
||||||
|
.expect("Failed to create MatrixToUri.");
|
||||||
|
assert_eq!(
|
||||||
|
matrix_uri.id(),
|
||||||
|
&(room_id!("!ruma:notareal.hs"), event_id!("$event:notareal.hs")).into()
|
||||||
|
);
|
||||||
|
assert!(matrix_uri.via().is_empty());
|
||||||
|
assert!(matrix_uri.action().is_none());
|
||||||
|
|
||||||
|
let matrix_uri =
|
||||||
|
MatrixUri::parse("matrix:roomid/ruma:notareal.hs/e/event:notareal.hs?via=notareal.hs&action=join&via=anotherinexistant.hs")
|
||||||
|
.expect("Failed to create MatrixToUri.");
|
||||||
|
assert_eq!(
|
||||||
|
matrix_uri.id(),
|
||||||
|
&(room_id!("!ruma:notareal.hs"), event_id!("$event:notareal.hs")).into()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
matrix_uri.via(),
|
||||||
|
&vec![
|
||||||
|
server_name!("notareal.hs").to_owned(),
|
||||||
|
server_name!("anotherinexistant.hs").to_owned()
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(matrix_uri.action(), Some(&UriAction::Join));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_matrixuri_invalid_uri() {
|
||||||
|
assert_eq!(MatrixUri::parse("").unwrap_err(), Error::InvalidUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_matrixuri_wrong_scheme() {
|
||||||
|
assert_eq!(
|
||||||
|
MatrixUri::parse("unknown:u/user:notareal.hs").unwrap_err(),
|
||||||
|
MatrixUriError::WrongScheme.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_matrixuri_too_many_actions() {
|
||||||
|
assert_eq!(
|
||||||
|
MatrixUri::parse("matrix:u/user:notareal.hs?action=chat&action=join").unwrap_err(),
|
||||||
|
MatrixUriError::TooManyActions.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_matrixuri_unknown_query_item() {
|
||||||
|
assert_eq!(
|
||||||
|
MatrixUri::parse("matrix:roomid/roomid:notareal.hs?via=notareal.hs&fake=data")
|
||||||
|
.unwrap_err(),
|
||||||
|
MatrixUriError::UnknownQueryItem.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_matrixuri_wrong_identifier() {
|
||||||
|
assert_matches!(
|
||||||
|
MatrixUri::parse("matrix:notanidentifier").unwrap_err(),
|
||||||
|
Error::InvalidMatrixId(_)
|
||||||
|
);
|
||||||
|
assert_matches!(MatrixUri::parse("matrix:").unwrap_err(), Error::InvalidMatrixId(_));
|
||||||
|
assert_matches!(
|
||||||
|
MatrixUri::parse("matrix:u/jplatte:notareal.hs/e/event:notareal.hs").unwrap_err(),
|
||||||
|
Error::InvalidMatrixId(_)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
//! Matrix room alias identifiers.
|
//! Matrix room alias identifiers.
|
||||||
|
|
||||||
use crate::{server_name::ServerName, EventId, MatrixToUri};
|
use crate::{matrix_uri::UriAction, server_name::ServerName, EventId, MatrixToUri, MatrixUri};
|
||||||
|
|
||||||
/// A Matrix [room alias ID].
|
/// A Matrix [room alias ID].
|
||||||
///
|
///
|
||||||
@ -41,6 +41,18 @@ impl RoomAliasId {
|
|||||||
MatrixToUri::new((self, ev_id).into(), Vec::new())
|
MatrixToUri::new((self, ev_id).into(), Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a `matrix:` URI for this room alias ID.
|
||||||
|
///
|
||||||
|
/// If `join` is `true`, a click on the URI should join the room.
|
||||||
|
pub fn matrix_uri(&self, join: bool) -> MatrixUri {
|
||||||
|
MatrixUri::new(self.into(), Vec::new(), Some(UriAction::Join).filter(|_| join))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a `matrix:` URI for an event scoped under this room alias ID.
|
||||||
|
pub fn matrix_event_uri(&self, ev_id: &EventId) -> MatrixUri {
|
||||||
|
MatrixUri::new((self, ev_id).into(), Vec::new(), None)
|
||||||
|
}
|
||||||
|
|
||||||
fn colon_idx(&self) -> usize {
|
fn colon_idx(&self) -> usize {
|
||||||
self.as_str().find(':').unwrap()
|
self.as_str().find(':').unwrap()
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
//! Matrix room identifiers.
|
//! Matrix room identifiers.
|
||||||
|
|
||||||
use crate::{EventId, MatrixToUri, ServerName};
|
use crate::{matrix_uri::UriAction, EventId, MatrixToUri, MatrixUri, ServerName};
|
||||||
|
|
||||||
/// A Matrix [room ID].
|
/// A Matrix [room ID].
|
||||||
///
|
///
|
||||||
@ -63,6 +63,43 @@ impl RoomId {
|
|||||||
MatrixToUri::new((self, ev_id).into(), Vec::new())
|
MatrixToUri::new((self, ev_id).into(), Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a `matrix:` URI for this room ID.
|
||||||
|
///
|
||||||
|
/// If `join` is `true`, a click on the URI should join the room.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use ruma_identifiers::{room_id, server_name};
|
||||||
|
///
|
||||||
|
/// assert_eq!(
|
||||||
|
/// room_id!("!somewhere:example.org")
|
||||||
|
/// .matrix_uri([&*server_name!("example.org"), &*server_name!("alt.example.org")], true)
|
||||||
|
/// .to_string(),
|
||||||
|
/// "matrix:roomid/somewhere:example.org?via=example.org&via=alt.example.org&action=join"
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
pub fn matrix_uri<'a>(
|
||||||
|
&self,
|
||||||
|
via: impl IntoIterator<Item = &'a ServerName>,
|
||||||
|
join: bool,
|
||||||
|
) -> MatrixUri {
|
||||||
|
MatrixUri::new(
|
||||||
|
self.into(),
|
||||||
|
via.into_iter().collect(),
|
||||||
|
Some(UriAction::Join).filter(|_| join),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a `matrix:` URI for an event scoped under this room ID.
|
||||||
|
pub fn matrix_event_uri<'a>(
|
||||||
|
&self,
|
||||||
|
ev_id: &EventId,
|
||||||
|
via: impl IntoIterator<Item = &'a ServerName>,
|
||||||
|
) -> MatrixUri {
|
||||||
|
MatrixUri::new((self, ev_id).into(), via.into_iter().collect(), None)
|
||||||
|
}
|
||||||
|
|
||||||
fn colon_idx(&self) -> usize {
|
fn colon_idx(&self) -> usize {
|
||||||
self.as_str().find(':').unwrap()
|
self.as_str().find(':').unwrap()
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use std::{rc::Rc, sync::Arc};
|
use std::{rc::Rc, sync::Arc};
|
||||||
|
|
||||||
use crate::{MatrixToUri, ServerName};
|
use crate::{matrix_uri::UriAction, MatrixToUri, MatrixUri, ServerName};
|
||||||
|
|
||||||
/// A Matrix [user ID].
|
/// A Matrix [user ID].
|
||||||
///
|
///
|
||||||
@ -122,6 +122,26 @@ impl UserId {
|
|||||||
MatrixToUri::new(self.into(), Vec::new())
|
MatrixToUri::new(self.into(), Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a `matrix:` URI for this user ID.
|
||||||
|
///
|
||||||
|
/// If `chat` is `true`, a click on the URI should start a direct message
|
||||||
|
/// with the user.
|
||||||
|
///
|
||||||
|
/// # 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_uri(false),
|
||||||
|
/// display_name = "jplatte",
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
pub fn matrix_uri(&self, chat: bool) -> MatrixUri {
|
||||||
|
MatrixUri::new(self.into(), Vec::new(), Some(UriAction::Chat).filter(|_| chat))
|
||||||
|
}
|
||||||
|
|
||||||
fn colon_idx(&self) -> usize {
|
fn colon_idx(&self) -> usize {
|
||||||
self.as_str().find(':').unwrap()
|
self.as_str().find(':').unwrap()
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user