Move RoomName to ruma-identifiers and use it more

This commit is contained in:
Adam 2021-07-19 21:00:44 +01:00 committed by Jonas Platte
parent 57266bf284
commit 63411165da
No known key found for this signature in database
GPG Key ID: CC154DE0E30B7C67
17 changed files with 67 additions and 84 deletions

View File

@ -8,7 +8,7 @@ pub mod set_room_visibility;
use js_int::{uint, UInt}; use js_int::{uint, UInt};
#[cfg(feature = "unstable-pre-spec")] #[cfg(feature = "unstable-pre-spec")]
use ruma_events::room::join_rules::JoinRule; use ruma_events::room::join_rules::JoinRule;
use ruma_identifiers::{MxcUri, RoomAliasId, RoomId}; use ruma_identifiers::{MxcUri, RoomAliasId, RoomId, RoomNameBox};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// A chunk of a room list response, describing one room /// A chunk of a room list response, describing one room
@ -25,7 +25,7 @@ pub struct PublicRoomsChunk {
/// The name of the room, if any. /// The name of the room, if any.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>, pub name: Option<RoomNameBox>,
/// The number of members joined to the room. /// The number of members joined to the room.
pub num_joined_members: UInt, pub num_joined_members: UInt,

View File

@ -11,7 +11,7 @@ use ruma_events::{
}, },
AnyInitialStateEvent, AnyInitialStateEvent,
}; };
use ruma_identifiers::{RoomId, RoomVersionId, UserId}; use ruma_identifiers::{RoomId, RoomName, RoomVersionId, UserId};
use ruma_serde::{Raw, StringEnum}; use ruma_serde::{Raw, StringEnum};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -58,7 +58,7 @@ ruma_api! {
/// If this is included, an `m.room.name` event will be sent into the room to indicate /// If this is included, an `m.room.name` event will be sent into the room to indicate
/// the name of the room. /// the name of the room.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<&'a str>, pub name: Option<&'a RoomName>,
/// Power level content to override in the default power level event. /// Power level content to override in the default power level event.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]

View File

@ -3,7 +3,7 @@
use std::fmt; use std::fmt;
use js_int::UInt; use js_int::UInt;
use ruma_identifiers::{MxcUri, RoomAliasId, RoomId}; use ruma_identifiers::{MxcUri, RoomAliasId, RoomId, RoomNameBox};
use ruma_serde::Outgoing; use ruma_serde::Outgoing;
use serde::{ use serde::{
de::{Error, MapAccess, Visitor}, de::{Error, MapAccess, Visitor},
@ -29,7 +29,7 @@ pub struct PublicRoomsChunk {
/// The name of the room, if any. /// The name of the room, if any.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>, pub name: Option<RoomNameBox>,
/// The number of members joined to the room. /// The number of members joined to the room.
pub num_joined_members: UInt, pub num_joined_members: UInt,

View File

@ -2,7 +2,7 @@
Breaking changes: Breaking changes:
* `room::name::NameEventContent` now uses a custom `RoomName` type for its * `room::name::NameEventContent` now uses a custom `RoomNameBox` type for its
`name` field and makes it public, in response the constructor and `name` `name` field and makes it public, in response the constructor and `name`
accessor had their types updated too accessor had their types updated too
* Replace `InvalidEvent` by a more specific `FromStringError` for room name * Replace `InvalidEvent` by a more specific `FromStringError` for room name

View File

@ -1,10 +1,8 @@
//! Types for the *m.room.name* event. //! Types for the *m.room.name* event.
use std::convert::TryFrom;
use ruma_events_macros::EventContent; use ruma_events_macros::EventContent;
use ruma_identifiers::{RoomName, RoomNameBox};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::StateEvent; use crate::StateEvent;
@ -18,76 +16,22 @@ pub type NameEvent = StateEvent<NameEventContent>;
pub struct NameEventContent { pub struct NameEventContent {
/// The name of the room. /// The name of the room.
#[serde(default, deserialize_with = "ruma_serde::empty_string_as_none")] #[serde(default, deserialize_with = "ruma_serde::empty_string_as_none")]
pub name: Option<RoomName>, pub name: Option<RoomNameBox>,
} }
impl NameEventContent { impl NameEventContent {
/// Create a new `NameEventContent` with the given name. /// Create a new `NameEventContent` with the given name.
pub fn new(name: Option<RoomName>) -> Self { pub fn new(name: Option<RoomNameBox>) -> Self {
Self { name } Self { name }
} }
/// The name of the room, if any. /// The name of the room, if any.
#[deprecated = "You can access the name field directly."] #[deprecated = "You can access the name field directly."]
pub fn name(&self) -> Option<&RoomName> { pub fn name(&self) -> Option<&RoomName> {
self.name.as_ref() self.name.as_deref()
} }
} }
/// The name of a room.
///
/// It can't exceed 255 characters or be empty.
#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
#[serde(transparent)]
pub struct RoomName(String);
impl TryFrom<String> for RoomName {
type Error = FromStringError;
fn try_from(value: String) -> Result<Self, Self::Error> {
match value.len() {
0 => Err(FromStringError::Empty),
1..=255 => Ok(RoomName(value)),
_ => Err(FromStringError::TooLong),
}
}
}
impl From<RoomName> for String {
fn from(name: RoomName) -> Self {
name.0
}
}
impl<'de> Deserialize<'de> for RoomName {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
let str_name = String::deserialize(deserializer)?;
match RoomName::try_from(str_name) {
Ok(name) => Ok(name),
Err(e) => Err(D::Error::custom(e)),
}
}
}
/// Errors that can occur when converting a string to `RoomName`.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum FromStringError {
/// Room name string was empty.
#[error("room name may not be empty")]
Empty,
/// Room name string exceeded 255 byte limit.
#[error("room name length may not exceed 255 bytes")]
TooLong,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::convert::TryFrom; use std::convert::TryFrom;
@ -95,17 +39,17 @@ mod tests {
use js_int::{int, uint}; use js_int::{int, uint};
use matches::assert_matches; use matches::assert_matches;
use ruma_common::MilliSecondsSinceUnixEpoch; use ruma_common::MilliSecondsSinceUnixEpoch;
use ruma_identifiers::{event_id, room_id, user_id}; use ruma_identifiers::{event_id, room_id, user_id, RoomNameBox};
use ruma_serde::Raw; use ruma_serde::Raw;
use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
use super::NameEventContent; use super::NameEventContent;
use crate::{room::name::RoomName, StateEvent, Unsigned}; use crate::{StateEvent, Unsigned};
#[test] #[test]
fn serialization_with_optional_fields_as_none() { fn serialization_with_optional_fields_as_none() {
let name_event = StateEvent { let name_event = StateEvent {
content: NameEventContent { name: RoomName::try_from("The room name".to_owned()).ok() }, content: NameEventContent { name: RoomNameBox::try_from("The room name").ok() },
event_id: event_id!("$h29iv0s8:example.com"), event_id: event_id!("$h29iv0s8:example.com"),
origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(1)), origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(1)),
prev_content: None, prev_content: None,
@ -134,11 +78,11 @@ mod tests {
#[test] #[test]
fn serialization_with_all_fields() { fn serialization_with_all_fields() {
let name_event = StateEvent { let name_event = StateEvent {
content: NameEventContent { name: RoomName::try_from("The room name".to_owned()).ok() }, content: NameEventContent { name: RoomNameBox::try_from("The room name").ok() },
event_id: event_id!("$h29iv0s8:example.com"), event_id: event_id!("$h29iv0s8:example.com"),
origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(1)), origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(1)),
prev_content: Some(NameEventContent { prev_content: Some(NameEventContent {
name: RoomName::try_from("The old name".to_owned()).ok(), name: RoomNameBox::try_from("The old name").ok(),
}), }),
room_id: room_id!("!n8f893n9:example.com"), room_id: room_id!("!n8f893n9:example.com"),
sender: user_id!("@carl:example.com"), sender: user_id!("@carl:example.com"),
@ -211,7 +155,7 @@ mod tests {
#[test] #[test]
fn new_with_empty_name_creates_content_as_none() { fn new_with_empty_name_creates_content_as_none() {
assert_matches!( assert_matches!(
NameEventContent::new(RoomName::try_from(String::new()).ok()), NameEventContent::new(RoomNameBox::try_from(String::new()).ok()),
NameEventContent { name: None } NameEventContent { name: None }
); );
} }
@ -266,7 +210,7 @@ mod tests {
#[test] #[test]
fn nonempty_field_as_some() { fn nonempty_field_as_some() {
let name = RoomName::try_from("The room name".to_owned()).ok(); let name = RoomNameBox::try_from("The room name").ok();
let json_data = json!({ let json_data = json!({
"content": { "content": {
"name": "The room name" "name": "The room name"

View File

@ -1,7 +1,8 @@
use std::convert::TryFrom; use std::convert::TryFrom;
use matches::assert_matches; use matches::assert_matches;
use ruma_events::{room::name::RoomName, AnyInitialStateEvent, InitialStateEvent}; use ruma_events::{AnyInitialStateEvent, InitialStateEvent};
use ruma_identifiers::RoomNameBox;
use serde_json::json; use serde_json::json;
#[test] #[test]
@ -13,7 +14,7 @@ fn deserialize_initial_state_event() {
})) }))
.unwrap(), .unwrap(),
AnyInitialStateEvent::RoomName(InitialStateEvent { content, state_key}) AnyInitialStateEvent::RoomName(InitialStateEvent { content, state_key})
if content.name == Some(RoomName::try_from("foo".to_owned()).unwrap()) if content.name == Some(RoomNameBox::try_from("foo").unwrap())
&& state_key.is_empty() && state_key.is_empty()
); );
} }

View File

@ -2,10 +2,10 @@ use std::convert::TryFrom;
use js_int::uint; use js_int::uint;
use ruma_events::{ use ruma_events::{
room::{join_rules::JoinRule, name::RoomName, topic::TopicEventContent}, room::{join_rules::JoinRule, topic::TopicEventContent},
AnyStateEventContent, AnyStrippedStateEvent, StrippedStateEvent, AnyStateEventContent, AnyStrippedStateEvent, StrippedStateEvent,
}; };
use ruma_identifiers::{mxc_uri, user_id}; use ruma_identifiers::{mxc_uri, user_id, RoomNameBox};
use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
#[test] #[test]
@ -96,7 +96,7 @@ fn deserialize_stripped_state_events() {
let event = from_json_value::<AnyStrippedStateEvent>(name_event).unwrap(); let event = from_json_value::<AnyStrippedStateEvent>(name_event).unwrap();
match event { match event {
AnyStrippedStateEvent::RoomName(event) => { AnyStrippedStateEvent::RoomName(event) => {
assert_eq!(event.content.name, Some(RoomName::try_from("Ruma".to_owned()).unwrap())); assert_eq!(event.content.name, Some(RoomNameBox::try_from("Ruma").unwrap()));
assert_eq!(event.state_key, ""); assert_eq!(event.state_key, "");
assert_eq!(event.sender.to_string(), "@example:localhost"); assert_eq!(event.sender.to_string(), "@example:localhost");
} }

View File

@ -9,6 +9,9 @@ pub enum Error {
/// The client secret is empty. /// The client secret is empty.
EmptyClientSecret, EmptyClientSecret,
/// The room name is empty.
EmptyRoomName,
/// The room version ID is empty. /// The room version ID is empty.
EmptyRoomVersionId, EmptyRoomVersionId,
@ -44,6 +47,7 @@ impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let message = match self { let message = match self {
Error::EmptyClientSecret => "client secret is empty", Error::EmptyClientSecret => "client secret is empty",
Error::EmptyRoomName => "room name is empty",
Error::EmptyRoomVersionId => "room version ID is empty", Error::EmptyRoomVersionId => "room version ID is empty",
Error::InvalidCharacters => "localpart contains invalid characters", Error::InvalidCharacters => "localpart contains invalid characters",
Error::InvalidKeyAlgorithm => "invalid key algorithm specified", Error::InvalidKeyAlgorithm => "invalid key algorithm specified",

View File

@ -10,6 +10,7 @@ pub mod mxc_uri;
pub mod room_alias_id; pub mod room_alias_id;
pub mod room_id; pub mod room_id;
pub mod room_id_or_alias_id; pub mod room_id_or_alias_id;
pub mod room_name;
pub mod room_version_id; pub mod room_version_id;
pub mod server_name; pub mod server_name;
pub mod session_id; pub mod session_id;

View File

@ -0,0 +1,9 @@
use crate::Error;
pub fn validate(value: &str) -> Result<(), Error> {
match value.len() {
0 => Err(Error::EmptyRoomName),
1..=255 => Ok(()),
_ => Err(Error::MaximumLengthExceeded),
}
}

View File

@ -1,5 +1,9 @@
# [unreleased] # [unreleased]
Improvements:
* Add `RoomName`, `RoomNameBox` types
# 0.19.4 # 0.19.4
Improvements: Improvements:

View File

@ -31,6 +31,7 @@ pub use crate::{
room_alias_id::RoomAliasId, room_alias_id::RoomAliasId,
room_id::RoomId, room_id::RoomId,
room_id_or_room_alias_id::RoomIdOrAliasId, room_id_or_room_alias_id::RoomIdOrAliasId,
room_name::{RoomName, RoomNameBox},
room_version_id::RoomVersionId, room_version_id::RoomVersionId,
server_name::{ServerName, ServerNameBox}, server_name::{ServerName, ServerNameBox},
session_id::{SessionId, SessionIdBox}, session_id::{SessionId, SessionIdBox},
@ -55,6 +56,7 @@ mod opaque_ids;
mod room_alias_id; mod room_alias_id;
mod room_id; mod room_id;
mod room_id_or_room_alias_id; mod room_id_or_room_alias_id;
mod room_name;
mod room_version_id; mod room_version_id;
mod server_name; mod server_name;
mod session_id; mod session_id;

View File

@ -402,7 +402,14 @@ macro_rules! opaque_identifier_validated {
where where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
{ {
Box::<str>::deserialize(deserializer).map($id::from_owned) use serde::de::Error;
let s = String::deserialize(deserializer)?;
match try_from(s) {
Ok(o) => Ok(o),
Err(e) => Err(D::Error::custom(e)),
}
} }
} }

View File

@ -0,0 +1,10 @@
//! Matrix room name.
use ruma_identifiers_validation::room_name::validate;
opaque_identifier_validated! {
/// The name of a room.
///
/// It can't exceed 255 bytes or be empty.
pub type RoomName [ validate ];
}

View File

@ -2,7 +2,7 @@
use ruma_api::ruma_api; use ruma_api::ruma_api;
use ruma_common::thirdparty::Medium; use ruma_common::thirdparty::Medium;
use ruma_identifiers::{MxcUri, RoomAliasId, RoomId, UserId}; use ruma_identifiers::{MxcUri, RoomAliasId, RoomId, RoomName, UserId};
use serde::{ser::SerializeSeq, Deserialize, Serialize}; use serde::{ser::SerializeSeq, Deserialize, Serialize};
ruma_api! { ruma_api! {
@ -52,7 +52,7 @@ ruma_api! {
/// ///
/// This should be retrieved from the `m.room.name` state event. /// This should be retrieved from the `m.room.name` state event.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub room_name: Option<&'a str>, pub room_name: Option<&'a RoomName>,
/// The display name of the user ID initiating the invite. /// The display name of the user ID initiating the invite.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]

View File

@ -7,7 +7,7 @@ use ruma_common::{
SecondsSinceUnixEpoch, SecondsSinceUnixEpoch,
}; };
use ruma_events::EventType; use ruma_events::EventType;
use ruma_identifiers::{EventId, RoomAliasId, RoomId, UserId}; use ruma_identifiers::{EventId, RoomAliasId, RoomId, RoomName, UserId};
use ruma_serde::{Outgoing, StringEnum}; use ruma_serde::{Outgoing, StringEnum};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::value::RawValue as RawJsonValue; use serde_json::value::RawValue as RawJsonValue;
@ -90,7 +90,7 @@ pub struct Notification<'a> {
/// The name of the room in which the event occurred. /// The name of the room in which the event occurred.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub room_name: Option<&'a str>, pub room_name: Option<&'a RoomName>,
/// An alias to display for the room in which the event occurred. /// An alias to display for the room in which the event occurred.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]

View File

@ -264,6 +264,7 @@ fn strip_lifetimes(field_type: &mut Type) -> bool {
|| last_seg.ident == "ServerName" || last_seg.ident == "ServerName"
|| last_seg.ident == "SessionId" || last_seg.ident == "SessionId"
|| last_seg.ident == "RawJsonValue" || last_seg.ident == "RawJsonValue"
|| 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.
Some(parse_quote! { ::std::boxed::Box<#path> }) Some(parse_quote! { ::std::boxed::Box<#path> })