//! Matrix identifiers for places where a room ID or room alias ID are used interchangeably. use std::{borrow::Cow, convert::TryFrom, hint::unreachable_unchecked, num::NonZeroU8}; #[cfg(feature = "diesel")] use diesel::sql_types::Text; use crate::{error::Error, parse_id}; /// A Matrix room ID or a Matrix room alias ID. /// /// `RoomIdOrAliasId` is useful for APIs that accept either kind of room identifier. It is converted /// from a string slice, and can be converted back into a string as needed. When converted from a /// string slice, the variant is determined by the leading sigil character. /// /// ``` /// # use std::convert::TryFrom; /// # use ruma_identifiers::RoomIdOrAliasId; /// assert_eq!( /// RoomIdOrAliasId::try_from("#ruma:example.com").unwrap().as_ref(), /// "#ruma:example.com" /// ); /// /// assert_eq!( /// RoomIdOrAliasId::try_from("!n8f893n9:example.com").unwrap().as_ref(), /// "!n8f893n9:example.com" /// ); /// ``` #[derive(Clone, Debug)] #[cfg_attr(feature = "diesel", derive(FromSqlRow, QueryId, AsExpression, SqlType))] #[cfg_attr(feature = "diesel", sql_type = "Text")] pub struct RoomIdOrAliasId { full_id: String, colon_idx: NonZeroU8, } impl RoomIdOrAliasId { /// Returns the host of the room (alias) ID, containing the server name (including the port) of /// the originating homeserver. pub fn hostname(&self) -> &str { &self.full_id[self.colon_idx.get() as usize + 1..] } /// Returns the local part (everything after the `!` or `#` and before the first colon). pub fn localpart(&self) -> &str { &self.full_id[1..self.colon_idx.get() as usize] } /// Whether this is a room id (starts with `'!'`) pub fn is_room_id(&self) -> bool { self.variant() == Variant::RoomId } /// Whether this is a room alias id (starts with `'#'`) pub fn is_room_alias_id(&self) -> bool { self.variant() == Variant::RoomAliasId } fn variant(&self) -> Variant { match self.full_id.bytes().next() { Some(b'!') => Variant::RoomId, Some(b'#') => Variant::RoomAliasId, _ => unsafe { unreachable_unchecked() }, } } } #[derive(PartialEq)] enum Variant { RoomId, RoomAliasId, } impl TryFrom> for RoomIdOrAliasId { type Error = Error; /// Attempts to create a new Matrix room ID or a room alias ID from a string representation. /// /// The string must either include the leading ! sigil, the localpart, a literal colon, and a /// valid homeserver host or include the leading # sigil, the alias, a literal colon, and a /// valid homeserver host. fn try_from(room_id_or_alias_id: Cow<'_, str>) -> Result { let colon_idx = parse_id(&room_id_or_alias_id, &['#', '!'])?; Ok(Self { full_id: room_id_or_alias_id.into_owned(), colon_idx, }) } } common_impls!(RoomIdOrAliasId, "a Matrix room ID or room alias ID"); #[cfg(test)] mod tests { use std::convert::TryFrom; use serde_json::{from_str, to_string}; use super::RoomIdOrAliasId; use crate::error::Error; #[test] fn valid_room_id_or_alias_id_with_a_room_alias_id() { assert_eq!( RoomIdOrAliasId::try_from("#ruma:example.com") .expect("Failed to create RoomAliasId.") .as_ref(), "#ruma:example.com" ); } #[test] fn valid_room_id_or_alias_id_with_a_room_id() { assert_eq!( RoomIdOrAliasId::try_from("!29fhd83h92h0:example.com") .expect("Failed to create RoomId.") .as_ref(), "!29fhd83h92h0:example.com" ); } #[test] fn missing_sigil_for_room_id_or_alias_id() { assert_eq!( RoomIdOrAliasId::try_from("ruma:example.com").unwrap_err(), Error::MissingSigil ); } #[test] fn serialize_valid_room_id_or_alias_id_with_a_room_alias_id() { assert_eq!( to_string( &RoomIdOrAliasId::try_from("#ruma:example.com") .expect("Failed to create RoomAliasId.") ) .expect("Failed to convert RoomAliasId to JSON."), r##""#ruma:example.com""## ); } #[test] fn serialize_valid_room_id_or_alias_id_with_a_room_id() { assert_eq!( to_string( &RoomIdOrAliasId::try_from("!29fhd83h92h0:example.com") .expect("Failed to create RoomId.") ) .expect("Failed to convert RoomId to JSON."), r#""!29fhd83h92h0:example.com""# ); } #[test] fn deserialize_valid_room_id_or_alias_id_with_a_room_alias_id() { assert_eq!( from_str::(r##""#ruma:example.com""##) .expect("Failed to convert JSON to RoomAliasId"), RoomIdOrAliasId::try_from("#ruma:example.com").expect("Failed to create RoomAliasId.") ); } #[test] fn deserialize_valid_room_id_or_alias_id_with_a_room_id() { assert_eq!( from_str::(r##""!29fhd83h92h0:example.com""##) .expect("Failed to convert JSON to RoomId"), RoomIdOrAliasId::try_from("!29fhd83h92h0:example.com") .expect("Failed to create RoomAliasId.") ); } }