events: Enforce MessageContent to not be empty

This commit is contained in:
Kévin Commaille 2022-03-26 09:39:29 +01:00 committed by Kévin Commaille
parent f9390c7c35
commit 685bd34fd4
3 changed files with 51 additions and 14 deletions

View File

@ -47,7 +47,7 @@
//! [MSC3245]: https://github.com/matrix-org/matrix-spec-proposals/pull/3245
//! [MSC3381]: https://github.com/matrix-org/matrix-spec-proposals/pull/3381
//! [`RoomMessageEventContent`]: super::room::message::RoomMessageEventContent
use std::ops::Deref;
use std::{convert::TryFrom, ops::Deref};
use ruma_macros::EventContent;
use serde::{Deserialize, Serialize};
@ -123,11 +123,25 @@ impl MessageEventContent {
}
/// Text message content.
///
/// A `MessageContent` must contain at least one message to be used as a fallback text
/// representation.
#[derive(Clone, Debug, Deserialize)]
#[serde(try_from = "MessageContentSerDeHelper")]
pub struct MessageContent(pub(crate) Vec<Text>);
impl MessageContent {
/// Create a `MessageContent` from an array of messages.
///
/// Returns `None` if the array is empty.
pub fn new(messages: Vec<Text>) -> Option<Self> {
if messages.is_empty() {
None
} else {
Some(Self(messages))
}
}
/// A convenience constructor to create a plain text message.
pub fn plain(body: impl Into<String>) -> Self {
Self(vec![Text::plain(body)])
@ -178,9 +192,17 @@ impl MessageContent {
}
}
impl From<Vec<Text>> for MessageContent {
fn from(variants: Vec<Text>) -> Self {
Self(variants)
/// The error type returned when trying to construct an empty `MessageContent`.
#[derive(Debug, Error)]
#[non_exhaustive]
#[error("MessageContent cannot be empty")]
pub struct EmptyMessageContentError;
impl TryFrom<Vec<Text>> for MessageContent {
type Error = EmptyMessageContentError;
fn try_from(messages: Vec<Text>) -> Result<Self, Self::Error> {
Self::new(messages).ok_or(EmptyMessageContentError)
}
}

View File

@ -62,7 +62,7 @@ impl Serialize for MessageContent {
}
pub(crate) mod as_vec {
use serde::{ser::SerializeSeq, Deserialize, Deserializer, Serializer};
use serde::{de, ser::SerializeSeq, Deserialize, Deserializer, Serializer};
use crate::events::message::{MessageContent, Text};
@ -87,7 +87,10 @@ pub(crate) mod as_vec {
where
D: Deserializer<'de>,
{
Option::<Vec<Text>>::deserialize(deserializer)
.map(|content| content.filter(|content| !content.is_empty()).map(Into::into))
Option::<Vec<Text>>::deserialize(deserializer).and_then(|content| {
content.map(MessageContent::new).ok_or_else(|| {
de::Error::invalid_value(de::Unexpected::Other("empty array"), &"a non-empty array")
})
})
}
}

View File

@ -1,5 +1,7 @@
#![cfg(feature = "unstable-msc1767")]
use std::convert::TryFrom;
use assign::assign;
use js_int::uint;
use matches::assert_matches;
@ -7,7 +9,7 @@ use ruma_common::{
event_id,
events::{
emote::EmoteEventContent,
message::MessageEventContent,
message::{MessageContent, MessageEventContent, Text},
notice::NoticeEventContent,
room::message::{
EmoteMessageEventContent, InReplyTo, MessageType, NoticeMessageEventContent, Relation,
@ -19,6 +21,19 @@ use ruma_common::{
};
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
#[test]
fn try_from_valid() {
assert_matches!(
MessageContent::try_from(vec![Text::plain("A message")]),
Ok(message) if message.len() == 1
);
}
#[test]
fn try_from_invalid() {
assert_matches!(MessageContent::try_from(vec![]), Err(_));
}
#[test]
fn html_content_serialization() {
let message_event_content =
@ -712,13 +727,12 @@ fn room_message_emote_unstable_deserialization() {
#[test]
#[cfg(feature = "unstable-msc3554")]
fn lang_serialization() {
use ruma_common::events::message::{MessageContent, Text};
let content = MessageContent::from(vec![
let content = MessageContent::try_from(vec![
assign!(Text::plain("Bonjour le monde !"), { lang: Some("fr".into()) }),
assign!(Text::plain("Hallo Welt!"), { lang: Some("de".into()) }),
assign!(Text::plain("Hello World!"), { lang: Some("en".into()) }),
]);
])
.unwrap();
assert_eq!(
to_json_value(&content).unwrap(),
@ -735,8 +749,6 @@ fn lang_serialization() {
#[test]
#[cfg(feature = "unstable-msc3554")]
fn lang_deserialization() {
use ruma_common::events::message::MessageContent;
let json_data = json!({
"org.matrix.msc1767.message": [
{ "body": "Bonjour le monde !", "mimetype": "text/plain", "lang": "fr"},