events: Replace Captions with MessageContent

Use a custom serde implementation
This commit is contained in:
Kévin Commaille 2022-03-25 18:14:11 +01:00 committed by Kévin Commaille
parent 93b4114a82
commit 245bf75276
6 changed files with 58 additions and 89 deletions

View File

@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
use super::{
file::{EncryptedContent, FileContent},
message::{MessageContent, Text},
message::MessageContent,
room::message::Relation,
};
use crate::MxcUri;
@ -35,8 +35,13 @@ pub struct ImageEventContent {
pub thumbnail: Vec<ThumbnailContent>,
/// The captions of the message.
#[serde(rename = "m.caption", default, skip_serializing_if = "Captions::is_empty")]
pub caption: Captions,
#[serde(
rename = "m.caption",
with = "super::message::content_serde::as_vec",
default,
skip_serializing_if = "Option::is_none"
)]
pub caption: Option<MessageContent>,
/// Information about related messages for [rich replies].
///
@ -151,71 +156,6 @@ impl ThumbnailContent {
}
}
/// An array of captions.
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Captions(pub(crate) Vec<Text>);
impl Captions {
/// Creates a new `Captions` with the given captions.
///
/// The captions must be ordered by most preferred first.
pub fn new(captions: &[Text]) -> Self {
Self(captions.to_owned())
}
/// A convenience constructor to create a plain text caption.
pub fn plain(body: impl Into<String>) -> Self {
Self(vec![Text::plain(body)])
}
/// A convenience constructor to create an HTML caption.
pub fn html(body: impl Into<String>, html_body: impl Into<String>) -> Self {
Self(vec![Text::html(html_body), Text::plain(body)])
}
/// A convenience constructor to create a Markdown caption.
///
/// Returns an HTML caption if some Markdown formatting was detected, otherwise returns a plain
/// text caption.
#[cfg(feature = "markdown")]
pub fn markdown(body: impl AsRef<str> + Into<String>) -> Self {
let mut message = Vec::with_capacity(2);
if let Some(html_body) = Text::markdown(&body) {
message.push(html_body);
}
message.push(Text::plain(body));
Self(message)
}
/// Get the captions.
///
/// The captions are ordered by most preferred first.
pub fn captions(&self) -> &[Text] {
&self.0
}
/// Whether this is empty.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Get the plain text representation of this caption.
pub fn find_plain(&self) -> Option<&str> {
self.captions()
.iter()
.find(|content| content.mimetype == "text/plain")
.map(|content| content.body.as_ref())
}
/// Get the HTML representation of this caption.
pub fn find_html(&self) -> Option<&str> {
self.captions()
.iter()
.find(|content| content.mimetype == "text/html")
.map(|content| content.body.as_ref())
}
}
/// Image content.
#[derive(Default, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]

View File

@ -52,7 +52,7 @@ use std::ops::Deref;
use ruma_macros::EventContent;
use serde::{Deserialize, Serialize};
mod content_serde;
pub(crate) mod content_serde;
use content_serde::MessageContentSerDeHelper;

View File

@ -67,3 +67,34 @@ impl Serialize for MessageContent {
st.end()
}
}
pub(crate) mod as_vec {
use serde::{ser::SerializeSeq, Deserialize, Deserializer, Serializer};
use crate::events::message::{MessageContent, Text};
/// Serializes a `Option<MessageContent>` as a `Vec<Text>`.
pub fn serialize<S>(content: &Option<MessageContent>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if let Some(content) = content {
let mut seq = serializer.serialize_seq(Some(content.len()))?;
for e in content.iter() {
seq.serialize_element(e)?;
}
seq.end()
} else {
serializer.serialize_seq(Some(0))?.end()
}
}
/// Deserializes a `Vec<Text>` to an `Option<MessageContent>`.
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<MessageContent>, D::Error>
where
D: Deserializer<'de>,
{
Option::<Vec<Text>>::deserialize(deserializer)
.map(|content| content.filter(|content| !content.is_empty()).map(Into::into))
}
}

View File

@ -9,10 +9,7 @@ use ruma_macros::EventContent;
use serde::{Deserialize, Serialize};
use super::{
file::FileContent,
image::{Captions, ThumbnailContent},
message::MessageContent,
room::message::Relation,
file::FileContent, image::ThumbnailContent, message::MessageContent, room::message::Relation,
};
/// The payload for an extensible video message.
@ -37,8 +34,13 @@ pub struct VideoEventContent {
pub thumbnail: Vec<ThumbnailContent>,
/// The captions of the message.
#[serde(rename = "m.caption", default, skip_serializing_if = "Captions::is_empty")]
pub caption: Captions,
#[serde(
rename = "m.caption",
with = "super::message::content_serde::as_vec",
default,
skip_serializing_if = "Option::is_none"
)]
pub caption: Option<MessageContent>,
/// Information about related messages.
#[serde(flatten, skip_serializing_if = "Option::is_none")]

View File

