From 63411165da3b5b4597ec3290049a97cb550359d7 Mon Sep 17 00:00:00 2001 From: Adam <13720823+Frinksy@users.noreply.github.com> Date: Mon, 19 Jul 2021 21:00:44 +0100 Subject: [PATCH] Move `RoomName` to `ruma-identifiers` and use it more --- crates/ruma-client-api/src/r0/directory.rs | 4 +- .../src/r0/room/create_room.rs | 4 +- crates/ruma-common/src/directory.rs | 4 +- crates/ruma-events/CHANGELOG.md | 2 +- crates/ruma-events/src/room/name.rs | 78 +++---------------- crates/ruma-events/tests/initial_state.rs | 5 +- crates/ruma-events/tests/stripped.rs | 6 +- .../ruma-identifiers-validation/src/error.rs | 4 + crates/ruma-identifiers-validation/src/lib.rs | 1 + .../src/room_name.rs | 9 +++ crates/ruma-identifiers/CHANGELOG.md | 4 + crates/ruma-identifiers/src/lib.rs | 2 + crates/ruma-identifiers/src/macros.rs | 9 ++- crates/ruma-identifiers/src/room_name.rs | 10 +++ .../src/invitation/store_invitation/v2.rs | 4 +- .../src/send_event_notification/v1.rs | 4 +- crates/ruma-serde-macros/src/outgoing.rs | 1 + 17 files changed, 67 insertions(+), 84 deletions(-) create mode 100644 crates/ruma-identifiers-validation/src/room_name.rs create mode 100644 crates/ruma-identifiers/src/room_name.rs diff --git a/crates/ruma-client-api/src/r0/directory.rs b/crates/ruma-client-api/src/r0/directory.rs index 3b5967fe..49afbd1e 100644 --- a/crates/ruma-client-api/src/r0/directory.rs +++ b/crates/ruma-client-api/src/r0/directory.rs @@ -8,7 +8,7 @@ pub mod set_room_visibility; use js_int::{uint, UInt}; #[cfg(feature = "unstable-pre-spec")] use ruma_events::room::join_rules::JoinRule; -use ruma_identifiers::{MxcUri, RoomAliasId, RoomId}; +use ruma_identifiers::{MxcUri, RoomAliasId, RoomId, RoomNameBox}; use serde::{Deserialize, Serialize}; /// A chunk of a room list response, describing one room @@ -25,7 +25,7 @@ pub struct PublicRoomsChunk { /// The name of the room, if any. #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, + pub name: Option, /// The number of members joined to the room. pub num_joined_members: UInt, diff --git a/crates/ruma-client-api/src/r0/room/create_room.rs b/crates/ruma-client-api/src/r0/room/create_room.rs index 3ef528ec..9ea754e4 100644 --- a/crates/ruma-client-api/src/r0/room/create_room.rs +++ b/crates/ruma-client-api/src/r0/room/create_room.rs @@ -11,7 +11,7 @@ use ruma_events::{ }, AnyInitialStateEvent, }; -use ruma_identifiers::{RoomId, RoomVersionId, UserId}; +use ruma_identifiers::{RoomId, RoomName, RoomVersionId, UserId}; use ruma_serde::{Raw, StringEnum}; 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 /// the name of the room. #[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. #[serde(skip_serializing_if = "Option::is_none")] diff --git a/crates/ruma-common/src/directory.rs b/crates/ruma-common/src/directory.rs index 1a94c8bc..20b1e651 100644 --- a/crates/ruma-common/src/directory.rs +++ b/crates/ruma-common/src/directory.rs @@ -3,7 +3,7 @@ use std::fmt; use js_int::UInt; -use ruma_identifiers::{MxcUri, RoomAliasId, RoomId}; +use ruma_identifiers::{MxcUri, RoomAliasId, RoomId, RoomNameBox}; use ruma_serde::Outgoing; use serde::{ de::{Error, MapAccess, Visitor}, @@ -29,7 +29,7 @@ pub struct PublicRoomsChunk { /// The name of the room, if any. #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, + pub name: Option, /// The number of members joined to the room. pub num_joined_members: UInt, diff --git a/crates/ruma-events/CHANGELOG.md b/crates/ruma-events/CHANGELOG.md index 227c21d9..f3b6b32b 100644 --- a/crates/ruma-events/CHANGELOG.md +++ b/crates/ruma-events/CHANGELOG.md @@ -2,7 +2,7 @@ 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` accessor had their types updated too * Replace `InvalidEvent` by a more specific `FromStringError` for room name diff --git a/crates/ruma-events/src/room/name.rs b/crates/ruma-events/src/room/name.rs index 3d57da88..fef3cb08 100644 --- a/crates/ruma-events/src/room/name.rs +++ b/crates/ruma-events/src/room/name.rs @@ -1,10 +1,8 @@ //! Types for the *m.room.name* event. -use std::convert::TryFrom; - use ruma_events_macros::EventContent; +use ruma_identifiers::{RoomName, RoomNameBox}; use serde::{Deserialize, Serialize}; -use thiserror::Error; use crate::StateEvent; @@ -18,76 +16,22 @@ pub type NameEvent = StateEvent; pub struct NameEventContent { /// The name of the room. #[serde(default, deserialize_with = "ruma_serde::empty_string_as_none")] - pub name: Option, + pub name: Option, } impl NameEventContent { /// Create a new `NameEventContent` with the given name. - pub fn new(name: Option) -> Self { + pub fn new(name: Option) -> Self { Self { name } } /// The name of the room, if any. #[deprecated = "You can access the name field directly."] 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 for RoomName { - type Error = FromStringError; - - fn try_from(value: String) -> Result { - match value.len() { - 0 => Err(FromStringError::Empty), - 1..=255 => Ok(RoomName(value)), - _ => Err(FromStringError::TooLong), - } - } -} - -impl From for String { - fn from(name: RoomName) -> Self { - name.0 - } -} - -impl<'de> Deserialize<'de> for RoomName { - fn deserialize(deserializer: D) -> Result - 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)] mod tests { use std::convert::TryFrom; @@ -95,17 +39,17 @@ mod tests { use js_int::{int, uint}; use matches::assert_matches; 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 serde_json::{from_value as from_json_value, json, to_value as to_json_value}; use super::NameEventContent; - use crate::{room::name::RoomName, StateEvent, Unsigned}; + use crate::{StateEvent, Unsigned}; #[test] fn serialization_with_optional_fields_as_none() { 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"), origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(1)), prev_content: None, @@ -134,11 +78,11 @@ mod tests { #[test] fn serialization_with_all_fields() { 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"), origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(1)), 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"), sender: user_id!("@carl:example.com"), @@ -211,7 +155,7 @@ mod tests { #[test] fn new_with_empty_name_creates_content_as_none() { assert_matches!( - NameEventContent::new(RoomName::try_from(String::new()).ok()), + NameEventContent::new(RoomNameBox::try_from(String::new()).ok()), NameEventContent { name: None } ); } @@ -266,7 +210,7 @@ mod tests { #[test] 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!({ "content": { "name": "The room name" diff --git a/crates/ruma-events/tests/initial_state.rs b/crates/ruma-events/tests/initial_state.rs index a2873339..fba822fe 100644 --- a/crates/ruma-events/tests/initial_state.rs +++ b/crates/ruma-events/tests/initial_state.rs @@ -1,7 +1,8 @@ use std::convert::TryFrom; 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; #[test] @@ -13,7 +14,7 @@ fn deserialize_initial_state_event() { })) .unwrap(), 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() ); } diff --git a/crates/ruma-events/tests/stripped.rs b/crates/ruma-events/tests/stripped.rs index 35de335c..70e08ca1 100644 --- a/crates/ruma-events/tests/stripped.rs +++ b/crates/ruma-events/tests/stripped.rs @@ -2,10 +2,10 @@ use std::convert::TryFrom; use js_int::uint; use ruma_events::{ - room::{join_rules::JoinRule, name::RoomName, topic::TopicEventContent}, + room::{join_rules::JoinRule, topic::TopicEventContent}, 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}; #[test] @@ -96,7 +96,7 @@ fn deserialize_stripped_state_events() { let event = from_json_value::(name_event).unwrap(); match 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.sender.to_string(), "@example:localhost"); } diff --git a/crates/ruma-identifiers-validation/src/error.rs b/crates/ruma-identifiers-validation/src/error.rs index 2587feb3..2a2d9aa1 100644 --- a/crates/ruma-identifiers-validation/src/error.rs +++ b/crates/ruma-identifiers-validation/src/error.rs @@ -9,6 +9,9 @@ pub enum Error { /// The client secret is empty. EmptyClientSecret, + /// The room name is empty. + EmptyRoomName, + /// The room version ID is empty. EmptyRoomVersionId, @@ -44,6 +47,7 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let message = match self { Error::EmptyClientSecret => "client secret is empty", + Error::EmptyRoomName => "room name is empty", Error::EmptyRoomVersionId => "room version ID is empty", Error::InvalidCharacters => "localpart contains invalid characters", Error::InvalidKeyAlgorithm => "invalid key algorithm specified", diff --git a/crates/ruma-identifiers-validation/src/lib.rs b/crates/ruma-identifiers-validation/src/lib.rs index 553426a5..ea21417c 100644 --- a/crates/ruma-identifiers-validation/src/lib.rs +++ b/crates/ruma-identifiers-validation/src/lib.rs @@ -10,6 +10,7 @@ pub mod mxc_uri; pub mod room_alias_id; pub mod room_id; pub mod room_id_or_alias_id; +pub mod room_name; pub mod room_version_id; pub mod server_name; pub mod session_id; diff --git a/crates/ruma-identifiers-validation/src/room_name.rs b/crates/ruma-identifiers-validation/src/room_name.rs new file mode 100644 index 00000000..dc1737ff --- /dev/null +++ b/crates/ruma-identifiers-validation/src/room_name.rs @@ -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), + } +} diff --git a/crates/ruma-identifiers/CHANGELOG.md b/crates/ruma-identifiers/CHANGELOG.md index ae69ca85..7e9acd3c 100644 --- a/crates/ruma-identifiers/CHANGELOG.md +++ b/crates/ruma-identifiers/CHANGELOG.md @@ -1,5 +1,9 @@ # [unreleased] +Improvements: + +* Add `RoomName`, `RoomNameBox` types + # 0.19.4 Improvements: diff --git a/crates/ruma-identifiers/src/lib.rs b/crates/ruma-identifiers/src/lib.rs index e93ee536..3f12d099 100644 --- a/crates/ruma-identifiers/src/lib.rs +++ b/crates/ruma-identifiers/src/lib.rs @@ -31,6 +31,7 @@ pub use crate::{ room_alias_id::RoomAliasId, room_id::RoomId, room_id_or_room_alias_id::RoomIdOrAliasId, + room_name::{RoomName, RoomNameBox}, room_version_id::RoomVersionId, server_name::{ServerName, ServerNameBox}, session_id::{SessionId, SessionIdBox}, @@ -55,6 +56,7 @@ mod opaque_ids; mod room_alias_id; mod room_id; mod room_id_or_room_alias_id; +mod room_name; mod room_version_id; mod server_name; mod session_id; diff --git a/crates/ruma-identifiers/src/macros.rs b/crates/ruma-identifiers/src/macros.rs index fd353ecf..ec121f4a 100644 --- a/crates/ruma-identifiers/src/macros.rs +++ b/crates/ruma-identifiers/src/macros.rs @@ -402,7 +402,14 @@ macro_rules! opaque_identifier_validated { where D: serde::Deserializer<'de>, { - Box::::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)), + } } } diff --git a/crates/ruma-identifiers/src/room_name.rs b/crates/ruma-identifiers/src/room_name.rs new file mode 100644 index 00000000..09153c47 --- /dev/null +++ b/crates/ruma-identifiers/src/room_name.rs @@ -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 ]; +} diff --git a/crates/ruma-identity-service-api/src/invitation/store_invitation/v2.rs b/crates/ruma-identity-service-api/src/invitation/store_invitation/v2.rs index 35fb68e6..17f27269 100644 --- a/crates/ruma-identity-service-api/src/invitation/store_invitation/v2.rs +++ b/crates/ruma-identity-service-api/src/invitation/store_invitation/v2.rs @@ -2,7 +2,7 @@ use ruma_api::ruma_api; 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}; ruma_api! { @@ -52,7 +52,7 @@ ruma_api! { /// /// This should be retrieved from the `m.room.name` state event. #[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. #[serde(skip_serializing_if = "Option::is_none")] diff --git a/crates/ruma-push-gateway-api/src/send_event_notification/v1.rs b/crates/ruma-push-gateway-api/src/send_event_notification/v1.rs index 830afe8e..5068e4e5 100644 --- a/crates/ruma-push-gateway-api/src/send_event_notification/v1.rs +++ b/crates/ruma-push-gateway-api/src/send_event_notification/v1.rs @@ -7,7 +7,7 @@ use ruma_common::{ SecondsSinceUnixEpoch, }; 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 serde::{Deserialize, Serialize}; 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. #[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. #[serde(skip_serializing_if = "Option::is_none")] diff --git a/crates/ruma-serde-macros/src/outgoing.rs b/crates/ruma-serde-macros/src/outgoing.rs index d344f54d..c597a3b7 100644 --- a/crates/ruma-serde-macros/src/outgoing.rs +++ b/crates/ruma-serde-macros/src/outgoing.rs @@ -264,6 +264,7 @@ fn strip_lifetimes(field_type: &mut Type) -> bool { || last_seg.ident == "ServerName" || last_seg.ident == "SessionId" || last_seg.ident == "RawJsonValue" + || last_seg.ident == "RoomName" { // The identifiers that need to be boxed `Box` since they are DST's. Some(parse_quote! { ::std::boxed::Box<#path> })