diff --git a/ruma-events/src/room/message.rs b/ruma-events/src/room/message.rs index ca0feaaa..bc648412 100644 --- a/ruma-events/src/room/message.rs +++ b/ruma-events/src/room/message.rs @@ -1,6 +1,6 @@ //! Types for the *m.room.message* event. -use std::collections::BTreeMap; +use std::borrow::Cow; use js_int::UInt; use ruma_events_macros::MessageEventContent; @@ -8,7 +8,7 @@ use ruma_identifiers::MxcUri; #[cfg(feature = "unstable-pre-spec")] use ruma_identifiers::{DeviceIdBox, UserId}; use ruma_serde::StringEnum; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value as JsonValue; #[cfg(feature = "unstable-pre-spec")] @@ -23,12 +23,12 @@ pub use super::relationships::InReplyTo; mod content_serde; pub mod feedback; -use crate::MessageEvent as OuterMessageEvent; +type JsonObject = serde_json::Map; /// This event is used when sending messages in a room. /// /// Messages are not limited to be text. -pub type MessageEvent = OuterMessageEvent; +pub type MessageEvent = crate::MessageEvent; /// The payload for `MessageEvent`. #[derive(Clone, Debug, Serialize, MessageEventContent)] @@ -127,6 +127,80 @@ pub enum MessageType { _Custom(CustomEventContent), } +impl MessageType { + /// Creates a `MessageType` with the given `msgtype` string and data. + /// + /// Prefer to use the public variants of `MessageType` where possible; this constructor is meant + /// be used for unsupported message types only and does not allow setting arbitrary data for + /// supported ones. + pub fn new(msgtype: &str, data: JsonObject) -> serde_json::Result { + fn from_json_object(obj: JsonObject) -> serde_json::Result { + serde_json::from_value(JsonValue::Object(obj)) + } + + Ok(match msgtype { + "m.audio" => Self::Audio(from_json_object(data)?), + "m.emote" => Self::Emote(from_json_object(data)?), + "m.file" => Self::File(from_json_object(data)?), + "m.image" => Self::Image(from_json_object(data)?), + "m.location" => Self::Location(from_json_object(data)?), + "m.notice" => Self::Notice(from_json_object(data)?), + "m.server_notice" => Self::ServerNotice(from_json_object(data)?), + "m.text" => Self::Text(from_json_object(data)?), + "m.video" => Self::Video(from_json_object(data)?), + #[cfg(feature = "unstable-pre-spec")] + "m.key.verification.request" => Self::VerificationRequest(from_json_object(data)?), + _ => Self::_Custom(CustomEventContent { msgtype: msgtype.to_owned(), data }), + }) + } + + /// Returns a reference to the `msgtype` string. + pub fn msgtype(&self) -> &str { + match self { + Self::Audio(_) => "m.audio", + Self::Emote(_) => "m.emote", + Self::File(_) => "m.file", + Self::Image(_) => "m.image", + Self::Location(_) => "m.location", + Self::Notice(_) => "m.notice", + Self::ServerNotice(_) => "m.server_notice", + Self::Text(_) => "m.text", + Self::Video(_) => "m.video", + #[cfg(feature = "unstable-pre-spec")] + Self::VerificationRequest(_) => "m.key.verification.request", + Self::_Custom(c) => &c.msgtype, + } + } + + /// Returns the associated data. + /// + /// Prefer to use the public variants of `MessageType` where possible; this method is meant to + /// be used for unsupported message types only. + pub fn data(&self) -> Cow<'_, JsonObject> { + fn serialize(obj: &T) -> JsonObject { + match serde_json::to_value(obj).expect("message type serialization to succeed") { + JsonValue::Object(obj) => obj, + _ => panic!("all message types must serialize to objects"), + } + } + + match self { + Self::Audio(d) => Cow::Owned(serialize(d)), + Self::Emote(d) => Cow::Owned(serialize(d)), + Self::File(d) => Cow::Owned(serialize(d)), + Self::Image(d) => Cow::Owned(serialize(d)), + Self::Location(d) => Cow::Owned(serialize(d)), + Self::Notice(d) => Cow::Owned(serialize(d)), + Self::ServerNotice(d) => Cow::Owned(serialize(d)), + Self::Text(d) => Cow::Owned(serialize(d)), + Self::Video(d) => Cow::Owned(serialize(d)), + #[cfg(feature = "unstable-pre-spec")] + Self::VerificationRequest(d) => Cow::Owned(serialize(d)), + Self::_Custom(c) => Cow::Borrowed(&c.data), + } + } +} + impl From for MessageEventContent { fn from(msgtype: MessageType) -> Self { Self::new(msgtype) @@ -606,5 +680,5 @@ pub struct CustomEventContent { /// Remaining event content #[serde(flatten)] - pub data: BTreeMap, + pub data: JsonObject, } diff --git a/ruma-events/tests/room_message.rs b/ruma-events/tests/room_message.rs index a75c3780..6fff9866 100644 --- a/ruma-events/tests/room_message.rs +++ b/ruma-events/tests/room_message.rs @@ -1,7 +1,6 @@ use std::time::{Duration, UNIX_EPOCH}; use assign::assign; -use maplit::btreemap; use matches::assert_matches; #[cfg(feature = "unstable-pre-spec")] use ruma_events::{ @@ -23,6 +22,18 @@ use ruma_identifiers::{event_id, mxc_uri, room_id, user_id}; use ruma_serde::Raw; use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; +macro_rules! json_object { + ( $($key:expr => $value:expr),* $(,)? ) => { + { + let mut _map = serde_json::Map::::new(); + $( + let _ = _map.insert($key, $value); + )* + _map + } + }; +} + #[test] fn serialization() { let ev = MessageEvent { @@ -78,7 +89,7 @@ fn content_serialization() { #[test] fn custom_content_serialization() { - let json_data = btreemap! { + let json_data = json_object! { "custom_field".into() => json!("baba"), "another_one".into() => json!("abab"), }; @@ -105,7 +116,7 @@ fn custom_content_deserialization() { "another_one": "abab", }); - let expected_json_data = btreemap! { + let expected_json_data = json_object! { "custom_field".into() => json!("baba"), "another_one".into() => json!("abab"), };