identifiers: Make RoomAliasId a DST

This commit is contained in:
Jonas Platte 2021-09-19 18:25:22 +02:00
parent ec605a0959
commit b0db5e94e1
No known key found for this signature in database
GPG Key ID: 7D261D771D915378
17 changed files with 83 additions and 101 deletions

View File

@ -19,7 +19,7 @@ use serde::{Deserialize, Serialize};
#[derive(Debug)]
pub struct Request {
pub room_id: RoomId, // body
pub room_alias: RoomAliasId, // path
pub room_alias: Box<RoomAliasId>, // path
}
impl Outgoing for Request {

View File

@ -17,11 +17,11 @@ use serde::{Deserialize, Serialize};
pub struct PublicRoomsChunk {
/// Aliases of the room.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub aliases: Vec<RoomAliasId>,
pub aliases: Vec<Box<RoomAliasId>>,
/// The canonical alias of the room, if any.
#[serde(skip_serializing_if = "Option::is_none")]
pub canonical_alias: Option<RoomAliasId>,
pub canonical_alias: Option<Box<RoomAliasId>>,
/// The name of the room, if any.
#[serde(skip_serializing_if = "Option::is_none")]

View File

@ -21,7 +21,7 @@ ruma_api! {
response: {
/// The server's local aliases on the room.
pub aliases: Vec<RoomAliasId>,
pub aliases: Vec<Box<RoomAliasId>>,
}
error: crate::Error
@ -36,7 +36,7 @@ impl<'a> Request<'a> {
impl Response {
/// Creates a new `Response` with the given aliases.
pub fn new(aliases: Vec<RoomAliasId>) -> Self {
pub fn new(aliases: Vec<Box<RoomAliasId>>) -> Self {
Self { aliases }
}
}

View File

@ -61,7 +61,7 @@
//!
//! async {
//! let response = client
//! .send_request(get_alias::Request::new(&room_alias_id!("#example_room:example.com")))
//! .send_request(get_alias::Request::new(room_alias_id!("#example_room:example.com")))
//! .await?;
//!
//! assert_eq!(response.room_id, room_id!("!n8f893n9:example.com"));

View File

@ -21,11 +21,11 @@ use serde_json::Value as JsonValue;
pub struct PublicRoomsChunk {
/// Aliases of the room.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub aliases: Vec<RoomAliasId>,
pub aliases: Vec<Box<RoomAliasId>>,
/// The canonical alias of the room, if any.
#[serde(skip_serializing_if = "Option::is_none")]
pub canonical_alias: Option<RoomAliasId>,
pub canonical_alias: Option<Box<RoomAliasId>>,
/// The name of the room, if any.
#[serde(skip_serializing_if = "Option::is_none")]

View File

@ -173,7 +173,7 @@ impl From<FieldTypeInit> for FieldType {
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct Location {
/// An alias for a matrix room.
pub alias: RoomAliasId,
pub alias: Box<RoomAliasId>,
/// The protocol ID that the third party location is a part of.
pub protocol: String,
@ -184,7 +184,11 @@ pub struct Location {
impl Location {
/// Creates a new `Location` with the given alias, protocol and fields.
pub fn new(alias: RoomAliasId, protocol: String, fields: BTreeMap<String, String>) -> Self {
pub fn new(
alias: Box<RoomAliasId>,
protocol: String,
fields: BTreeMap<String, String>,
) -> Self {
Self { alias, protocol, fields }
}
}

View File

@ -18,12 +18,12 @@ use crate::{
#[ruma_event(type = "m.room.aliases", kind = State, custom_redacted)]
pub struct RoomAliasesEventContent {
/// A list of room aliases.
pub aliases: Vec<RoomAliasId>,
pub aliases: Vec<Box<RoomAliasId>>,
}
impl RoomAliasesEventContent {
/// Create an `RoomAliasesEventContent` from the given aliases.
pub fn new(aliases: Vec<RoomAliasId>) -> Self {
pub fn new(aliases: Vec<Box<RoomAliasId>>) -> Self {
Self { aliases }
}
}
@ -55,14 +55,14 @@ pub struct RedactedRoomAliasesEventContent {
///
/// According to the Matrix spec version 1 redaction rules allowed this field to be
/// kept after redaction, this was changed in version 6.
pub aliases: Option<Vec<RoomAliasId>>,
pub aliases: Option<Vec<Box<RoomAliasId>>>,
}
impl RedactedRoomAliasesEventContent {
/// Create a `RedactedAliasesEventContent` with the given aliases.
///
/// This is only valid for room version 5 and below.
pub fn new_v1(aliases: Vec<RoomAliasId>) -> Self {
pub fn new_v1(aliases: Vec<Box<RoomAliasId>>) -> Self {
Self { aliases: Some(aliases) }
}

View File

@ -20,11 +20,11 @@ pub struct RoomCanonicalAliasEventContent {
deserialize_with = "ruma_serde::empty_string_as_none",
skip_serializing_if = "Option::is_none"
)]
pub alias: Option<RoomAliasId>,
pub alias: Option<Box<RoomAliasId>>,
/// List of alternative aliases to the room.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub alt_aliases: Vec<RoomAliasId>,
pub alt_aliases: Vec<Box<RoomAliasId>>,
}
impl RoomCanonicalAliasEventContent {
@ -48,7 +48,7 @@ mod tests {
fn serialization_with_optional_fields_as_none() {
let canonical_alias_event = StateEvent {
content: RoomCanonicalAliasEventContent {
alias: Some(room_alias_id!("#somewhere:localhost")),
alias: Some(room_alias_id!("#somewhere:localhost").to_owned()),
alt_aliases: Vec::new(),
},
event_id: event_id!("$h29iv0s8:example.com").to_owned(),
@ -143,7 +143,7 @@ mod tests {
#[test]
fn nonempty_field_as_some() {
let alias = Some(room_alias_id!("#somewhere:localhost"));
let alias = Some(room_alias_id!("#somewhere:localhost").to_owned());
let json_data = json!({
"content": {
"alias": "#somewhere:localhost"

View File

@ -36,10 +36,14 @@ fn aliases_event_with_prev_content() -> JsonValue {
#[test]
fn serialize_aliases_with_prev_content() {
let aliases_event = StateEvent {
content: RoomAliasesEventContent::new(vec![room_alias_id!("#somewhere:localhost")]),
content: RoomAliasesEventContent::new(vec![
room_alias_id!("#somewhere:localhost").to_owned()
]),
event_id: event_id!("$h29iv0s8:example.com").to_owned(),
origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(1)),
prev_content: Some(RoomAliasesEventContent::new(vec![room_alias_id!("#inner:localhost")])),
prev_content: Some(RoomAliasesEventContent::new(vec![
room_alias_id!("#inner:localhost").to_owned()
])),
room_id: room_id!("!roomid:room.com"),
sender: user_id!("@carl:example.com"),
state_key: "".into(),
@ -55,7 +59,9 @@ fn serialize_aliases_with_prev_content() {
#[test]
fn serialize_aliases_without_prev_content() {
let aliases_event = StateEvent {
content: RoomAliasesEventContent::new(vec![room_alias_id!("#somewhere:localhost")]),
content: RoomAliasesEventContent::new(vec![
room_alias_id!("#somewhere:localhost").to_owned()
]),
event_id: event_id!("$h29iv0s8:example.com").to_owned(),
origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(1)),
prev_content: None,

View File

@ -55,7 +55,7 @@ pub fn room_alias_id(input: TokenStream) -> TokenStream {
assert!(room_alias_id::validate(&id.value()).is_ok(), "Invalid room_alias_id");
let output = quote! {
<#dollar_crate::RoomAliasId as ::std::convert::TryFrom<&str>>::try_from(#id).unwrap()
<&#dollar_crate::RoomAliasId as ::std::convert::TryFrom<&str>>::try_from(#id).unwrap()
};
output.into()

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<NonZeroU8, Error> {
parse_id(s, &['#'])
pub fn validate(s: &str) -> Result<(), Error> {
validate_delimited_id(s, &['#'])
}

View File

@ -1,6 +1,6 @@
//! Matrix room alias identifiers.
use std::{convert::TryInto, fmt, num::NonZeroU8};
use std::convert::TryInto;
use crate::{server_name::ServerName, EventId, MatrixToRef};
@ -13,57 +13,41 @@ use crate::{server_name::ServerName, EventId, MatrixToRef};
/// # use std::convert::TryFrom;
/// # use ruma_identifiers::RoomAliasId;
/// assert_eq!(
/// RoomAliasId::try_from("#ruma:example.com").unwrap().as_ref(),
/// <&RoomAliasId>::try_from("#ruma:example.com").unwrap(),
/// "#ruma:example.com"
/// );
/// ```
#[derive(Clone)]
pub struct RoomAliasId {
pub(crate) full_id: Box<str>,
pub(crate) colon_idx: NonZeroU8,
}
#[repr(transparent)]
pub struct RoomAliasId(str);
impl fmt::Debug for RoomAliasId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.full_id.fmt(f)
}
}
opaque_identifier_validated!(RoomAliasId, ruma_identifiers_validation::room_alias_id::validate);
impl RoomAliasId {
/// Returns the room's alias.
pub fn alias(&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.
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()
}
/// Create a `matrix.to` reference for this room alias ID.
pub fn matrix_to_url(&self) -> MatrixToRef<'_> {
MatrixToRef::new(&self.full_id, Vec::new())
MatrixToRef::new(self.as_str(), 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())
MatrixToRef::event(self.as_str(), ev_id, Vec::new())
}
fn colon_idx(&self) -> usize {
self.as_str().find(':').unwrap()
}
}
/// Attempts to create a new Matrix room alias ID from a string representation.
///
/// The string must include the leading # sigil, the alias, a literal colon, and a server name.
fn try_from<S>(room_alias_id: S) -> Result<RoomAliasId, crate::Error>
where
S: AsRef<str> + Into<Box<str>>,
{
let colon_idx = ruma_identifiers_validation::room_alias_id::validate(room_alias_id.as_ref())?;
Ok(RoomAliasId { full_id: room_alias_id.into(), colon_idx })
}
common_impls!(RoomAliasId, try_from, "a Matrix room alias ID");
#[cfg(test)]
mod tests {
use std::convert::TryFrom;
@ -74,9 +58,7 @@ mod tests {
#[test]
fn valid_room_alias_id() {
assert_eq!(
RoomAliasId::try_from("#ruma:example.com")
.expect("Failed to create RoomAliasId.")
.as_ref(),
<&RoomAliasId>::try_from("#ruma:example.com").expect("Failed to create RoomAliasId."),
"#ruma:example.com"
);
}
@ -84,9 +66,7 @@ mod tests {
#[test]
fn empty_localpart() {
assert_eq!(
RoomAliasId::try_from("#:myhomeserver.io")
.expect("Failed to create RoomAliasId.")
.as_ref(),
<&RoomAliasId>::try_from("#:myhomeserver.io").expect("Failed to create RoomAliasId."),
"#:myhomeserver.io"
);
}
@ -96,7 +76,8 @@ mod tests {
fn serialize_valid_room_alias_id() {
assert_eq!(
serde_json::to_string(
&RoomAliasId::try_from("#ruma:example.com").expect("Failed to create RoomAliasId.")
<&RoomAliasId>::try_from("#ruma:example.com")
.expect("Failed to create RoomAliasId.")
)
.expect("Failed to convert RoomAliasId to JSON."),
r##""#ruma:example.com""##
@ -107,18 +88,17 @@ mod tests {
#[test]
fn deserialize_valid_room_alias_id() {
assert_eq!(
serde_json::from_str::<RoomAliasId>(r##""#ruma:example.com""##)
serde_json::from_str::<Box<RoomAliasId>>(r##""#ruma:example.com""##)
.expect("Failed to convert JSON to RoomAliasId"),
RoomAliasId::try_from("#ruma:example.com").expect("Failed to create RoomAliasId.")
<&RoomAliasId>::try_from("#ruma:example.com").expect("Failed to create RoomAliasId.")
);
}
#[test]
fn valid_room_alias_id_with_explicit_standard_port() {
assert_eq!(
RoomAliasId::try_from("#ruma:example.com:443")
.expect("Failed to create RoomAliasId.")
.as_ref(),
<&RoomAliasId>::try_from("#ruma:example.com:443")
.expect("Failed to create RoomAliasId."),
"#ruma:example.com:443"
);
}
@ -126,9 +106,8 @@ mod tests {
#[test]
fn valid_room_alias_id_with_non_standard_port() {
assert_eq!(
RoomAliasId::try_from("#ruma:example.com:5000")
.expect("Failed to create RoomAliasId.")
.as_ref(),
<&RoomAliasId>::try_from("#ruma:example.com:5000")
.expect("Failed to create RoomAliasId."),
"#ruma:example.com:5000"
);
}
@ -136,9 +115,8 @@ mod tests {
#[test]
fn valid_room_alias_id_unicode() {
assert_eq!(
RoomAliasId::try_from("#老虎£я:example.com")
.expect("Failed to create RoomAliasId.")
.as_ref(),
<&RoomAliasId>::try_from("#老虎£я:example.com")
.expect("Failed to create RoomAliasId."),
"#老虎£я:example.com"
);
}
@ -146,33 +124,33 @@ mod tests {
#[test]
fn missing_room_alias_id_sigil() {
assert_eq!(
RoomAliasId::try_from("39hvsi03hlne:example.com").unwrap_err(),
<&RoomAliasId>::try_from("39hvsi03hlne:example.com").unwrap_err(),
Error::MissingLeadingSigil
);
}
#[test]
fn missing_room_alias_id_delimiter() {
assert_eq!(RoomAliasId::try_from("#ruma").unwrap_err(), Error::MissingDelimiter);
assert_eq!(<&RoomAliasId>::try_from("#ruma").unwrap_err(), Error::MissingDelimiter);
}
#[test]
fn invalid_leading_sigil() {
assert_eq!(
RoomAliasId::try_from("!room_id:foo.bar").unwrap_err(),
<&RoomAliasId>::try_from("!room_id:foo.bar").unwrap_err(),
Error::MissingLeadingSigil
);
}
#[test]
fn invalid_room_alias_id_host() {
assert_eq!(RoomAliasId::try_from("#ruma:/").unwrap_err(), Error::InvalidServerName);
assert_eq!(<&RoomAliasId>::try_from("#ruma:/").unwrap_err(), Error::InvalidServerName);
}
#[test]
fn invalid_room_alias_id_port() {
assert_eq!(
RoomAliasId::try_from("#ruma:example.com:notaport").unwrap_err(),
<&RoomAliasId>::try_from("#ruma:example.com:notaport").unwrap_err(),
Error::InvalidServerName
);
}

View File

@ -63,15 +63,12 @@ impl RoomIdOrAliasId {
/// Turn this `RoomIdOrAliasId` into `Either<RoomId, RoomAliasId>`
#[cfg(feature = "either")]
pub fn into_either(self) -> either::Either<RoomId, RoomAliasId> {
pub fn into_either(self) -> either::Either<RoomId, Box<RoomAliasId>> {
match self.variant() {
Variant::RoomId => {
either::Either::Left(RoomId { full_id: self.full_id, colon_idx: self.colon_idx })
}
Variant::RoomAliasId => either::Either::Right(RoomAliasId {
full_id: self.full_id,
colon_idx: self.colon_idx,
}),
Variant::RoomAliasId => either::Either::Right(self.as_str().try_into().unwrap()),
}
}
@ -112,33 +109,29 @@ impl From<RoomId> for RoomIdOrAliasId {
}
}
impl From<RoomAliasId> for RoomIdOrAliasId {
fn from(RoomAliasId { full_id, colon_idx }: RoomAliasId) -> Self {
Self { full_id, colon_idx }
impl From<Box<RoomAliasId>> for RoomIdOrAliasId {
fn from(room_alias_id: Box<RoomAliasId>) -> Self {
Self::try_from(room_alias_id.as_str()).unwrap()
}
}
impl TryFrom<RoomIdOrAliasId> for RoomId {
type Error = RoomAliasId;
type Error = Box<RoomAliasId>;
fn try_from(id: RoomIdOrAliasId) -> Result<RoomId, RoomAliasId> {
fn try_from(id: RoomIdOrAliasId) -> Result<RoomId, Box<RoomAliasId>> {
match id.variant() {
Variant::RoomId => Ok(RoomId { full_id: id.full_id, colon_idx: id.colon_idx }),
Variant::RoomAliasId => {
Err(RoomAliasId { full_id: id.full_id, colon_idx: id.colon_idx })
}
Variant::RoomAliasId => Err(id.as_str().try_into().unwrap()),
}
}
}
impl TryFrom<RoomIdOrAliasId> for RoomAliasId {
impl TryFrom<RoomIdOrAliasId> for Box<RoomAliasId> {
type Error = RoomId;
fn try_from(id: RoomIdOrAliasId) -> Result<RoomAliasId, RoomId> {
fn try_from(id: RoomIdOrAliasId) -> Result<Box<RoomAliasId>, RoomId> {
match id.variant() {
Variant::RoomAliasId => {
Ok(RoomAliasId { full_id: id.full_id, colon_idx: id.colon_idx })
}
Variant::RoomAliasId => Ok(id.as_str().try_into().unwrap()),
Variant::RoomId => Err(RoomId { full_id: id.full_id, colon_idx: id.colon_idx }),
}
}

View File

@ -368,7 +368,7 @@ mod tests {
event_type: Some(&EventType::RoomMessage),
sender: Some(&uid),
sender_display_name: Some("Major Tom"),
room_alias: Some(&alias),
room_alias: Some(alias),
content: Some(serde_json::from_str("{}").unwrap()),
counts: count,
prio: NotificationPriority::Low,

View File

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

View File

@ -48,5 +48,6 @@ async fn main() -> anyhow::Result<()> {
}
};
hello_world(homeserver_url, &username, &password, &RoomAliasId::try_from(room.as_str())?).await
hello_world(homeserver_url, &username, &password, <&RoomAliasId>::try_from(room.as_str())?)
.await
}

View File

@ -44,5 +44,6 @@ async fn main() -> anyhow::Result<()> {
}
};
hello_world(homeserver_url, &username, &password, &RoomAliasId::try_from(room.as_str())?).await
hello_world(homeserver_url, &username, &password, <&RoomAliasId>::try_from(room.as_str())?)
.await
}