Move new_content and relates_to fields to MessageEventContent struct

MessageEventContent used to be an enum, it now has a msgtype field where
the enum (now called MessageType) sits.
This commit is contained in:
Devin Ragotzy 2021-02-12 18:29:07 -05:00 committed by GitHub
parent 798cd49e9e
commit 12c294422b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 194 additions and 139 deletions

View File

@ -5,7 +5,7 @@ use http::Uri;
use ruma::{
api::client::r0::{filter::FilterDefinition, sync::sync_events},
events::{
room::message::{MessageEventContent, TextMessageEventContent},
room::message::{MessageEventContent, MessageType, TextMessageEventContent},
AnySyncMessageEvent, AnySyncRoomEvent, SyncMessageEvent,
},
presence::PresenceState,
@ -40,9 +40,13 @@ async fn log_messages(homeserver_url: Uri, username: &str, password: &str) -> an
if let AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomMessage(
SyncMessageEvent {
content:
MessageEventContent::Text(TextMessageEventContent {
body: msg_body, ..
}),
MessageEventContent {
msgtype:
MessageType::Text(TextMessageEventContent {
body: msg_body, ..
}),
..
},
sender,
..
},

View File

@ -34,8 +34,63 @@ pub type MessageEvent = OuterMessageEvent<MessageEventContent>;
#[derive(Clone, Debug, Serialize, MessageEventContent)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[ruma_event(type = "m.room.message")]
#[serde(untagged)]
pub enum MessageEventContent {
pub struct MessageEventContent {
/// A key which identifies the type of message being sent.
///
/// This also holds the specific content of each message.
#[serde(flatten)]
pub msgtype: MessageType,
/// Information about related messages for
/// [rich replies](https://matrix.org/docs/spec/client_server/r0.6.1#rich-replies).
#[serde(rename = "m.relates_to", skip_serializing_if = "Option::is_none")]
pub relates_to: Option<Relation>,
/// New content of an edited message.
///
/// This should only be set if `relates_to` is `Some(Relation::Replacement(_))`.
#[cfg(feature = "unstable-pre-spec")]
#[serde(rename = "m.new_content", skip_serializing_if = "Option::is_none")]
pub new_content: Option<Box<MessageEventContent>>,
}
impl MessageEventContent {
/// Create a `MessageEventContent` with the given `MessageType`.
pub fn new(msgtype: MessageType) -> Self {
Self {
msgtype,
relates_to: None,
#[cfg(feature = "unstable-pre-spec")]
new_content: None,
}
}
/// A constructor to create a plain text message.
pub fn text_plain(body: impl Into<String>) -> Self {
Self::new(MessageType::Text(TextMessageEventContent::plain(body)))
}
/// A constructor to create an html message.
pub fn text_html(body: impl Into<String>, html_body: impl Into<String>) -> Self {
Self::new(MessageType::Text(TextMessageEventContent::html(body, html_body)))
}
/// A constructor to create a plain text notice.
pub fn notice_plain(body: impl Into<String>) -> Self {
Self::new(MessageType::Notice(NoticeMessageEventContent::plain(body)))
}
/// A constructor to create an html notice.
pub fn notice_html(body: impl Into<String>, html_body: impl Into<String>) -> Self {
Self::new(MessageType::Notice(NoticeMessageEventContent::html(body, html_body)))
}
}
/// The content that is specific to each message type variant.
#[derive(Clone, Debug, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[serde(tag = "msgtype")]
pub enum MessageType {
/// An audio message.
Audio(AudioMessageEventContent),
@ -72,6 +127,12 @@ pub enum MessageEventContent {
_Custom(CustomEventContent),
}
impl From<MessageType> for MessageEventContent {
fn from(msgtype: MessageType) -> Self {
Self::new(msgtype)
}
}
/// Enum modeling the different ways relationships can be expressed in a
/// `m.relates_to` field of an m.room.message event.
#[derive(Clone, Debug, Deserialize, Serialize)]
@ -132,28 +193,6 @@ impl From<RelatesToJsonRepr> for Relation {
}
}
impl MessageEventContent {
/// A convenience constructor to create a plain text message.
pub fn text_plain(body: impl Into<String>) -> Self {
Self::Text(TextMessageEventContent::plain(body))
}
/// A convenience constructor to create an html message.
pub fn text_html(body: impl Into<String>, html_body: impl Into<String>) -> Self {
Self::Text(TextMessageEventContent::html(body, html_body))
}
/// A convenience constructor to create an plain text notice.
pub fn notice_plain(body: impl Into<String>) -> Self {
Self::Notice(NoticeMessageEventContent::plain(body))
}
/// A convenience constructor to create an html notice.
pub fn notice_html(body: impl Into<String>, html_body: impl Into<String>) -> Self {
Self::Notice(NoticeMessageEventContent::html(body, html_body))
}
}
/// The payload for an audio message.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(tag = "msgtype", rename = "m.audio")]
@ -321,30 +360,12 @@ pub struct NoticeMessageEventContent {
/// Formatted form of the message `body`.
#[serde(flatten)]
pub formatted: Option<FormattedBody>,
/// Information about related messages for
/// [rich replies](https://matrix.org/docs/spec/client_server/r0.6.1#rich-replies).
#[serde(rename = "m.relates_to", skip_serializing_if = "Option::is_none")]
pub relates_to: Option<Relation>,
/// New content of an edited message.
///
/// This should only be set if `relates_to` is `Some(Relation::Replacement(_))`.
#[cfg(feature = "unstable-pre-spec")]
#[serde(rename = "m.new_content", skip_serializing_if = "Option::is_none")]
pub new_content: Option<Box<MessageEventContent>>,
}
impl NoticeMessageEventContent {
/// A convenience constructor to create a plain text notice.
pub fn plain(body: impl Into<String>) -> Self {
Self {
body: body.into(),
formatted: None,
relates_to: None,
#[cfg(feature = "unstable-pre-spec")]
new_content: None,
}
Self { body: body.into(), formatted: None }
}
/// A convenience constructor to create an html notice.
@ -453,30 +474,12 @@ pub struct TextMessageEventContent {
/// Formatted form of the message `body`.
#[serde(flatten)]
pub formatted: Option<FormattedBody>,
/// Information about related messages for
/// [rich replies](https://matrix.org/docs/spec/client_server/r0.6.1#rich-replies).
#[serde(rename = "m.relates_to", skip_serializing_if = "Option::is_none")]
pub relates_to: Option<Relation>,
/// New content of an edited message.
///
/// This should only be set if `relates_to` is `Some(Relation::Replacement(_))`.
#[cfg(feature = "unstable-pre-spec")]
#[serde(rename = "m.new_content", skip_serializing_if = "Option::is_none")]
pub new_content: Option<Box<MessageEventContent>>,
}
impl TextMessageEventContent {
/// A convenience constructor to create a plain text message.
pub fn plain(body: impl Into<String>) -> Self {
Self {
body: body.into(),
formatted: None,
relates_to: None,
#[cfg(feature = "unstable-pre-spec")]
new_content: None,
}
Self { body: body.into(), formatted: None }
}
/// A convenience constructor to create an html message.

View File

@ -1,15 +1,23 @@
//! `Deserialize` implementation for MessageEventContent
//! `Deserialize` implementation for MessageEventContent and MessageType.
use serde::{de, Deserialize};
use serde_json::value::RawValue as RawJsonValue;
use crate::{from_raw_json_value, room::message::MessageEventContent};
use crate::{
from_raw_json_value,
room::message::{MessageEventContent, MessageType, Relation},
};
/// Helper struct to determine the msgtype from a `serde_json::value::RawValue`
/// Helper struct to determine the msgtype, relates_to and new_content fields
/// from a `serde_json::value::RawValue`
#[derive(Debug, Deserialize)]
struct MessageDeHelper {
/// The message type field
msgtype: String,
struct MessageContentDeHelper {
#[serde(rename = "m.relates_to")]
relates_to: Option<Relation>,
#[cfg(feature = "unstable-pre-spec")]
#[serde(rename = "m.new_content")]
new_content: Option<Box<MessageEventContent>>,
}
impl<'de> de::Deserialize<'de> for MessageEventContent {
@ -18,7 +26,31 @@ impl<'de> de::Deserialize<'de> for MessageEventContent {
D: de::Deserializer<'de>,
{
let json = Box::<RawJsonValue>::deserialize(deserializer)?;
let MessageDeHelper { msgtype } = from_raw_json_value(&json)?;
let helper = from_raw_json_value::<MessageContentDeHelper, D::Error>(&json)?;
Ok(Self {
msgtype: from_raw_json_value(&json)?,
relates_to: helper.relates_to,
#[cfg(feature = "unstable-pre-spec")]
new_content: helper.new_content,
})
}
}
/// Helper struct to determine the msgtype from a `serde_json::value::RawValue`
#[derive(Debug, Deserialize)]
struct MessageTypeDeHelper {
/// The message type field
msgtype: String,
}
impl<'de> de::Deserialize<'de> for MessageType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
let json = Box::<RawJsonValue>::deserialize(deserializer)?;
let MessageTypeDeHelper { msgtype } = from_raw_json_value(&json)?;
Ok(match msgtype.as_ref() {
"m.audio" => Self::Audio(from_raw_json_value(&json)?),

View File

@ -5,7 +5,7 @@ use serde_json::{from_value as from_json_value, json, Value as JsonValue};
use ruma_events::{
room::{
aliases::AliasesEventContent,
message::{MessageEventContent, TextMessageEventContent},
message::{MessageEventContent, MessageType, TextMessageEventContent},
power_levels::PowerLevelsEventContent,
},
AnyEvent, AnyMessageEvent, AnyRoomEvent, AnyStateEvent, AnyStateEventContent,
@ -137,12 +137,14 @@ fn message_event_sync_deserialization() {
from_json_value::<AnySyncRoomEvent>(json_data),
Ok(AnySyncRoomEvent::Message(
AnySyncMessageEvent::RoomMessage(SyncMessageEvent {
content: MessageEventContent::Text(TextMessageEventContent {
body,
formatted: Some(formatted),
relates_to: None,
content: MessageEventContent {
msgtype: MessageType::Text(TextMessageEventContent {
body,
formatted: Some(formatted),
..
}),
..
}),
},
..
})
))
@ -177,12 +179,14 @@ fn message_room_event_deserialization() {
from_json_value::<AnyRoomEvent>(json_data),
Ok(AnyRoomEvent::Message(
AnyMessageEvent::RoomMessage(MessageEvent {
content: MessageEventContent::Text(TextMessageEventContent {
body,
formatted: Some(formatted),
relates_to: None,
content: MessageEventContent {
msgtype: MessageType::Text(TextMessageEventContent {
body,
formatted: Some(formatted),
..
}),
..
}),
},
..
})
))
@ -217,12 +221,14 @@ fn message_event_deserialization() {
from_json_value::<AnyEvent>(json_data),
Ok(AnyEvent::Message(
AnyMessageEvent::RoomMessage(MessageEvent {
content: MessageEventContent::Text(TextMessageEventContent {
body,
formatted: Some(formatted),
relates_to: None,
content: MessageEventContent {
msgtype: MessageType::Text(TextMessageEventContent {
body,
formatted: Some(formatted),
..
}),
..
}),
},
..
})
))

View File

@ -11,7 +11,7 @@ use ruma_events::{
room::{
message::{
AudioMessageEventContent, CustomEventContent, MessageEvent, MessageEventContent,
Relation, TextMessageEventContent,
MessageType, Relation, TextMessageEventContent,
},
relationships::InReplyTo,
},
@ -26,12 +26,12 @@ use serde_json::{from_value as from_json_value, json, to_value as to_json_value}
#[test]
fn serialization() {
let ev = MessageEvent {
content: MessageEventContent::Audio(AudioMessageEventContent {
content: MessageEventContent::new(MessageType::Audio(AudioMessageEventContent {
body: "test".into(),
info: None,
url: Some("http://example.com/audio.mp3".into()),
file: None,
}),
})),
event_id: event_id!("$143273582443PhrSn:example.org"),
origin_server_ts: UNIX_EPOCH + Duration::from_millis(10_000),
room_id: room_id!("!testroomid:example.org"),
@ -58,12 +58,13 @@ fn serialization() {
#[test]
fn content_serialization() {
let message_event_content = MessageEventContent::Audio(AudioMessageEventContent {
body: "test".into(),
info: None,
url: Some("http://example.com/audio.mp3".into()),
file: None,
});
let message_event_content =
MessageEventContent::new(MessageType::Audio(AudioMessageEventContent {
body: "test".into(),
info: None,
url: Some("http://example.com/audio.mp3".into()),
file: None,
}));
assert_eq!(
to_json_value(&message_event_content).unwrap(),
@ -81,7 +82,7 @@ fn custom_content_serialization() {
"custom_field".into() => json!("baba"),
"another_one".into() => json!("abab"),
};
let custom_event_content = MessageEventContent::_Custom(CustomEventContent {
let custom_event_content = MessageType::_Custom(CustomEventContent {
msgtype: "my_custom_msgtype".into(),
data: json_data,
});
@ -110,11 +111,11 @@ fn custom_content_deserialization() {
};
assert_matches!(
from_json_value::<Raw<MessageEventContent>>(json_data)
from_json_value::<Raw<MessageType>>(json_data)
.unwrap()
.deserialize()
.unwrap(),
MessageEventContent::_Custom(CustomEventContent {
MessageType::_Custom(CustomEventContent {
msgtype,
data
}) if msgtype == "my_custom_msgtype"
@ -124,10 +125,8 @@ fn custom_content_deserialization() {
#[test]
fn formatted_body_serialization() {
let message_event_content = MessageEventContent::Text(TextMessageEventContent::html(
"Hello, World!",
"Hello, <em>World</em>!",
));
let message_event_content =
MessageEventContent::text_html("Hello, World!", "Hello, <em>World</em>!");
assert_eq!(
to_json_value(&message_event_content).unwrap(),
@ -142,9 +141,8 @@ fn formatted_body_serialization() {
#[test]
fn plain_text_content_serialization() {
let message_event_content = MessageEventContent::Text(TextMessageEventContent::plain(
"> <@test:example.com> test\n\ntest reply",
));
let message_event_content =
MessageEventContent::text_plain("> <@test:example.com> test\n\ntest reply");
assert_eq!(
to_json_value(&message_event_content).unwrap(),
@ -157,13 +155,12 @@ fn plain_text_content_serialization() {
#[test]
fn relates_to_content_serialization() {
let message_event_content = MessageEventContent::Text(
assign!(TextMessageEventContent::plain("> <@test:example.com> test\n\ntest reply"), {
let message_event_content =
assign!(MessageEventContent::text_plain("> <@test:example.com> test\n\ntest reply"), {
relates_to: Some(Relation::Reply {
in_reply_to: InReplyTo { event_id: event_id!("$15827405538098VGFWH:example.com") },
}),
}),
);
});
let json_data = json!({
"body": "> <@test:example.com> test\n\ntest reply",
@ -195,12 +192,15 @@ fn edit_deserialization_061() {
assert_matches!(
from_json_value::<MessageEventContent>(json_data).unwrap(),
MessageEventContent::Text(TextMessageEventContent {
body,
formatted: None,
MessageEventContent {
msgtype: MessageType::Text(TextMessageEventContent {
body,
formatted: None,
..
}),
relates_to: Some(Relation::Custom(_)),
..
}) if body == "s/foo/bar"
} if body == "s/foo/bar"
);
}
@ -225,23 +225,27 @@ fn edit_deserialization_future() {
assert_matches!(
from_json_value::<MessageEventContent>(json_data).unwrap(),
MessageEventContent::Text(TextMessageEventContent {
body,
formatted: None,
MessageEventContent {
msgtype: MessageType::Text(TextMessageEventContent {
body,
formatted: None,
..
}),
relates_to: Some(Relation::Replacement(Replacement { event_id })),
new_content: Some(new_content),
..
}) if body == "s/foo/bar"
} if body == "s/foo/bar"
&& event_id == ev_id
&& matches!(
&*new_content,
MessageEventContent::Text(TextMessageEventContent {
body,
formatted: None,
relates_to: None,
new_content: None,
MessageEventContent {
msgtype: MessageType::Text(TextMessageEventContent {
body,
formatted: None,
..
}),
..
}) if body == "bar"
} if body == "bar"
)
);
}
@ -266,12 +270,15 @@ fn verification_request_deserialization() {
assert_matches!(
from_json_value::<MessageEventContent>(json_data).unwrap(),
MessageEventContent::VerificationRequest(KeyVerificationRequestEventContent {
body,
to,
from_device,
methods,
}) if body == "@example:localhost is requesting to verify your key, ..."
MessageEventContent {
msgtype: MessageType::VerificationRequest(KeyVerificationRequestEventContent {
body,
to,
from_device,
methods,
}),
..
} if body == "@example:localhost is requesting to verify your key, ..."
&& to == user_id
&& from_device == device_id
&& methods.contains(&VerificationMethod::MSasV1)
@ -299,7 +306,7 @@ fn verification_request_serialization() {
"methods": methods
});
let content = MessageEventContent::VerificationRequest(KeyVerificationRequestEventContent {
let content = MessageType::VerificationRequest(KeyVerificationRequestEventContent {
to: user_id,
from_device: device_id,
body,
@ -322,12 +329,15 @@ fn content_deserialization() {
.unwrap()
.deserialize()
.unwrap(),
MessageEventContent::Audio(AudioMessageEventContent {
body,
info: None,
url: Some(url),
file: None,
}) if body == "test" && url == "http://example.com/audio.mp3"
MessageEventContent {
msgtype: MessageType::Audio(AudioMessageEventContent {
body,
info: None,
url: Some(url),
file: None,
}),
..
} if body == "test" && url == "http://example.com/audio.mp3"
);
}