//! Types for the *m.room.message* event. use std::{collections::BTreeMap, time::SystemTime}; use js_int::UInt; use ruma_identifiers::{EventId, RoomId, UserId}; use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer}; use serde_json::{from_value, Value}; use super::{EncryptedFile, ImageInfo, ThumbnailInfo}; use crate::{EventType, FromRaw}; pub mod feedback; /// A message sent to a room. #[derive(Clone, Debug, PartialEq, Serialize)] #[serde(rename = "m.room.message", tag = "type")] pub struct MessageEvent { /// The event's content. pub content: MessageEventContent, /// The unique identifier for the event. pub event_id: EventId, /// Time on originating homeserver when this event was sent. #[serde(with = "ruma_serde::time::ms_since_unix_epoch")] pub origin_server_ts: SystemTime, /// The unique identifier for the room associated with this event. #[serde(skip_serializing_if = "Option::is_none")] pub room_id: Option, /// The unique identifier for the user who sent this event. pub sender: UserId, /// Additional key-value pairs not signed by the homeserver. #[serde(skip_serializing_if = "BTreeMap::is_empty")] pub unsigned: BTreeMap, } /// The payload for `MessageEvent`. #[allow(clippy::large_enum_variant)] #[derive(Clone, Debug, PartialEq)] pub enum MessageEventContent { /// An audio message. Audio(AudioMessageEventContent), /// An emote message. Emote(EmoteMessageEventContent), /// A file message. File(FileMessageEventContent), /// An image message. Image(ImageMessageEventContent), /// A location message. Location(LocationMessageEventContent), /// A notice message. Notice(NoticeMessageEventContent), /// A server notice message. ServerNotice(ServerNoticeMessageEventContent), /// A text message. Text(TextMessageEventContent), /// A video message. Video(VideoMessageEventContent), /// Additional variants may be added in the future and will not be considered breaking changes /// to ruma-events. #[doc(hidden)] __Nonexhaustive, } impl FromRaw for MessageEvent { type Raw = raw::MessageEvent; fn from_raw(raw: raw::MessageEvent) -> Self { Self { content: FromRaw::from_raw(raw.content), event_id: raw.event_id, origin_server_ts: raw.origin_server_ts, room_id: raw.room_id, sender: raw.sender, unsigned: raw.unsigned, } } } impl FromRaw for MessageEventContent { type Raw = raw::MessageEventContent; fn from_raw(raw: raw::MessageEventContent) -> Self { use raw::MessageEventContent::*; match raw { Audio(content) => MessageEventContent::Audio(content), Emote(content) => MessageEventContent::Emote(content), File(content) => MessageEventContent::File(content), Image(content) => MessageEventContent::Image(content), Location(content) => MessageEventContent::Location(content), Notice(content) => MessageEventContent::Notice(content), ServerNotice(content) => MessageEventContent::ServerNotice(content), Text(content) => MessageEventContent::Text(content), Video(content) => MessageEventContent::Video(content), __Nonexhaustive => { unreachable!("It should be impossible to obtain a __Nonexhaustive variant.") } } } } impl_room_event!(MessageEvent, MessageEventContent, EventType::RoomMessage); impl Serialize for MessageEventContent { fn serialize(&self, serializer: S) -> Result where S: Serializer, { use serde::ser::Error as _; match *self { MessageEventContent::Audio(ref content) => content.serialize(serializer), MessageEventContent::Emote(ref content) => content.serialize(serializer), MessageEventContent::File(ref content) => content.serialize(serializer), MessageEventContent::Image(ref content) => content.serialize(serializer), MessageEventContent::Location(ref content) => content.serialize(serializer), MessageEventContent::Notice(ref content) => content.serialize(serializer), MessageEventContent::ServerNotice(ref content) => content.serialize(serializer), MessageEventContent::Text(ref content) => content.serialize(serializer), MessageEventContent::Video(ref content) => content.serialize(serializer), MessageEventContent::__Nonexhaustive => Err(S::Error::custom( "Attempted to deserialize __Nonexhaustive variant.", )), } } } pub(crate) mod raw { use super::*; /// A message sent to a room. #[derive(Clone, Debug, Deserialize, PartialEq)] pub struct MessageEvent { /// The event's content. pub content: MessageEventContent, /// The unique identifier for the event. pub event_id: EventId, /// Time on originating homeserver when this event was sent. #[serde(with = "ruma_serde::time::ms_since_unix_epoch")] pub origin_server_ts: SystemTime, /// 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, /// Additional key-value pairs not signed by the homeserver. #[serde(default)] pub unsigned: BTreeMap, } /// The payload for `MessageEvent`. #[allow(clippy::large_enum_variant)] #[derive(Clone, Debug, PartialEq)] pub enum MessageEventContent { /// An audio message. Audio(AudioMessageEventContent), /// An emote message. Emote(EmoteMessageEventContent), /// A file message. File(FileMessageEventContent), /// An image message. Image(ImageMessageEventContent), /// A location message. Location(LocationMessageEventContent), /// A notice message. Notice(NoticeMessageEventContent), /// A server notice message. ServerNotice(ServerNoticeMessageEventContent), /// An text message. Text(TextMessageEventContent), /// A video message. Video(VideoMessageEventContent), /// Additional variants may be added in the future and will not be considered breaking changes /// to ruma-events. #[doc(hidden)] __Nonexhaustive, } impl<'de> Deserialize<'de> for MessageEventContent { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { use serde::de::Error as _; let value: Value = Deserialize::deserialize(deserializer)?; let message_type_value = match value.get("msgtype") { Some(value) => value.clone(), None => return Err(D::Error::missing_field("msgtype")), }; let message_type = match from_value::(message_type_value) { Ok(message_type) => message_type, Err(error) => return Err(D::Error::custom(error.to_string())), }; match message_type { MessageType::Audio => { let content = match from_value::(value) { Ok(content) => content, Err(error) => return Err(D::Error::custom(error.to_string())), }; Ok(MessageEventContent::Audio(content)) } MessageType::Emote => { let content = match from_value::(value) { Ok(content) => content, Err(error) => return Err(D::Error::custom(error.to_string())), }; Ok(MessageEventContent::Emote(content)) } MessageType::File => { let content = match from_value::(value) { Ok(content) => content, Err(error) => return Err(D::Error::custom(error.to_string())), }; Ok(MessageEventContent::File(content)) } MessageType::Image => { let content = match from_value::(value) { Ok(content) => content, Err(error) => return Err(D::Error::custom(error.to_string())), }; Ok(MessageEventContent::Image(content)) } MessageType::Location => { let content = match from_value::(value) { Ok(content) => content, Err(error) => return Err(D::Error::custom(error.to_string())), }; Ok(MessageEventContent::Location(content)) } MessageType::Notice => { let content = match from_value::(value) { Ok(content) => content, Err(error) => return Err(D::Error::custom(error.to_string())), }; Ok(MessageEventContent::Notice(content)) } MessageType::ServerNotice => { let content = match from_value::(value) { Ok(content) => content, Err(error) => return Err(D::Error::custom(error.to_string())), }; Ok(MessageEventContent::ServerNotice(content)) } MessageType::Text => { let content = match from_value::(value) { Ok(content) => content, Err(error) => return Err(D::Error::custom(error.to_string())), }; Ok(MessageEventContent::Text(content)) } MessageType::Video => { let content = match from_value::(value) { Ok(content) => content, Err(error) => return Err(D::Error::custom(error.to_string())), }; Ok(MessageEventContent::Video(content)) } MessageType::__Nonexhaustive => Err(D::Error::custom( "Attempted to deserialize __Nonexhaustive variant.", )), } } } } /// The message type of message event, e.g. `m.image` or `m.text`. #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] pub enum MessageType { /// An audio message. #[serde(rename = "m.audio")] Audio, /// An emote message. #[serde(rename = "m.emote")] Emote, /// A file message. #[serde(rename = "m.file")] File, /// An image message. #[serde(rename = "m.image")] Image, /// A location message. #[serde(rename = "m.location")] Location, /// A notice message. #[serde(rename = "m.notice")] Notice, /// A server notice. #[serde(rename = "m.server_notice")] ServerNotice, /// A text message. #[serde(rename = "m.text")] Text, /// A video message. #[serde(rename = "m.video")] Video, /// Additional variants may be added in the future and will not be considered breaking changes /// to ruma-events. #[doc(hidden)] #[serde(skip)] __Nonexhaustive, } /// The payload for an audio message. #[derive(Clone, Debug, Deserialize, PartialEq)] pub struct AudioMessageEventContent { /// The textual representation of this message. pub body: String, /// Metadata for the audio clip referred to in `url`. #[serde(skip_serializing_if = "Option::is_none")] pub info: Option, /// The URL to the audio clip. Required if the file is unencrypted. The URL (typically /// [MXC URI](https://matrix.org/docs/spec/client_server/r0.5.0#mxc-uri)) to the audio clip. #[serde(skip_serializing_if = "Option::is_none")] pub url: Option, /// Required if the audio clip is encrypted. Information on the encrypted audio clip. #[serde(skip_serializing_if = "Option::is_none")] pub file: Option, } /// Metadata about an audio clip. #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct AudioInfo { /// The duration of the audio in milliseconds. #[serde(skip_serializing_if = "Option::is_none")] pub duration: Option, /// The mimetype of the audio, e.g. "audio/aac." #[serde(skip_serializing_if = "Option::is_none")] pub mimetype: Option, /// The size of the audio clip in bytes. #[serde(skip_serializing_if = "Option::is_none")] pub size: Option, } /// The payload for an emote message. #[derive(Clone, Debug, Deserialize, PartialEq)] pub struct EmoteMessageEventContent { /// The emote action to perform. pub body: String, /// The format used in the `formatted_body`. Currently only `org.matrix.custom.html` is /// supported. #[serde(skip_serializing_if = "Option::is_none")] pub format: Option, /// The formatted version of the `body`. This is required if `format` is specified. #[serde(skip_serializing_if = "Option::is_none")] pub formatted_body: Option, } /// The payload for a file message. #[derive(Clone, Debug, Deserialize, PartialEq)] pub struct FileMessageEventContent { /// A human-readable description of the file. This is recommended to be the filename of the /// original upload. pub body: String, /// The original filename of the uploaded file. #[serde(skip_serializing_if = "Option::is_none")] pub filename: Option, /// Metadata about the file referred to in `url`. #[serde(skip_serializing_if = "Option::is_none")] pub info: Option, /// The URL to the file. Required if the file is unencrypted. The URL (typically /// [MXC URI](https://matrix.org/docs/spec/client_server/r0.5.0#mxc-uri)) to the file. #[serde(skip_serializing_if = "Option::is_none")] pub url: Option, /// Required if file is encrypted. Information on the encrypted file. #[serde(skip_serializing_if = "Option::is_none")] pub file: Option, } /// Metadata about a file. #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct FileInfo { /// The mimetype of the file, e.g. "application/msword." pub mimetype: Option, /// The size of the file in bytes. pub size: Option, /// Metadata about the image referred to in `thumbnail_url`. #[serde(skip_serializing_if = "Option::is_none")] pub thumbnail_info: Option, /// The URL to the thumbnail of the file. Only present if the thumbnail is unencrypted. #[serde(skip_serializing_if = "Option::is_none")] pub thumbnail_url: Option, /// Information on the encrypted thumbnail file. Only present if the thumbnail is encrypted. #[serde(skip_serializing_if = "Option::is_none")] pub thumbnail_file: Option, } /// The payload for an image message. #[derive(Clone, Debug, Deserialize, PartialEq)] pub struct ImageMessageEventContent { /// A textual representation of the image. This could be the alt text of the image, the filename /// of the image, or some kind of content description for accessibility e.g. "image attachment." pub body: String, /// Metadata about the image referred to in `url`. #[serde(skip_serializing_if = "Option::is_none")] pub info: Option, /// The URL to the image. Required if the file is unencrypted. The URL (typically /// [MXC URI](https://matrix.org/docs/spec/client_server/r0.5.0#mxc-uri)) to the image. pub url: Option, /// Required if image is encrypted. Information on the encrypted image. #[serde(skip_serializing_if = "Option::is_none")] pub file: Option, } /// The payload for a location message. #[derive(Clone, Debug, Deserialize, PartialEq)] pub struct LocationMessageEventContent { /// A description of the location e.g. "Big Ben, London, UK,"or some kind of content description /// for accessibility, e.g. "location attachment." pub body: String, /// A geo URI representing the location. pub geo_uri: String, /// Info about the location being represented. #[serde(skip_serializing_if = "Option::is_none")] pub info: Option, } /// Thumbnail info associated with a location. #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct LocationInfo { /// Metadata about the image referred to in `thumbnail_url` or `thumbnail_file`. #[serde(skip_serializing_if = "Option::is_none")] pub thumbnail_info: Option, /// The URL to a thumbnail of the location being represented. Only present if the thumbnail is /// unencrypted. #[serde(skip_serializing_if = "Option::is_none")] pub thumbnail_url: Option, /// Information on an encrypted thumbnail of the location being represented. Only present if the /// thumbnail is encrypted. #[serde(skip_serializing_if = "Option::is_none")] pub thumbnail_file: Option, } /// The payload for a notice message. #[derive(Clone, Debug, Deserialize, PartialEq)] pub struct NoticeMessageEventContent { /// The notice text to send. pub body: String, /// Information about related messages for /// [rich replies](https://matrix.org/docs/spec/client_server/r0.5.0#rich-replies). #[serde(rename = "m.relates_to")] #[serde(skip_serializing_if = "Option::is_none")] pub relates_to: Option, } /// The payload for a server notice message. #[derive(Clone, Debug, Deserialize, PartialEq)] pub struct ServerNoticeMessageEventContent { /// A human-readable description of the notice. pub body: String, /// The type of notice being represented. pub server_notice_type: ServerNoticeType, /// A URI giving a contact method for the server administrator. /// /// Required if the notice type is `m.server_notice.usage_limit_reached`. pub admin_contact: Option, /// The kind of usage limit the server has exceeded. /// /// Required if the notice type is `m.server_notice.usage_limit_reached`. pub limit_type: Option, } /// Types of server notices. #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] pub enum ServerNoticeType { /// The server has exceeded some limit which requires the server administrator to intervene. #[serde(rename = "m.server_notice.usage_limit_reached")] UsageLimitReached, /// Additional variants may be added in the future and will not be considered breaking changes /// to ruma-events. #[doc(hidden)] #[serde(skip)] __Nonexhaustive, } /// Types of usage limits. #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] pub enum LimitType { /// The server's number of active users in the last 30 days has exceeded the maximum. /// /// New connections are being refused by the server. What defines "active" is left as an /// implementation detail, however servers are encouraged to treat syncing users as "active". #[serde(rename = "monthly_active_user")] MonthlyActiveUser, /// Additional variants may be added in the future and will not be considered breaking changes /// to ruma-events. #[doc(hidden)] #[serde(skip)] __Nonexhaustive, } /// The payload for a text message. #[derive(Clone, Debug, Deserialize, PartialEq)] pub struct TextMessageEventContent { /// The body of the message. pub body: String, /// The format used in the `formatted_body`. Currently only `org.matrix.custom.html` is /// supported. #[serde(skip_serializing_if = "Option::is_none")] pub format: Option, /// The formatted version of the `body`. This is required if `format` is specified. #[serde(skip_serializing_if = "Option::is_none")] pub formatted_body: Option, /// Information about related messages for /// [rich replies](https://matrix.org/docs/spec/client_server/r0.5.0#rich-replies). #[serde(rename = "m.relates_to")] #[serde(skip_serializing_if = "Option::is_none")] pub relates_to: Option, } /// The payload for a video message. #[derive(Clone, Debug, Deserialize, PartialEq)] pub struct VideoMessageEventContent { /// A description of the video, e.g. "Gangnam Style," or some kind of content description for /// accessibility, e.g. "video attachment." pub body: String, /// Metadata about the video clip referred to in `url`. #[serde(skip_serializing_if = "Option::is_none")] pub info: Option, /// The URL to the video clip. Required if the file is unencrypted. The URL (typically /// [MXC URI](https://matrix.org/docs/spec/client_server/r0.5.0#mxc-uri)) to the video clip. pub url: Option, /// Required if video clip is encrypted. Information on the encrypted video clip. #[serde(skip_serializing_if = "Option::is_none")] pub file: Option, } /// Metadata about a video. #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct VideoInfo { /// The duration of the video in milliseconds. #[serde(skip_serializing_if = "Option::is_none")] pub duration: Option, /// The height of the video in pixels. #[serde(rename = "h")] #[serde(skip_serializing_if = "Option::is_none")] pub height: Option, /// The width of the video in pixels. #[serde(rename = "w")] #[serde(skip_serializing_if = "Option::is_none")] pub width: Option, /// The mimetype of the video, e.g. "video/mp4." #[serde(skip_serializing_if = "Option::is_none")] pub mimetype: Option, /// The size of the video in bytes. #[serde(skip_serializing_if = "Option::is_none")] pub size: Option, /// Metadata about an image. #[serde(skip_serializing_if = "Option::is_none")] pub thumbnail_info: Option, /// The URL (typically [MXC URI](https://matrix.org/docs/spec/client_server/r0.5.0#mxc-uri)) to /// an image thumbnail of the video clip. Only present if the thumbnail is unencrypted. #[serde(skip_serializing_if = "Option::is_none")] pub thumbnail_url: Option, /// Information on the encrypted thumbnail file. Only present if the thumbnail is encrypted. #[serde(skip_serializing_if = "Option::is_none")] pub thumbnail_file: Option, } /// Information about related messages for /// [rich replies](https://matrix.org/docs/spec/client_server/r0.5.0#rich-replies). #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct RelatesTo { /// Information about another message being replied to. #[serde(rename = "m.in_reply_to")] pub in_reply_to: InReplyTo, } /// Information about the event a "rich reply" is replying to. #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct InReplyTo { /// The event being replied to. pub event_id: EventId, } impl_enum! { MessageType { Audio => "m.audio", Emote => "m.emote", File => "m.file", Image => "m.image", Location => "m.location", Notice => "m.notice", ServerNotice => "m.server_notice", Text => "m.text", Video => "m.video", } } impl TextMessageEventContent { /// A convenience constructor to create a plain text message pub fn new_plain(body: impl Into) -> TextMessageEventContent { TextMessageEventContent { body: body.into(), format: None, formatted_body: None, relates_to: None, } } } impl Serialize for AudioMessageEventContent { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut len = 2; if self.info.is_some() { len += 1; } if self.url.is_some() { len += 1; } if self.file.is_some() { len += 1; } let mut state = serializer.serialize_struct("AudioMessageEventContent", len)?; state.serialize_field("body", &self.body)?; if self.info.is_some() { state.serialize_field("info", &self.info)?; } state.serialize_field("msgtype", "m.audio")?; if self.url.is_some() { state.serialize_field("url", &self.url)?; } if self.file.is_some() { state.serialize_field("file", &self.file)?; } state.end() } } impl Serialize for EmoteMessageEventContent { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut len = 2; if self.format.is_some() { len += 1; } if self.formatted_body.is_some() { len += 1; } let mut state = serializer.serialize_struct("EmoteMessageEventContent", len)?; state.serialize_field("body", &self.body)?; if self.format.is_some() { state.serialize_field("format", &self.format)?; } if self.formatted_body.is_some() { state.serialize_field("formatted_body", &self.formatted_body)?; } state.serialize_field("msgtype", "m.emote")?; state.end() } } impl Serialize for FileMessageEventContent { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut len = 2; if self.filename.is_some() { len += 1; } if self.info.is_some() { len += 1; } if self.url.is_some() { len += 1; } if self.file.is_some() { len += 1; } let mut state = serializer.serialize_struct("FileMessageEventContent", len)?; state.serialize_field("body", &self.body)?; if self.filename.is_some() { state.serialize_field("filename", &self.filename)?; } state.serialize_field("msgtype", "m.file")?; if self.info.is_some() { state.serialize_field("info", &self.info)?; } if self.url.is_some() { state.serialize_field("url", &self.url)?; } if self.file.is_some() { state.serialize_field("file", &self.file)?; } state.end() } } impl Serialize for ImageMessageEventContent { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut len = 2; if self.info.is_some() { len += 1; } if self.url.is_some() { len += 1; } if self.file.is_some() { len += 1; } let mut state = serializer.serialize_struct("ImageMessageEventContent", len)?; state.serialize_field("body", &self.body)?; state.serialize_field("msgtype", "m.image")?; if self.info.is_some() { state.serialize_field("info", &self.info)?; } if self.url.is_some() { state.serialize_field("url", &self.url)?; } if self.file.is_some() { state.serialize_field("file", &self.file)?; } state.end() } } impl Serialize for LocationMessageEventContent { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut len = 3; if self.info.is_some() { len += 1; } let mut state = serializer.serialize_struct("LocationMessageEventContent", len)?; state.serialize_field("body", &self.body)?; state.serialize_field("geo_uri", &self.geo_uri)?; state.serialize_field("msgtype", "m.location")?; if self.info.is_some() { state.serialize_field("info", &self.info)?; } state.end() } } impl Serialize for NoticeMessageEventContent { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut len = 2; if self.relates_to.is_some() { len += 1; } let mut state = serializer.serialize_struct("NoticeMessageEventContent", len)?; state.serialize_field("body", &self.body)?; state.serialize_field("msgtype", "m.notice")?; if self.relates_to.is_some() { state.serialize_field("m.relates_to", &self.relates_to)?; } state.end() } } impl Serialize for ServerNoticeMessageEventContent { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut len = 3; if self.admin_contact.is_some() { len += 1; } if self.limit_type.is_some() { len += 1; } let mut state = serializer.serialize_struct("ServerNoticeMessageEventContent", len)?; state.serialize_field("body", &self.body)?; state.serialize_field("msgtype", "m.server_notice")?; state.serialize_field("server_notice_type", &self.server_notice_type)?; if self.admin_contact.is_some() { state.serialize_field("admin_contact", &self.admin_contact)?; } if self.limit_type.is_some() { state.serialize_field("limit_type", &self.limit_type)?; } state.end() } } impl Serialize for TextMessageEventContent { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut len = 2; if self.format.is_some() { len += 1; } if self.formatted_body.is_some() { len += 1; } if self.relates_to.is_some() { len += 1; } let mut state = serializer.serialize_struct("TextMessageEventContent", len)?; state.serialize_field("body", &self.body)?; if self.format.is_some() { state.serialize_field("format", &self.format)?; } if self.formatted_body.is_some() { state.serialize_field("formatted_body", &self.formatted_body)?; } state.serialize_field("msgtype", "m.text")?; if self.relates_to.is_some() { state.serialize_field("m.relates_to", &self.relates_to)?; } state.end() } } impl Serialize for VideoMessageEventContent { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut len = 2; if self.info.is_some() { len += 1; } if self.url.is_some() { len += 1; } if self.file.is_some() { len += 1; } let mut state = serializer.serialize_struct("VideoMessageEventContent", len)?; state.serialize_field("body", &self.body)?; state.serialize_field("msgtype", "m.video")?; if self.info.is_some() { state.serialize_field("info", &self.info)?; } if self.url.is_some() { state.serialize_field("url", &self.url)?; } if self.file.is_some() { state.serialize_field("file", &self.file)?; } state.end() } } #[cfg(test)] mod tests { use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; use super::{AudioMessageEventContent, MessageEventContent}; use crate::room::message::{InReplyTo, RelatesTo, TextMessageEventContent}; use crate::EventResult; use ruma_identifiers::EventId; use std::convert::TryFrom; #[test] fn serialization() { let message_event_content = MessageEventContent::Audio(AudioMessageEventContent { body: "test".to_string(), info: None, url: Some("http://example.com/audio.mp3".to_string()), file: None, }); assert_eq!( to_json_value(&message_event_content).unwrap(), json!({ "body": "test", "msgtype": "m.audio", "url": "http://example.com/audio.mp3" }) ); } #[test] fn plain_text() { let message_event_content = MessageEventContent::Text(TextMessageEventContent::new_plain( "> <@test:example.com> test\n\ntest reply", )); assert_eq!( to_json_value(&message_event_content).unwrap(), json!({ "body": "> <@test:example.com> test\n\ntest reply", "msgtype": "m.text" }) ); } #[test] fn relates_to_serialization() { let message_event_content = MessageEventContent::Text(TextMessageEventContent { body: "> <@test:example.com> test\n\ntest reply".to_owned(), format: None, formatted_body: None, relates_to: Some(RelatesTo { in_reply_to: InReplyTo { event_id: EventId::try_from("$15827405538098VGFWH:example.com").unwrap(), }, }), }); let json_data = json!({ "body": "> <@test:example.com> test\n\ntest reply", "msgtype": "m.text", "m.relates_to": { "m.in_reply_to": { "event_id": "$15827405538098VGFWH:example.com" } } }); assert_eq!(to_json_value(&message_event_content).unwrap(), json_data); } #[test] fn deserialization() { let message_event_content = MessageEventContent::Audio(AudioMessageEventContent { body: "test".to_string(), info: None, url: Some("http://example.com/audio.mp3".to_string()), file: None, }); let json_data = json!({ "body": "test", "msgtype": "m.audio", "url": "http://example.com/audio.mp3" }); assert_eq!( from_json_value::>(json_data) .unwrap() .into_result() .unwrap(), message_event_content ); } #[test] fn deserialization_failure() { let json_data = json!({ "body": "test","msgtype": "m.location", "url": "http://example.com/audio.mp3" }); assert!( from_json_value::>(json_data) .unwrap() .into_result() .is_err() ); } }