diff --git a/src/direct.rs b/src/direct.rs index a7d22ed8..aed07e11 100644 --- a/src/direct.rs +++ b/src/direct.rs @@ -2,32 +2,32 @@ use std::collections::HashMap; +use ruma_events_macros::ruma_event; use ruma_identifiers::{RoomId, UserId}; -use serde::{Deserialize, Serialize}; -event! { +ruma_event! { /// Informs the client about the rooms that are considered direct by a user. - pub struct DirectEvent(DirectEventContent) {} + DirectEvent { + kind: Event, + event_type: Direct, + content_type_alias: { + /// The payload for `DirectEvent`. + /// + /// A mapping of `UserId`s to a list of `RoomId`s which are considered *direct* for that + /// particular user. + HashMap> + }, + } } -/// The payload of a `DirectEvent`. -/// -/// A mapping of `UserId`'s to a collection of `RoomId`'s which are considered -/// *direct* for that particular user. -pub type DirectEventContent = HashMap>; - #[cfg(test)] mod tests { use std::collections::HashMap; use ruma_identifiers::{RoomId, UserId}; - use serde_json::{from_str, to_string}; + use serde_json::to_string; - use crate::{ - collections, - direct::{DirectEvent, DirectEventContent}, - EventType, - }; + use super::{DirectEvent, DirectEventContent}; #[test] fn serialization() { @@ -39,7 +39,6 @@ mod tests { let event = DirectEvent { content, - event_type: EventType::Direct, }; assert_eq!( @@ -70,33 +69,10 @@ mod tests { rooms[1].to_string() ); - let event = from_str::(&json_data).unwrap(); - assert_eq!(event.event_type, EventType::Direct); - + let event = DirectEvent::from_str(&json_data).unwrap(); let direct_rooms = event.content.get(&alice).unwrap(); + assert!(direct_rooms.contains(&rooms[0])); assert!(direct_rooms.contains(&rooms[1])); - - match from_str::(&json_data).unwrap() { - collections::all::Event::Direct(event) => { - assert_eq!(event.event_type, EventType::Direct); - - let direct_rooms = event.content.get(&alice).unwrap(); - assert!(direct_rooms.contains(&rooms[0])); - assert!(direct_rooms.contains(&rooms[1])); - } - _ => unreachable!(), - }; - - match from_str::(&json_data).unwrap() { - collections::only::Event::Direct(event) => { - assert_eq!(event.event_type, EventType::Direct); - - let direct_rooms = event.content.get(&alice).unwrap(); - assert!(direct_rooms.contains(&rooms[0])); - assert!(direct_rooms.contains(&rooms[1])); - } - _ => unreachable!(), - }; } } diff --git a/src/dummy.rs b/src/dummy.rs index 070720a1..a58c863e 100644 --- a/src/dummy.rs +++ b/src/dummy.rs @@ -1,84 +1,10 @@ //! Types for the *m.dummy* event. -use std::{ - collections::HashMap, - fmt::{Formatter, Result as FmtResult}, -}; +use ruma_events_macros::ruma_event; -use serde::{de::{Error, MapAccess, Visitor}, ser::{SerializeMap, SerializeStruct}, Deserialize, Deserializer, Serialize, Serializer}; - -use crate::Event; - -/// This event type is used to indicate new Olm sessions for end-to-end encryption. -/// -/// Typically it is encrypted as an *m.room.encrypted* event, then sent as a to-device event. -/// -/// The event does not have any content associated with it. The sending client is expected to -/// send a key share request shortly after this message, causing the receiving client to process -/// this *m.dummy* event as the most recent event and using the keyshare request to set up the -/// session. The keyshare request and *m.dummy* combination should result in the original -/// sending client receiving keys over the newly established session. -#[derive(Clone, Debug)] -pub struct DummyEvent { - /// The event's content. - pub content: DummyEventContent, -} - -/// The payload for `DummyEvent`. -#[derive(Clone, Debug)] -pub struct DummyEventContent; - -impl DummyEvent { - /// Attempt to create `Self` from parsing a string of JSON data. - pub fn from_str(json: &str) -> Result { - serde_json::from_str::(json)?; - - Ok(Self { - content: DummyEventContent, - }) - } -} - -impl Serialize for DummyEvent { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer - { - let mut state = serializer.serialize_struct("DummyEvent", 2)?; - - state.serialize_field("content", &self.content); - state.serialize_field("type", &self.event_type()); - - state.end() - } -} - -impl crate::Event for DummyEvent { - /// The type of the event. - const EVENT_TYPE: crate::EventType = crate::EventType::Dummy; - - /// The type of this event's `content` field. - type Content = DummyEventContent; - - /// The event's content. - fn content(&self) -> &Self::Content { - &self.content - } -} - -// This is necessary because the content is represented in JSON as an empty object. -impl Serialize for DummyEventContent { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer - { - serializer.serialize_map(Some(0))?.end() - } -} - -mod raw { - use super::*; +use crate::Empty; +ruma_event! { /// This event type is used to indicate new Olm sessions for end-to-end encryption. /// /// Typically it is encrypted as an *m.room.encrypted* event, then sent as a to-device event. @@ -88,51 +14,24 @@ mod raw { /// this *m.dummy* event as the most recent event and using the keyshare request to set up the /// session. The keyshare request and *m.dummy* combination should result in the original /// sending client receiving keys over the newly established session. - #[derive(Clone, Debug, Deserialize)] - pub struct DummyEvent { - /// The event's content. - pub content: DummyEventContent, - } - - /// The payload for `DummyEvent`. - #[derive(Clone, Debug)] - pub struct DummyEventContent; - - impl<'de> Deserialize<'de> for DummyEventContent { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de> - { - struct EmptyMapVisitor; - - impl <'de> Visitor<'de> for EmptyMapVisitor { - type Value = DummyEventContent; - - fn expecting(&self, f: &mut Formatter) -> FmtResult { - write!(f, "an object/map") - } - - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de> - { - Ok(DummyEventContent) - } - } - - deserializer.deserialize_map(EmptyMapVisitor) + DummyEvent { + kind: Event, + event_type: Dummy, + content_type_alias: { + /// The payload for `DummyEvent`. + Empty } } } #[cfg(test)] mod tests { - use super::{DummyEvent, DummyEventContent}; + use super::{DummyEvent, Empty}; #[test] fn serialization() { let dummy_event = DummyEvent { - content: DummyEventContent, + content: Empty, }; let actual = serde_json::to_string(&dummy_event).unwrap(); diff --git a/src/ignored_user_list.rs b/src/ignored_user_list.rs index e4dfefa4..2db1576a 100644 --- a/src/ignored_user_list.rs +++ b/src/ignored_user_list.rs @@ -3,25 +3,100 @@ use std::collections::HashMap; use ruma_identifiers::UserId; -use serde::{Deserialize, Serialize}; +use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer}; -event! { - /// A list of users to ignore. - pub struct IgnoredUserListEvent(IgnoredUserListEventContent) {} +use crate::{Empty, Event}; + +/// A list of users to ignore. +#[derive(Clone, Debug, PartialEq)] +pub struct IgnoredUserListEvent { + /// The event's content. + pub content: IgnoredUserListEventContent, } -/// The payload of an `IgnoredUserListEvent`. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +/// The payload for `IgnoredUserListEvent`. +#[derive(Clone, Debug, PartialEq)] pub struct IgnoredUserListEventContent { /// A list of users to ignore. - /// - /// The values in the hash map are not meaningful. They are used to generate an empty JSON - /// object to support the odd structure used by the Matrix specification: - /// - /// ```text - /// "@someone:example.org": {} - /// ``` - pub ignored_users: HashMap>, + pub ignored_users: Vec, +} + +impl IgnoredUserListEvent { + /// Attempt to create `Self` from parsing a string of JSON data. + pub fn from_str(json: &str) -> Result { + let raw = serde_json::from_str::(json)?; + + Ok(Self { + content: IgnoredUserListEventContent { + ignored_users: raw.content.ignored_users.keys().cloned().collect(), + }, + }) + } +} + +impl Serialize for IgnoredUserListEvent { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer + { + let mut state = serializer.serialize_struct("IgnoredUserListEvent", 2)?; + + state.serialize_field("content", &self.content); + state.serialize_field("type", &self.event_type()); + + state.end() + } +} + +impl crate::Event for IgnoredUserListEvent { + /// The type of the event. + const EVENT_TYPE: crate::EventType = crate::EventType::IgnoredUserList; + + /// The type of this event's `content` field. + type Content = IgnoredUserListEventContent; + + /// The event's content. + fn content(&self) -> &Self::Content { + &self.content + } +} + +impl Serialize for IgnoredUserListEventContent { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer + { + let mut map = HashMap::new(); + + for user_id in &self.ignored_users { + map.insert(user_id.clone(), Empty); + } + + let raw = raw::IgnoredUserListEventContent { + ignored_users: map, + }; + + raw.serialize(serializer) + } +} + +mod raw { + use crate::Empty; + use super::*; + + /// A list of users to ignore. + #[derive(Clone, Debug, Deserialize)] + pub struct IgnoredUserListEvent { + /// The event's content. + pub content: IgnoredUserListEventContent, + } + + /// The payload for `IgnoredUserListEvent`. + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct IgnoredUserListEventContent { + /// A list of users to ignore. + pub ignored_users: HashMap, + } } #[cfg(test)] @@ -30,40 +105,33 @@ mod tests { use ruma_identifiers::UserId; - use super::IgnoredUserListEventContent; + use super::{IgnoredUserListEvent, IgnoredUserListEventContent}; #[test] - fn serialize_to_empty_json_object() { - let mut ignored_user_list_event_content = IgnoredUserListEventContent { - ignored_users: HashMap::new(), + fn serialization() { + let ignored_user_list_event = IgnoredUserListEvent { + content: IgnoredUserListEventContent { + ignored_users: vec![UserId::try_from("@carl:example.com").unwrap()], + }, }; - let user_id = UserId::try_from("@carl:example.com").unwrap(); + let json = serde_json::to_string(&ignored_user_list_event).unwrap(); - ignored_user_list_event_content - .ignored_users - .insert(user_id, HashMap::new()); - - let json = serde_json::to_string(&ignored_user_list_event_content).unwrap(); - - assert_eq!(json, r#"{"ignored_users":{"@carl:example.com":{}}}"#); + assert_eq!(json, r#"{"content":{"ignored_users":{"@carl:example.com":{}}},"type":"m.ignored_user_list"}"#); } #[test] - fn deserialize_from_empty_json_object() { - let json = r#"{"ignored_users":{"@carl:example.com":{}}}"#; + fn deserialization() { + let json = r#"{"content":{"ignored_users":{"@carl:example.com":{}}},"type":"m.ignored_user_list"}"#; - let ignored_user_list_event_content: IgnoredUserListEventContent = - serde_json::from_str(&json).unwrap(); + let actual = IgnoredUserListEvent::from_str(json).unwrap(); - let mut expected = IgnoredUserListEventContent { - ignored_users: HashMap::new(), + let expected = IgnoredUserListEvent { + content: IgnoredUserListEventContent { + ignored_users: vec![UserId::try_from("@carl:example.com").unwrap()], + }, }; - let user_id = UserId::try_from("@carl:example.com").unwrap(); - - expected.ignored_users.insert(user_id, HashMap::new()); - - assert_eq!(ignored_user_list_event_content, expected); + assert_eq!(actual, expected); } } diff --git a/src/lib.rs b/src/lib.rs index a8321a10..a219ab43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -107,7 +107,8 @@ use std::{ use js_int::UInt; use ruma_identifiers::{EventId, RoomId, UserId}; use serde::{ - de::{Error as SerdeError, IntoDeserializer, Visitor}, + de::{Error as SerdeError, IntoDeserializer, MapAccess, Visitor}, + ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer, }; use serde_json::Value; @@ -121,22 +122,22 @@ mod macros; // pub mod all; // pub mod only; // } -// pub mod direct; +pub mod direct; pub mod dummy; pub mod forwarded_room_key; // pub mod fully_read; -// pub mod ignored_user_list; +pub mod ignored_user_list; // pub mod key; pub mod presence; // pub mod push_rules; -// pub mod receipt; +pub mod receipt; pub mod room; // pub mod room_key; pub mod room_key_request; pub mod sticker; // pub mod stripped; -// pub mod tag; -// pub mod typing; +pub mod tag; +pub mod typing; /// An event that is malformed or otherwise invalid. /// @@ -212,6 +213,48 @@ impl Display for FromStrError { impl Error for FromStrError {} +/// A meaningless value that serializes to an empty JSON object. +/// +/// This type is used in a few places where the Matrix specification requires an empty JSON object, +/// but it's wasteful to represent it as a `HashMap` in Rust code. +#[derive(Clone, Debug, PartialEq)] +pub struct Empty; + +impl Serialize for Empty { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer + { + serializer.serialize_map(Some(0))?.end() + } +} + +impl<'de> Deserialize<'de> for Empty { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de> + { + struct EmptyMapVisitor; + + impl <'de> Visitor<'de> for EmptyMapVisitor { + type Value = Empty; + + fn expecting(&self, f: &mut Formatter) -> FmtResult { + write!(f, "an object/map") + } + + fn visit_map(self, _map: A) -> Result + where + A: MapAccess<'de> + { + Ok(Empty) + } + } + + deserializer.deserialize_map(EmptyMapVisitor) + } +} + /// The type of an event. #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub enum EventType { diff --git a/src/macros.rs b/src/macros.rs index 54335940..ccab4112 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -23,193 +23,3 @@ macro_rules! impl_enum { } } } - -macro_rules! event { - ( $(#[$attr:meta])* - pub struct $name:ident($content_type:ty) { - $( - $(#[$field_attr:meta])* - pub $field_name:ident: $field_type:ty - ),* - } - ) => { - $(#[$attr])* - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct $name { - /// The event's content. - pub content: $content_type, - - /// The type of the event. - #[serde(rename = "type")] - pub event_type: $crate::EventType, - - $( - $(#[$field_attr])* - pub $field_name: $field_type - ),* - } - - impl_event!($name, $content_type); - } -} - -macro_rules! impl_event { - ($name:ident, $content_type:ty) => { - impl $crate::Event for $name { - type Content = $content_type; - - fn content(&self) -> &<$name as $crate::Event>::Content { - &self.content - } - - fn event_type(&self) -> &$crate::EventType { - &self.event_type - } - } - }; -} - -macro_rules! room_event { - ( $(#[$attr:meta])* - pub struct $name:ident($content_type:ty) { - $( - $(#[$field_attr:meta])* - pub $field_name:ident: $field_type:ty - ),* - } - ) => { - $(#[$attr])* - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct $name { - /// The event's content. - pub content: $content_type, - - /// The unique identifier for the event. - pub event_id: ::ruma_identifiers::EventId, - - /// The type of the event. - #[serde(rename = "type")] - pub event_type: $crate::EventType, - - /// Timestamp (milliseconds since the UNIX epoch) on originating homeserver when this - /// event was sent. - pub origin_server_ts: UInt, - - /// The unique identifier for the room associated with this event. - #[serde(skip_serializing_if="Option::is_none")] - pub room_id: Option<::ruma_identifiers::RoomId>, - - /// Additional key-value pairs not signed by the homeserver. - #[serde(skip_serializing_if = "Option::is_none")] - pub unsigned: Option<::serde_json::Value>, - - /// The unique identifier for the user who sent this event. - pub sender: ::ruma_identifiers::UserId, - - $( - $(#[$field_attr])* - pub $field_name: $field_type - ),* - } - - impl_room_event!($name, $content_type); - } -} - -macro_rules! impl_room_event { - ($name:ident, $content_type:ty) => { - impl_event!($name, $content_type); - - impl $crate::RoomEvent for $name { - fn event_id(&self) -> &::ruma_identifiers::EventId { - &self.event_id - } - - fn origin_server_ts(&self) -> UInt { - self.origin_server_ts - } - - fn room_id(&self) -> Option<&::ruma_identifiers::RoomId> { - self.room_id.as_ref() - } - - fn unsigned(&self) -> Option<&::serde_json::Value> { - self.unsigned.as_ref() - } - - fn sender(&self) -> &::ruma_identifiers::UserId { - &self.sender - } - } - }; -} - -macro_rules! state_event { - ( $(#[$attr:meta])* - pub struct $name:ident($content_type:ty) { - $( - $(#[$field_attr:meta])* - pub $field_name:ident: $field_type:ty - ),* - } - ) => { - $(#[$attr])* - #[allow(missing_docs)] - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct $name { - /// The event's content. - pub content: $content_type, - - /// The unique identifier for the event. - pub event_id: ::ruma_identifiers::EventId, - - /// The type of the event. - #[serde(rename = "type")] - pub event_type: $crate::EventType, - - /// Timestamp in milliseconds on originating homeserver when this event was sent. - pub origin_server_ts: UInt, - - /// The previous content for this state key, if any. - #[serde(skip_serializing_if = "Option::is_none")] - pub prev_content: Option<$content_type>, - - /// The unique identifier for the room associated with this event. - #[serde(skip_serializing_if="Option::is_none")] - pub room_id: Option<::ruma_identifiers::RoomId>, - - /// 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. - #[serde(skip_serializing_if = "Option::is_none")] - pub unsigned: Option<::serde_json::Value>, - - /// The unique identifier for the user associated with this event. - pub sender: ::ruma_identifiers::UserId, - - $( - $(#[$field_attr])* - pub $field_name: $field_type - ),* - } - - impl_state_event!($name, $content_type); - } -} - -macro_rules! impl_state_event { - ($name:ident, $content_type:ty) => { - impl_room_event!($name, $content_type); - - impl $crate::StateEvent for $name { - fn prev_content(&self) -> Option<&Self::Content> { - self.prev_content.as_ref() - } - - fn state_key(&self) -> &str { - &self.state_key - } - } - }; -} diff --git a/src/presence.rs b/src/presence.rs index 1b51e530..06c6472e 100644 --- a/src/presence.rs +++ b/src/presence.rs @@ -77,10 +77,9 @@ mod tests { use js_int::UInt; use ruma_identifiers::UserId; - use serde_json::{from_str, to_string}; + use serde_json::to_string; use super::{PresenceEvent, PresenceEventContent, PresenceState}; - use crate::EventType; /// Test serialization and deserialization of example m.presence event from the spec /// https://github.com/turt2live/matrix-doc/blob/master/event-schemas/examples/m.presence diff --git a/src/receipt.rs b/src/receipt.rs index 6e005b5d..d6d89809 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -3,23 +3,29 @@ use std::collections::HashMap; use js_int::UInt; +use ruma_events_macros::ruma_event; use ruma_identifiers::{EventId, RoomId, UserId}; use serde::{Deserialize, Serialize}; -event! { +ruma_event! { /// Informs the client of new receipts. - pub struct ReceiptEvent(ReceiptEventContent) { - /// The unique identifier for the room associated with this event. - pub room_id: RoomId + ReceiptEvent { + kind: Event, + event_type: Receipt, + fields: { + /// The unique identifier for the room associated with this event. + pub room_id: RoomId, + }, + content_type_alias: { + /// The payload for `ReceiptEvent`. + /// + /// A mapping of event ID to a collection of receipts for this event ID. The event ID is the ID of + /// the event being acknowledged and *not* an ID for the receipt itself. + HashMap + }, } } -/// The payload of a `ReceiptEvent`. -/// -/// A mapping of event ID to a collection of receipts for this event ID. The event ID is the ID of -/// the event being acknowledged and *not* an ID for the receipt itself. -pub type ReceiptEventContent = HashMap; - /// A collection of receipts. #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct Receipts { diff --git a/src/tag.rs b/src/tag.rs index 050743b0..3ed95d00 100644 --- a/src/tag.rs +++ b/src/tag.rs @@ -2,18 +2,19 @@ use std::collections::HashMap; +use ruma_events_macros::ruma_event; use serde::{Deserialize, Serialize}; -event! { +ruma_event! { /// Informs the client of tags on a room. - pub struct TagEvent(TagEventContent) {} -} - -/// The payload of a `TagEvent`. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] -pub struct TagEventContent { - /// A map of tag names to tag info. - pub tags: HashMap, + TagEvent { + kind: Event, + event_type: Tag, + content: { + /// A map of tag names to tag info. + pub tags: HashMap, + }, + } } /// Information about a tag. diff --git a/src/typing.rs b/src/typing.rs index eb7f2cfc..6c136408 100644 --- a/src/typing.rs +++ b/src/typing.rs @@ -1,19 +1,20 @@ //! Types for the *m.typing* event. +use ruma_events_macros::ruma_event; use ruma_identifiers::{RoomId, UserId}; -use serde::{Deserialize, Serialize}; -event! { +ruma_event! { /// Informs the client of the list of users currently typing. - pub struct TypingEvent(TypingEventContent) { - /// The unique identifier for the room associated with this event. - pub room_id: RoomId + TypingEvent { + kind: Event, + event_type: Typing, + fields: { + /// The unique identifier for the room associated with this event. + pub room_id: RoomId, + }, + content: { + /// The list of user IDs typing in this room, if any. + pub user_ids: Vec, + }, } } - -/// The payload of a `TypingEvent`. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] -pub struct TypingEventContent { - /// The list of user IDs typing in this room, if any. - pub user_ids: Vec, -}