identifiers: Make RoomIdOrAliasId a DST

This commit is contained in:
Jonas Platte 2021-11-24 21:36:15 +01:00
parent 2d4dbfe42f
commit 313124a099
No known key found for this signature in database
GPG Key ID: 7D261D771D915378
4 changed files with 43 additions and 58 deletions

View File

@ -356,7 +356,7 @@ pub struct ResultRoomEvents {
/// Any groups that were requested. /// Any groups that were requested.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")] #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub groups: BTreeMap<GroupingKey, BTreeMap<RoomIdOrUserId, ResultGroup>>, pub groups: BTreeMap<GroupingKey, BTreeMap<Box<RoomIdOrUserId>, ResultGroup>>,
/// Token that can be used to get the next batch of results, by passing as the `next_batch` /// Token that can be used to get the next batch of results, by passing as the `next_batch`
/// parameter to the next call. /// parameter to the next call.

View File

@ -1,7 +1,5 @@
use std::num::NonZeroU8; use crate::{validate_delimited_id, Error};
use crate::{parse_id, Error}; pub fn validate(s: &str) -> Result<(), Error> {
validate_delimited_id(s, &['#', '!'])
pub fn validate(s: &str) -> Result<NonZeroU8, Error> {
parse_id(s, &['#', '!'])
} }

View File

@ -2,9 +2,7 @@
use std::{ use std::{
convert::{TryFrom, TryInto}, convert::{TryFrom, TryInto},
fmt,
hint::unreachable_unchecked, hint::unreachable_unchecked,
num::NonZeroU8,
}; };
use crate::{server_name::ServerName, RoomAliasId, RoomId}; use crate::{server_name::ServerName, RoomAliasId, RoomId};
@ -19,36 +17,32 @@ use crate::{server_name::ServerName, RoomAliasId, RoomId};
/// # use std::convert::TryFrom; /// # use std::convert::TryFrom;
/// # use ruma_identifiers::RoomIdOrAliasId; /// # use ruma_identifiers::RoomIdOrAliasId;
/// assert_eq!( /// assert_eq!(
/// RoomIdOrAliasId::try_from("#ruma:example.com").unwrap().as_ref(), /// <&RoomIdOrAliasId>::try_from("#ruma:example.com").unwrap(),
/// "#ruma:example.com" /// "#ruma:example.com"
/// ); /// );
/// ///
/// assert_eq!( /// assert_eq!(
/// RoomIdOrAliasId::try_from("!n8f893n9:example.com").unwrap().as_ref(), /// <&RoomIdOrAliasId>::try_from("!n8f893n9:example.com").unwrap(),
/// "!n8f893n9:example.com" /// "!n8f893n9:example.com"
/// ); /// );
/// ``` /// ```
#[derive(Clone)] #[repr(transparent)]
pub struct RoomIdOrAliasId { pub struct RoomIdOrAliasId(str);
full_id: Box<str>,
colon_idx: NonZeroU8,
}
impl fmt::Debug for RoomIdOrAliasId { opaque_identifier_validated!(
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { RoomIdOrAliasId,
self.full_id.fmt(f) ruma_identifiers_validation::room_id_or_alias_id::validate
} );
}
impl RoomIdOrAliasId { impl RoomIdOrAliasId {
/// Returns the local part (everything after the `!` or `#` and before the first colon). /// Returns the local part (everything after the `!` or `#` and before the first colon).
pub fn localpart(&self) -> &str { pub fn localpart(&self) -> &str {
&self.full_id[1..self.colon_idx.get() as usize] &self.as_str()[1..self.colon_idx()]
} }
/// Returns the server name of the room (alias) ID. /// Returns the server name of the room (alias) ID.
pub fn server_name(&self) -> &ServerName { pub fn server_name(&self) -> &ServerName {
self.full_id[self.colon_idx.get() as usize + 1..].try_into().unwrap() self.as_str()[self.colon_idx() + 1..].try_into().unwrap()
} }
/// Whether this is a room id (starts with `'!'`) /// Whether this is a room id (starts with `'!'`)
@ -63,15 +57,22 @@ impl RoomIdOrAliasId {
/// Turn this `RoomIdOrAliasId` into `Either<RoomId, RoomAliasId>` /// Turn this `RoomIdOrAliasId` into `Either<RoomId, RoomAliasId>`
#[cfg(feature = "either")] #[cfg(feature = "either")]
pub fn into_either(self) -> either::Either<Box<RoomId>, Box<RoomAliasId>> { pub fn into_either(self: Box<Self>) -> either::Either<Box<RoomId>, Box<RoomAliasId>> {
match self.variant() { let variant = self.variant();
Variant::RoomId => either::Either::Left(self.as_str().try_into().unwrap()), let boxed_str = self.into_owned();
Variant::RoomAliasId => either::Either::Right(self.as_str().try_into().unwrap()),
match variant {
Variant::RoomId => either::Either::Left(RoomId::from_owned(boxed_str)),
Variant::RoomAliasId => either::Either::Right(RoomAliasId::from_owned(boxed_str)),
} }
} }
fn colon_idx(&self) -> usize {
self.as_str().find(':').unwrap()
}
fn variant(&self) -> Variant { fn variant(&self) -> Variant {
match self.full_id.bytes().next() { match self.as_str().bytes().next() {
Some(b'!') => Variant::RoomId, Some(b'!') => Variant::RoomId,
Some(b'#') => Variant::RoomAliasId, Some(b'#') => Variant::RoomAliasId,
_ => unsafe { unreachable_unchecked() }, _ => unsafe { unreachable_unchecked() },
@ -85,38 +86,22 @@ enum Variant {
RoomAliasId, RoomAliasId,
} }
/// Attempts to create a new Matrix room ID or a room alias ID from a string representation. impl From<Box<RoomId>> for Box<RoomIdOrAliasId> {
///
/// 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<S>(room_id_or_alias_id: S) -> Result<RoomIdOrAliasId, crate::Error>
where
S: AsRef<str> + Into<Box<str>>,
{
let colon_idx =
ruma_identifiers_validation::room_id_or_alias_id::validate(room_id_or_alias_id.as_ref())?;
Ok(RoomIdOrAliasId { full_id: room_id_or_alias_id.into(), colon_idx })
}
common_impls!(RoomIdOrAliasId, try_from, "a Matrix room ID or room alias ID");
impl From<Box<RoomId>> for RoomIdOrAliasId {
fn from(room_id: Box<RoomId>) -> Self { fn from(room_id: Box<RoomId>) -> Self {
Self::try_from(room_id.as_str()).unwrap() Self::try_from(room_id.as_str()).unwrap()
} }
} }
impl From<Box<RoomAliasId>> for RoomIdOrAliasId { impl From<Box<RoomAliasId>> for Box<RoomIdOrAliasId> {
fn from(room_alias_id: Box<RoomAliasId>) -> Self { fn from(room_alias_id: Box<RoomAliasId>) -> Self {
Self::try_from(room_alias_id.as_str()).unwrap() Self::try_from(room_alias_id.as_str()).unwrap()
} }
} }
impl TryFrom<RoomIdOrAliasId> for Box<RoomId> { impl TryFrom<Box<RoomIdOrAliasId>> for Box<RoomId> {
type Error = Box<RoomAliasId>; type Error = Box<RoomAliasId>;
fn try_from(id: RoomIdOrAliasId) -> Result<Box<RoomId>, Box<RoomAliasId>> { fn try_from(id: Box<RoomIdOrAliasId>) -> Result<Box<RoomId>, Box<RoomAliasId>> {
match id.variant() { match id.variant() {
Variant::RoomId => Ok(id.as_str().try_into().unwrap()), Variant::RoomId => Ok(id.as_str().try_into().unwrap()),
Variant::RoomAliasId => Err(id.as_str().try_into().unwrap()), Variant::RoomAliasId => Err(id.as_str().try_into().unwrap()),
@ -124,10 +109,10 @@ impl TryFrom<RoomIdOrAliasId> for Box<RoomId> {
} }
} }
impl TryFrom<RoomIdOrAliasId> for Box<RoomAliasId> { impl TryFrom<Box<RoomIdOrAliasId>> for Box<RoomAliasId> {
type Error = Box<RoomId>; type Error = Box<RoomId>;
fn try_from(id: RoomIdOrAliasId) -> Result<Box<RoomAliasId>, Box<RoomId>> { fn try_from(id: Box<RoomIdOrAliasId>) -> Result<Box<RoomAliasId>, Box<RoomId>> {
match id.variant() { match id.variant() {
Variant::RoomAliasId => Ok(id.as_str().try_into().unwrap()), Variant::RoomAliasId => Ok(id.as_str().try_into().unwrap()),
Variant::RoomId => Err(id.as_str().try_into().unwrap()), Variant::RoomId => Err(id.as_str().try_into().unwrap()),
@ -145,7 +130,7 @@ mod tests {
#[test] #[test]
fn valid_room_id_or_alias_id_with_a_room_alias_id() { fn valid_room_id_or_alias_id_with_a_room_alias_id() {
assert_eq!( assert_eq!(
RoomIdOrAliasId::try_from("#ruma:example.com") <&RoomIdOrAliasId>::try_from("#ruma:example.com")
.expect("Failed to create RoomAliasId.") .expect("Failed to create RoomAliasId.")
.as_ref(), .as_ref(),
"#ruma:example.com" "#ruma:example.com"
@ -155,7 +140,7 @@ mod tests {
#[test] #[test]
fn valid_room_id_or_alias_id_with_a_room_id() { fn valid_room_id_or_alias_id_with_a_room_id() {
assert_eq!( assert_eq!(
RoomIdOrAliasId::try_from("!29fhd83h92h0:example.com") <&RoomIdOrAliasId>::try_from("!29fhd83h92h0:example.com")
.expect("Failed to create RoomId.") .expect("Failed to create RoomId.")
.as_ref(), .as_ref(),
"!29fhd83h92h0:example.com" "!29fhd83h92h0:example.com"
@ -165,7 +150,7 @@ mod tests {
#[test] #[test]
fn missing_sigil_for_room_id_or_alias_id() { fn missing_sigil_for_room_id_or_alias_id() {
assert_eq!( assert_eq!(
RoomIdOrAliasId::try_from("ruma:example.com").unwrap_err(), <&RoomIdOrAliasId>::try_from("ruma:example.com").unwrap_err(),
Error::MissingLeadingSigil Error::MissingLeadingSigil
); );
} }
@ -175,7 +160,7 @@ mod tests {
fn serialize_valid_room_id_or_alias_id_with_a_room_alias_id() { fn serialize_valid_room_id_or_alias_id_with_a_room_alias_id() {
assert_eq!( assert_eq!(
serde_json::to_string( serde_json::to_string(
&RoomIdOrAliasId::try_from("#ruma:example.com") <&RoomIdOrAliasId>::try_from("#ruma:example.com")
.expect("Failed to create RoomAliasId.") .expect("Failed to create RoomAliasId.")
) )
.expect("Failed to convert RoomAliasId to JSON."), .expect("Failed to convert RoomAliasId to JSON."),
@ -188,7 +173,7 @@ mod tests {
fn serialize_valid_room_id_or_alias_id_with_a_room_id() { fn serialize_valid_room_id_or_alias_id_with_a_room_id() {
assert_eq!( assert_eq!(
serde_json::to_string( serde_json::to_string(
&RoomIdOrAliasId::try_from("!29fhd83h92h0:example.com") <&RoomIdOrAliasId>::try_from("!29fhd83h92h0:example.com")
.expect("Failed to create RoomId.") .expect("Failed to create RoomId.")
) )
.expect("Failed to convert RoomId to JSON."), .expect("Failed to convert RoomId to JSON."),
@ -200,9 +185,10 @@ mod tests {
#[test] #[test]
fn deserialize_valid_room_id_or_alias_id_with_a_room_alias_id() { fn deserialize_valid_room_id_or_alias_id_with_a_room_alias_id() {
assert_eq!( assert_eq!(
serde_json::from_str::<RoomIdOrAliasId>(r##""#ruma:example.com""##) serde_json::from_str::<Box<RoomIdOrAliasId>>(r##""#ruma:example.com""##)
.expect("Failed to convert JSON to RoomAliasId"), .expect("Failed to convert JSON to RoomAliasId"),
RoomIdOrAliasId::try_from("#ruma:example.com").expect("Failed to create RoomAliasId.") <&RoomIdOrAliasId>::try_from("#ruma:example.com")
.expect("Failed to create RoomAliasId.")
); );
} }
@ -210,9 +196,9 @@ mod tests {
#[test] #[test]
fn deserialize_valid_room_id_or_alias_id_with_a_room_id() { fn deserialize_valid_room_id_or_alias_id_with_a_room_id() {
assert_eq!( assert_eq!(
serde_json::from_str::<RoomIdOrAliasId>(r##""!29fhd83h92h0:example.com""##) serde_json::from_str::<Box<RoomIdOrAliasId>>(r##""!29fhd83h92h0:example.com""##)
.expect("Failed to convert JSON to RoomId"), .expect("Failed to convert JSON to RoomId"),
RoomIdOrAliasId::try_from("!29fhd83h92h0:example.com") <&RoomIdOrAliasId>::try_from("!29fhd83h92h0:example.com")
.expect("Failed to create RoomAliasId.") .expect("Failed to create RoomAliasId.")
); );
} }

View File

@ -269,6 +269,7 @@ fn strip_lifetimes(field_type: &mut Type) -> bool {
|| last_seg.ident == "RawJsonValue" || last_seg.ident == "RawJsonValue"
|| last_seg.ident == "RoomAliasId" || last_seg.ident == "RoomAliasId"
|| last_seg.ident == "RoomId" || last_seg.ident == "RoomId"
|| last_seg.ident == "RoomIdOrAliasId"
|| last_seg.ident == "RoomName" || last_seg.ident == "RoomName"
{ {
// The identifiers that need to be boxed `Box<T>` since they are DST's. // The identifiers that need to be boxed `Box<T>` since they are DST's.