Skip to content

Commit

Permalink
Add RoomName struct to ruma_events::room::name.
Browse files Browse the repository at this point in the history
  • Loading branch information
Frinksy committed Jun 25, 2021
1 parent 0846d7a commit 46f8a1b
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 39 deletions.
98 changes: 63 additions & 35 deletions crates/ruma-events/src/room/name.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Types for the *m.room.name* event.

use std::convert::TryFrom;

use ruma_events_macros::EventContent;
use serde::{Deserialize, Serialize};

Expand All @@ -11,51 +13,71 @@ pub type NameEvent = StateEvent<NameEventContent>;
/// The payload for `NameEvent`.
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
#[ruma_event(type = "m.room.name", kind = State)]
#[non_exhaustive]
pub struct NameEventContent {
/// The name of the room. This MUST NOT exceed 255 bytes.
#[serde(default, deserialize_with = "room_name")]
pub(crate) name: Option<String>,
/// The name of the room.
#[serde(default, deserialize_with = "ruma_serde::empty_string_as_none")]
pub name: Option<RoomName>,
}

impl NameEventContent {
/// Create a new `NameEventContent` with the given name.
///
/// # Errors
///
/// `InvalidInput` will be returned if the name is more than 255 bytes.
pub fn new(name: String) -> Result<Self, InvalidInput> {
match name.len() {
0 => Ok(Self { name: None }),
1..=255 => Ok(Self { name: Some(name) }),
_ => Err(InvalidInput("a room name cannot be more than 255 bytes".into())),
}
pub fn new(name: Option<RoomName>) -> Self {
Self { name }
}

/// The name of the room, if any.
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
pub fn name(&self) -> &Option<RoomName> {
&self.name
}
}

fn room_name<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: serde::de::Deserializer<'de>,
{
use serde::de::Error;

// this handles the null case and the empty string or nothing case
match Option::<String>::deserialize(deserializer)? {
Some(name) => match name.len() {
0 => Ok(None),
1..=255 => Ok(Some(name)),
_ => Err(D::Error::custom("a room name cannot be more than 255 bytes")),
},
None => Ok(None),
/// The name of a room.
///
/// It should not exceed 255 characters and should not be empty.
#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
#[serde(transparent)]
#[allow(clippy::exhaustive_structs)]
pub struct RoomName(String);

impl TryFrom<String> for RoomName {
type Error = InvalidInput;

fn try_from(value: String) -> Result<Self, Self::Error> {
match value.len() {
0 => Err(InvalidInput("a room name cannot be empty.".into())),
1..=255 => Ok(RoomName(value)),
_ => Err(InvalidInput("a room name cannot be more than 255 bytes.".into())),
}
}
}

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.to_string())),
}
}
}

#[cfg(test)]
mod tests {
use std::convert::TryFrom;

use js_int::{int, uint};
use matches::assert_matches;
use ruma_common::MilliSecondsSinceUnixEpoch;
Expand All @@ -64,12 +86,14 @@ mod tests {
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};

use super::NameEventContent;
use crate::{StateEvent, Unsigned};
use crate::{room::name::RoomName, StateEvent, Unsigned};

#[test]
fn serialization_with_optional_fields_as_none() {
let name_event = StateEvent {
content: NameEventContent { name: Some("The room name".into()) },
content: NameEventContent {
name: RoomName::try_from("The room name".to_string()).ok(),
},
event_id: event_id!("$h29iv0s8:example.com"),
origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(1)),
prev_content: None,
Expand Down Expand Up @@ -98,10 +122,14 @@ mod tests {
#[test]
fn serialization_with_all_fields() {
let name_event = StateEvent {
content: NameEventContent { name: Some("The room name".into()) },
content: NameEventContent {
name: RoomName::try_from("The room name".to_string()).ok(),
},
event_id: event_id!("$h29iv0s8:example.com"),
origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(1)),
prev_content: Some(NameEventContent { name: Some("The old name".into()) }),
prev_content: Some(NameEventContent {
name: RoomName::try_from("The old name".to_string()).ok(),
}),
room_id: room_id!("!n8f893n9:example.com"),
sender: user_id!("@carl:example.com"),
state_key: "".into(),
Expand Down Expand Up @@ -173,7 +201,7 @@ mod tests {
#[test]
fn new_with_empty_name_creates_content_as_none() {
assert_matches!(
NameEventContent::new(String::new()).unwrap(),
NameEventContent::new(RoomName::try_from(String::new()).ok()),
NameEventContent { name: None }
);
}
Expand Down Expand Up @@ -228,7 +256,7 @@ mod tests {

#[test]
fn nonempty_field_as_some() {
let name = Some("The room name".into());
let name = RoomName::try_from("The room name".to_string()).ok();
let json_data = json!({
"content": {
"name": "The room name"
Expand Down
6 changes: 4 additions & 2 deletions crates/ruma-events/tests/initial_state.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::convert::TryFrom;

use matches::assert_matches;
use ruma_events::{AnyInitialStateEvent, InitialStateEvent};
use ruma_events::{room::name::RoomName, AnyInitialStateEvent, InitialStateEvent};
use serde_json::json;

#[test]
Expand All @@ -11,6 +13,6 @@ fn deserialize_initial_state_event() {
}))
.unwrap(),
AnyInitialStateEvent::RoomName(InitialStateEvent { content, state_key})
if content.name() == Some("foo") && state_key.is_empty()
if content.name() == &RoomName::try_from("foo".to_string()).ok() && state_key.is_empty()
);
}
6 changes: 4 additions & 2 deletions crates/ruma-events/tests/stripped.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::convert::TryFrom;

use js_int::uint;
use ruma_events::{
room::{join_rules::JoinRule, topic::TopicEventContent},
room::{join_rules::JoinRule, name::RoomName, topic::TopicEventContent},
AnyStateEventContent, AnyStrippedStateEvent, StrippedStateEvent,
};
use ruma_identifiers::{mxc_uri, user_id};
Expand Down Expand Up @@ -94,7 +96,7 @@ fn deserialize_stripped_state_events() {
let event = from_json_value::<AnyStrippedStateEvent>(name_event).unwrap();
match event {
AnyStrippedStateEvent::RoomName(event) => {
assert_eq!(event.content.name(), Some("Ruma"));
assert_eq!(event.content.name(), &RoomName::try_from("Ruma".to_string()).ok());
assert_eq!(event.state_key, "");
assert_eq!(event.sender.to_string(), "@example:localhost");
}
Expand Down

0 comments on commit 46f8a1b

Please sign in to comment.