events: Add RoomName struct to room::name
This commit is contained in:
parent
ab3d48b576
commit
976f90a1a2
@ -1,5 +1,7 @@
|
|||||||
//! 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 serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -11,51 +13,70 @@ pub type NameEvent = StateEvent<NameEventContent>;
|
|||||||
/// The payload for `NameEvent`.
|
/// The payload for `NameEvent`.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
|
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
|
||||||
#[ruma_event(type = "m.room.name", kind = State)]
|
#[ruma_event(type = "m.room.name", kind = State)]
|
||||||
|
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||||
pub struct NameEventContent {
|
pub struct NameEventContent {
|
||||||
/// The name of the room. This MUST NOT exceed 255 bytes.
|
/// The name of the room.
|
||||||
#[serde(default, deserialize_with = "room_name")]
|
#[serde(default, deserialize_with = "ruma_serde::empty_string_as_none")]
|
||||||
pub(crate) name: Option<String>,
|
pub name: Option<RoomName>,
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
/// # Errors
|
Self { name }
|
||||||
///
|
|
||||||
/// `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())),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The name of the room, if any.
|
/// The name of the room, if any.
|
||||||
pub fn name(&self) -> Option<&str> {
|
pub fn name(&self) -> Option<&RoomName> {
|
||||||
self.name.as_deref()
|
self.name.as_ref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn room_name<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
|
/// The name of a room.
|
||||||
where
|
///
|
||||||
D: serde::de::Deserializer<'de>,
|
/// It should not exceed 255 characters and should not be empty.
|
||||||
{
|
#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
|
||||||
use serde::de::Error;
|
#[serde(transparent)]
|
||||||
|
pub struct RoomName(String);
|
||||||
|
|
||||||
// this handles the null case and the empty string or nothing case
|
impl TryFrom<String> for RoomName {
|
||||||
match Option::<String>::deserialize(deserializer)? {
|
type Error = InvalidInput;
|
||||||
Some(name) => match name.len() {
|
|
||||||
0 => Ok(None),
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
1..=255 => Ok(Some(name)),
|
match value.len() {
|
||||||
_ => Err(D::Error::custom("a room name cannot be more than 255 bytes")),
|
0 => Err(InvalidInput("a room name cannot be empty.".into())),
|
||||||
},
|
1..=255 => Ok(RoomName(value)),
|
||||||
None => Ok(None),
|
_ => 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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
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;
|
||||||
@ -64,12 +85,12 @@ mod tests {
|
|||||||
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::{StateEvent, Unsigned};
|
use crate::{room::name::RoomName, 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: Some("The room name".into()) },
|
content: NameEventContent { name: RoomName::try_from("The room name".to_owned()).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,
|
||||||
@ -98,10 +119,12 @@ 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: Some("The room name".into()) },
|
content: NameEventContent { name: RoomName::try_from("The room name".to_owned()).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 { name: Some("The old name".into()) }),
|
prev_content: Some(NameEventContent {
|
||||||
|
name: RoomName::try_from("The old name".to_owned()).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"),
|
||||||
state_key: "".into(),
|
state_key: "".into(),
|
||||||
@ -173,7 +196,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(String::new()).unwrap(),
|
NameEventContent::new(RoomName::try_from(String::new()).ok()),
|
||||||
NameEventContent { name: None }
|
NameEventContent { name: None }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -228,7 +251,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn nonempty_field_as_some() {
|
fn nonempty_field_as_some() {
|
||||||
let name = Some("The room name".into());
|
let name = RoomName::try_from("The room name".to_owned()).ok();
|
||||||
let json_data = json!({
|
let json_data = json!({
|
||||||
"content": {
|
"content": {
|
||||||
"name": "The room name"
|
"name": "The room name"
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use matches::assert_matches;
|
use matches::assert_matches;
|
||||||
use ruma_events::{AnyInitialStateEvent, InitialStateEvent};
|
use ruma_events::{room::name::RoomName, AnyInitialStateEvent, InitialStateEvent};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -11,6 +13,6 @@ fn deserialize_initial_state_event() {
|
|||||||
}))
|
}))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
AnyInitialStateEvent::RoomName(InitialStateEvent { content, state_key})
|
AnyInitialStateEvent::RoomName(InitialStateEvent { content, state_key})
|
||||||
if content.name() == Some("foo") && state_key.is_empty()
|
if content.name() == RoomName::try_from("foo".to_owned()).ok().as_ref() && state_key.is_empty()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use js_int::uint;
|
use js_int::uint;
|
||||||
use ruma_events::{
|
use ruma_events::{
|
||||||
room::{join_rules::JoinRule, topic::TopicEventContent},
|
room::{join_rules::JoinRule, name::RoomName, topic::TopicEventContent},
|
||||||
AnyStateEventContent, AnyStrippedStateEvent, StrippedStateEvent,
|
AnyStateEventContent, AnyStrippedStateEvent, StrippedStateEvent,
|
||||||
};
|
};
|
||||||
use ruma_identifiers::{mxc_uri, user_id};
|
use ruma_identifiers::{mxc_uri, user_id};
|
||||||
@ -94,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("Ruma"));
|
assert_eq!(event.content.name(), RoomName::try_from("Ruma".to_owned()).ok().as_ref());
|
||||||
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");
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user