@ -8,7 +8,7 @@ use ruma_common::{
events::{
file::{EncryptedContentInit, FileContent, FileContentInfo},
image::{
Captions, ImageContent, ImageEventContent, ThumbnailContent, ThumbnailFileContent,
ImageContent, ImageEventContent, ThumbnailContent, ThumbnailFileContent,
ThumbnailFileContentInfo,
},
message::MessageContent,
@ -128,7 +128,7 @@ fn image_event_serialization() {
),
None
)],
caption: Captions::plain("This is my house"),
caption: Some(MessageContent::plain("This is my house")),
relates_to: Some(Relation::Reply {
in_reply_to: InReplyTo::new(event_id!("$replyevent:example.com").to_owned()),
}),
@ -207,7 +207,7 @@ fn plain_content_deserialization() {
assert_matches!(
from_json_value::<ImageEventContent>(json_data)
.unwrap(),
ImageEventContent { message, file, image, thumbnail, caption, .. }
ImageEventContent { message, file, image, thumbnail, caption: Some(caption), .. }
if message.find_plain() == Some("Upload: my_cat.png")
&& message.find_html().is_none()
&& file.url == "mxc://notareal.hs/abcdef"
@ -248,7 +248,7 @@ fn encrypted_content_deserialization() {
assert_matches!(
from_json_value::<ImageEventContent>(json_data)
.unwrap(),
ImageEventContent { message, file, image, thumbnail, caption, .. }
ImageEventContent { message, file, image, thumbnail, caption: None, .. }
if message.find_plain() == Some("Upload: my_file.txt")
&& message.find_html().is_none()
&& file.url == "mxc://notareal.hs/abcdef"
@ -256,7 +256,6 @@ fn encrypted_content_deserialization() {
&& image.width.is_none()
&& image.height.is_none()
&& thumbnail[0].file.url == "mxc://notareal.hs/thumbnail"
&& caption.is_empty()
);
}
@ -295,7 +294,7 @@ fn message_event_deserialization() {
},
image,
thumbnail,
caption,
caption: None,
..
},
event_id,
@ -313,7 +312,6 @@ fn message_event_deserialization() {
&& image.width == Some(uint!(1300))
&& image.height == Some(uint!(837))
&& thumbnail.is_empty()
&& caption.is_empty()
&& origin_server_ts == MilliSecondsSinceUnixEpoch(uint!(134_829_848))
&& room_id == room_id!("!roomid:notareal.hs")
&& sender == user_id!("@user:notareal.hs")

View File

@ -9,7 +9,7 @@ use ruma_common::{
event_id,
events::{
file::{EncryptedContentInit, FileContent, FileContentInfo},
image::{Captions, ThumbnailContent, ThumbnailFileContent, ThumbnailFileContentInfo},
image::{ThumbnailContent, ThumbnailFileContent, ThumbnailFileContentInfo},
message::MessageContent,
room::{
message::{InReplyTo, Relation},
@ -135,7 +135,7 @@ fn event_serialization() {
),
None
)],
caption: Captions::plain("This is my awesome vintage lava lamp"),
caption: Some(MessageContent::plain("This is my awesome vintage lava lamp")),
relates_to: Some(Relation::Reply {
in_reply_to: InReplyTo::new(event_id!("$replyevent:example.com").to_owned()),
}),
@ -215,7 +215,7 @@ fn plain_content_deserialization() {
assert_matches!(
from_json_value::<VideoEventContent>(json_data)
.unwrap(),
VideoEventContent { message, file, video, thumbnail, caption, .. }
VideoEventContent { message, file, video, thumbnail, caption: Some(caption), .. }
if message.find_plain() == Some("Video: my_cat.mp4")
&& message.find_html().is_none()
&& file.url == "mxc://notareal.hs/abcdef"
@ -257,7 +257,7 @@ fn encrypted_content_deserialization() {
assert_matches!(
from_json_value::<VideoEventContent>(json_data)
.unwrap(),
VideoEventContent { message, file, video, thumbnail, caption, .. }
VideoEventContent { message, file, video, thumbnail, caption: None, .. }
if message.find_plain() == Some("Upload: my_cat.mp4")
&& message.find_html().is_none()
&& file.url == "mxc://notareal.hs/abcdef"
@ -266,7 +266,6 @@ fn encrypted_content_deserialization() {
&& video.height.is_none()
&& video.duration.is_none()
&& thumbnail[0].file.url == "mxc://notareal.hs/thumbnail"
&& caption.is_empty()
);
}
@ -305,7 +304,7 @@ fn message_event_deserialization() {
},
video,
thumbnail,
caption,
caption: None,
..
},
event_id,
@ -324,7 +323,6 @@ fn message_event_deserialization() {
&& video.height == Some(uint!(837))
&& video.duration.is_none()
&& thumbnail.is_empty()
&& caption.is_empty()
&& origin_server_ts == MilliSecondsSinceUnixEpoch(uint!(134_829_848))
&& room_id == room_id!("!roomid:notareal.hs")
&& sender == user_id!("@user:notareal.hs")