diff --git a/src/room.rs b/src/room.rs index 6acee293..37439530 100644 --- a/src/room.rs +++ b/src/room.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; pub mod aliases; pub mod avatar; -// pub mod canonical_alias; +pub mod canonical_alias; // pub mod create; // pub mod encrypted; pub mod encryption; diff --git a/src/room/canonical_alias.rs b/src/room/canonical_alias.rs index a57fdd5c..7ea8696d 100644 --- a/src/room/canonical_alias.rs +++ b/src/room/canonical_alias.rs @@ -1,43 +1,222 @@ //! Types for the *m.room.canonical_alias* event. +use std::{convert::TryFrom, str::FromStr}; + use js_int::UInt; -use ruma_identifiers::RoomAliasId; -use serde::{Deserialize, Serialize}; +use ruma_identifiers::{EventId, RoomAliasId, RoomId, UserId}; +use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer}; +use serde_json::Value; -use crate::empty_string_as_none; +use crate::{ + empty_string_as_none, Event, EventType, InvalidEvent, InvalidInput, RoomEvent, StateEvent, +}; -state_event! { - /// Informs the room as to which alias is the canonical one. - pub struct CanonicalAliasEvent(CanonicalAliasEventContent) {} +/// Informs the room as to which alias is the canonical one. +#[derive(Clone, Debug, PartialEq)] +pub struct CanonicalAliasEvent { + /// The event's content. + pub content: CanonicalAliasEventContent, + + /// The unique identifier for the event. + pub event_id: EventId, + + /// Timestamp (milliseconds since the UNIX epoch) on originating homeserver when this + /// event was sent. + pub origin_server_ts: UInt, + + /// The previous content for this state key, if any. + pub prev_content: Option, + + /// The unique identifier for the room associated with this event. + pub room_id: Option, + + /// The unique identifier for the user who sent this event. + pub sender: UserId, + + /// A key that determines which piece of room state the event represents. + pub state_key: String, + + /// Additional key-value pairs not signed by the homeserver. + pub unsigned: Option, } /// The payload of a `CanonicalAliasEvent`. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, PartialEq, Serialize)] pub struct CanonicalAliasEventContent { /// The canonical alias. + /// /// Rooms with `alias: None` should be treated the same as a room with no canonical alias. - // The spec says “A room with an m.room.canonical_alias event with an absent, null, or empty alias field - // should be treated the same as a room with no m.room.canonical_alias event.”. - // Serde maps null fields to None by default, serde(default) maps an absent field to None, - // and empty_string_as_none does exactly that, preventing empty strings getting parsed as RoomAliasId. - #[serde(default)] - #[serde(deserialize_with = "empty_string_as_none")] pub alias: Option, } +impl FromStr for CanonicalAliasEvent { + type Err = InvalidEvent; + + /// Attempt to create `Self` from parsing a string of JSON data. + fn from_str(json: &str) -> Result { + let raw = serde_json::from_str::(json)?; + + Ok(Self { + content: CanonicalAliasEventContent { + alias: raw.content.alias, + }, + event_id: raw.event_id, + origin_server_ts: raw.origin_server_ts, + prev_content: raw + .prev_content + .map(|prev| CanonicalAliasEventContent { alias: prev.alias }), + room_id: raw.room_id, + sender: raw.sender, + state_key: raw.state_key, + unsigned: raw.unsigned, + }) + } +} + +impl<'a> TryFrom<&'a str> for CanonicalAliasEvent { + type Error = InvalidEvent; + + /// Attempt to create `Self` from parsing a string of JSON data. + fn try_from(json: &'a str) -> Result { + FromStr::from_str(json) + } +} + +impl Serialize for CanonicalAliasEvent { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut len = 6; + + if self.prev_content.is_some() { + len += 1; + } + + if self.room_id.is_some() { + len += 1; + } + + if self.unsigned.is_some() { + len += 1; + } + + let mut state = serializer.serialize_struct("CanonicalAliasEvent", len)?; + + state.serialize_field("content", &self.content)?; + state.serialize_field("event_id", &self.event_id)?; + state.serialize_field("origin_server_ts", &self.origin_server_ts)?; + + if self.prev_content.is_some() { + state.serialize_field("prev_content", &self.prev_content)?; + } + + if self.room_id.is_some() { + state.serialize_field("room_id", &self.room_id)?; + } + + if self.unsigned.is_some() { + state.serialize_field("unsigned", &self.unsigned)?; + } + + state.serialize_field("sender", &self.sender)?; + state.serialize_field("state_key", &self.state_key)?; + state.serialize_field("type", &self.event_type())?; + + state.end() + } +} + +impl_state_event!( + CanonicalAliasEvent, + CanonicalAliasEventContent, + EventType::RoomCanonicalAlias +); + +mod raw { + use super::*; + + /// Informs the room as to which alias is the canonical one. + #[derive(Clone, Debug, Deserialize, PartialEq)] + pub struct CanonicalAliasEvent { + /// The event's content. + pub content: CanonicalAliasEventContent, + + /// The unique identifier for the event. + pub event_id: EventId, + + /// Timestamp (milliseconds since the UNIX epoch) on originating homeserver when this + /// event was sent. + pub origin_server_ts: UInt, + + /// The previous content for this state key, if any. + pub prev_content: Option, + + /// The unique identifier for the room associated with this event. + pub room_id: Option, + + /// The unique identifier for the user who sent this event. + pub sender: UserId, + + /// A key that determines which piece of room state the event represents. + pub state_key: String, + + /// Additional key-value pairs not signed by the homeserver. + pub unsigned: Option, + } + + /// The payload of a `CanonicalAliasEvent`. + #[derive(Clone, Debug, Deserialize, PartialEq)] + pub struct CanonicalAliasEventContent { + /// The canonical alias. + /// + /// Rooms with `alias: None` should be treated the same as a room with no canonical alias. + // The spec says "A room with an m.room.canonical_alias event with an absent, null, or empty + // alias field should be treated the same as a room with no m.room.canonical_alias event." + #[serde(default)] + #[serde(deserialize_with = "empty_string_as_none")] + pub alias: Option, + } +} + #[cfg(test)] mod tests { - use serde_json::from_str; - - use super::CanonicalAliasEventContent; - use ruma_identifiers::RoomAliasId; use std::convert::TryFrom; + use js_int::UInt; + use ruma_identifiers::{EventId, RoomAliasId, RoomId, UserId}; + use serde_json::Value; + + use super::{CanonicalAliasEvent, CanonicalAliasEventContent}; + + #[test] + fn serialization_with_optional_fields_as_none() { + let canonical_alias_event = CanonicalAliasEvent { + content: CanonicalAliasEventContent { + alias: Some(RoomAliasId::try_from("#somewhere:localhost").unwrap()), + }, + event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(), + origin_server_ts: UInt::try_from(1).unwrap(), + prev_content: None, + room_id: None, + sender: UserId::try_from("@carl:example.com").unwrap(), + state_key: "".to_string(), + unsigned: None, + }; + + let actual = serde_json::to_string(&canonical_alias_event).unwrap(); + let expected = r##"{"content":{"alias":"#somewhere:localhost"},"event_id":"$h29iv0s8:example.com","origin_server_ts":1,"sender":"@carl:example.com","state_key":"","type":"m.room.canonical_alias"}"##; + + assert_eq!(actual, expected); + } + #[test] fn absent_field_as_none() { assert_eq!( - from_str::(r#"{}"#) + r#"{"content":{},"event_id":"$h29iv0s8:example.com","origin_server_ts":1,"sender":"@carl:example.com","state_key":"","type":"m.room.canonical_alias"}"# + .parse::() .unwrap() + .content .alias, None ); @@ -46,8 +225,10 @@ mod tests { #[test] fn null_field_as_none() { assert_eq!( - from_str::(r#"{"alias":null}"#) + r#"{"content":{"alias":null},"event_id":"$h29iv0s8:example.com","origin_server_ts":1,"sender":"@carl:example.com","state_key":"","type":"m.room.canonical_alias"}"# + .parse::() .unwrap() + .content .alias, None ); @@ -56,8 +237,10 @@ mod tests { #[test] fn empty_field_as_none() { assert_eq!( - from_str::(r#"{"alias":""}"#) + r#"{"content":{"alias":""},"event_id":"$h29iv0s8:example.com","origin_server_ts":1,"sender":"@carl:example.com","state_key":"","type":"m.room.canonical_alias"}"# + .parse::() .unwrap() + .content .alias, None ); @@ -68,8 +251,10 @@ mod tests { let alias = Some(RoomAliasId::try_from("#somewhere:localhost").unwrap()); assert_eq!( - from_str::(r##"{"alias":"#somewhere:localhost"}"##) + r##"{"content":{"alias":"#somewhere:localhost"},"event_id":"$h29iv0s8:example.com","origin_server_ts":1,"sender":"@carl:example.com","state_key":"","type":"m.room.canonical_alias"}"## + .parse::() .unwrap() + .content .alias, alias ); diff --git a/src/room/name.rs b/src/room/name.rs index 3f8a2143..7393f8d8 100644 --- a/src/room/name.rs +++ b/src/room/name.rs @@ -44,12 +44,6 @@ pub struct NameEvent { #[derive(Clone, Debug, PartialEq, Serialize)] pub struct NameEventContent { /// The name of the room. This MUST NOT exceed 255 bytes. - // The spec says “A room with an m.room.name event with an absent, null, or empty name field - // should be treated the same as a room with no m.room.name event.”. - // Serde maps null fields to None by default, serde(default) maps an absent field to None, - // and empty_string_as_none completes the handling. - #[serde(default)] - #[serde(deserialize_with = "empty_string_as_none")] pub(crate) name: Option, } @@ -70,9 +64,9 @@ impl FromStr for NameEvent { .prev_content .map(|prev| NameEventContent { name: prev.name }), room_id: raw.room_id, - unsigned: raw.unsigned, sender: raw.sender, state_key: raw.state_key, + unsigned: raw.unsigned, }) } } @@ -177,24 +171,22 @@ mod raw { /// The unique identifier for the room associated with this event. pub room_id: Option, - /// Additional key-value pairs not signed by the homeserver. - pub unsigned: Option, - /// The unique identifier for the user who sent this event. pub sender: UserId, /// A key that determines which piece of room state the event represents. pub state_key: String, + + /// Additional key-value pairs not signed by the homeserver. + pub unsigned: Option, } /// The payload of a `NameEvent`. #[derive(Clone, Debug, Deserialize, PartialEq)] pub struct NameEventContent { /// The name of the room. This MUST NOT exceed 255 bytes. - // The spec says “A room with an m.room.name event with an absent, null, or empty name field - // should be treated the same as a room with no m.room.name event.”. - // Serde maps null fields to None by default, serde(default) maps an absent field to None, - // and empty_string_as_none completes the handling. + // The spec says "A room with an m.room.name event with an absent, null, or empty name field + // should be treated the same as a room with no m.room.name event." #[serde(default)] #[serde(deserialize_with = "empty_string_as_none")] pub(crate) name: Option, @@ -221,9 +213,9 @@ mod tests { origin_server_ts: UInt::try_from(1).unwrap(), prev_content: None, room_id: None, - unsigned: None, sender: UserId::try_from("@carl:example.com").unwrap(), state_key: "".to_string(), + unsigned: None, }; let actual = serde_json::to_string(&name_event).unwrap(); @@ -244,9 +236,9 @@ mod tests { name: Some("The old name".to_string()), }), room_id: Some(RoomId::try_from("!n8f893n9:example.com").unwrap()), - unsigned: Some(serde_json::from_str::(r#"{"foo":"bar"}"#).unwrap()), sender: UserId::try_from("@carl:example.com").unwrap(), state_key: "".to_string(), + unsigned: Some(serde_json::from_str::(r#"{"foo":"bar"}"#).unwrap()), }; let actual = serde_json::to_string(&name_event).unwrap(); diff --git a/src/room/power_levels.rs b/src/room/power_levels.rs index 62863ec2..bfcbb777 100644 --- a/src/room/power_levels.rs +++ b/src/room/power_levels.rs @@ -120,9 +120,9 @@ impl FromStr for PowerLevelsEvent { notifications: prev.notifications, }), room_id: raw.room_id, - unsigned: raw.unsigned, sender: raw.sender, state_key: raw.state_key, + unsigned: raw.unsigned, }) } }