diff --git a/crates/ruma-common/src/events/message.rs b/crates/ruma-common/src/events/message.rs index 1757d649..a6c8439e 100644 --- a/crates/ruma-common/src/events/message.rs +++ b/crates/ruma-common/src/events/message.rs @@ -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); impl MessageContent { + /// Create a `MessageContent` from an array of messages. + /// + /// Returns `None` if the array is empty. + pub fn new(messages: Vec) -> Option { + if messages.is_empty() { + None + } else { + Some(Self(messages)) + } + } + /// A convenience constructor to create a plain text message. pub fn plain(body: impl Into) -> Self { Self(vec![Text::plain(body)]) @@ -178,9 +192,17 @@ impl MessageContent { } } -impl From> for MessageContent { - fn from(variants: Vec) -> 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> for MessageContent { + type Error = EmptyMessageContentError; + + fn try_from(messages: Vec) -> Result { + Self::new(messages).ok_or(EmptyMessageContentError) } } diff --git a/crates/ruma-common/src/events/message/content_serde.rs b/crates/ruma-common/src/events/message/content_serde.rs index 63a1213f..af70868a 100644 --- a/crates/ruma-common/src/events/message/content_serde.rs +++ b/crates/ruma-common/src/events/message/content_serde.rs @@ -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::>::deserialize(deserializer) - .map(|content| content.filter(|content| !content.is_empty()).map(Into::into)) + Option::>::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") + }) + }) } } diff --git a/crates/ruma-common/tests/events/message.rs b/crates/ruma-common/tests/events/message.rs index aedc831c..0def174a 100644 --- a/crates/ruma-common/tests/events/message.rs +++ b/crates/ruma-common/tests/events/message.rs @@ -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"},