events: Enforce MessageContent to not be empty
This commit is contained in:
parent
f9390c7c35
commit
685bd34fd4
@ -47,7 +47,7 @@
|
|||||||
//! [MSC3245]: https://github.com/matrix-org/matrix-spec-proposals/pull/3245
|
//! [MSC3245]: https://github.com/matrix-org/matrix-spec-proposals/pull/3245
|
||||||
//! [MSC3381]: https://github.com/matrix-org/matrix-spec-proposals/pull/3381
|
//! [MSC3381]: https://github.com/matrix-org/matrix-spec-proposals/pull/3381
|
||||||
//! [`RoomMessageEventContent`]: super::room::message::RoomMessageEventContent
|
//! [`RoomMessageEventContent`]: super::room::message::RoomMessageEventContent
|
||||||
use std::ops::Deref;
|
use std::{convert::TryFrom, ops::Deref};
|
||||||
|
|
||||||
use ruma_macros::EventContent;
|
use ruma_macros::EventContent;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -123,11 +123,25 @@ impl MessageEventContent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Text message content.
|
/// Text message content.
|
||||||
|
///
|
||||||
|
/// A `MessageContent` must contain at least one message to be used as a fallback text
|
||||||
|
/// representation.
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(try_from = "MessageContentSerDeHelper")]
|
#[serde(try_from = "MessageContentSerDeHelper")]
|
||||||
pub struct MessageContent(pub(crate) Vec<Text>);
|
pub struct MessageContent(pub(crate) Vec<Text>);
|
||||||
|
|
||||||
impl MessageContent {
|
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.
|
/// A convenience constructor to create a plain text message.
|
||||||
pub fn plain(body: impl Into<String>) -> Self {
|
pub fn plain(body: impl Into<String>) -> Self {
|
||||||
Self(vec![Text::plain(body)])
|
Self(vec![Text::plain(body)])
|
||||||
@ -178,9 +192,17 @@ impl MessageContent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Vec<Text>> for MessageContent {
|
/// The error type returned when trying to construct an empty `MessageContent`.
|
||||||
fn from(variants: Vec<Text>) -> Self {
|
#[derive(Debug, Error)]
|
||||||
Self(variants)
|
#[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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ impl Serialize for MessageContent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) mod as_vec {
|
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};
|
use crate::events::message::{MessageContent, Text};
|
||||||
|
|
||||||
@ -87,7 +87,10 @@ pub(crate) mod as_vec {
|
|||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
Option::<Vec<Text>>::deserialize(deserializer)
|
Option::<Vec<Text>>::deserialize(deserializer).and_then(|content| {
|
||||||
.map(|content| content.filter(|content| !content.is_empty()).map(Into::into))
|
content.map(MessageContent::new).ok_or_else(|| {
|
||||||
|
de::Error::invalid_value(de::Unexpected::Other("empty array"), &"a non-empty array")
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#![cfg(feature = "unstable-msc1767")]
|
#![cfg(feature = "unstable-msc1767")]
|
||||||
|
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use assign::assign;
|
use assign::assign;
|
||||||
use js_int::uint;
|
use js_int::uint;
|
||||||
use matches::assert_matches;
|
use matches::assert_matches;
|
||||||
@ -7,7 +9,7 @@ use ruma_common::{
|
|||||||
event_id,
|
event_id,
|
||||||
events::{
|
events::{
|
||||||
emote::EmoteEventContent,
|
emote::EmoteEventContent,
|
||||||
message::MessageEventContent,
|
message::{MessageContent, MessageEventContent, Text},
|
||||||
notice::NoticeEventContent,
|
notice::NoticeEventContent,
|
||||||
room::message::{
|
room::message::{
|
||||||
EmoteMessageEventContent, InReplyTo, MessageType, NoticeMessageEventContent, Relation,
|
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};
|
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]
|
#[test]
|
||||||
fn html_content_serialization() {
|
fn html_content_serialization() {
|
||||||
let message_event_content =
|
let message_event_content =
|
||||||
@ -712,13 +727,12 @@ fn room_message_emote_unstable_deserialization() {
|
|||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "unstable-msc3554")]
|
#[cfg(feature = "unstable-msc3554")]
|
||||||
fn lang_serialization() {
|
fn lang_serialization() {
|
||||||
use ruma_common::events::message::{MessageContent, Text};
|
let content = MessageContent::try_from(vec![
|
||||||
|
|
||||||
let content = MessageContent::from(vec![
|
|
||||||
assign!(Text::plain("Bonjour le monde !"), { lang: Some("fr".into()) }),
|
assign!(Text::plain("Bonjour le monde !"), { lang: Some("fr".into()) }),
|
||||||
assign!(Text::plain("Hallo Welt!"), { lang: Some("de".into()) }),
|
assign!(Text::plain("Hallo Welt!"), { lang: Some("de".into()) }),
|
||||||
assign!(Text::plain("Hello World!"), { lang: Some("en".into()) }),
|
assign!(Text::plain("Hello World!"), { lang: Some("en".into()) }),
|
||||||
]);
|
])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
to_json_value(&content).unwrap(),
|
to_json_value(&content).unwrap(),
|
||||||
@ -735,8 +749,6 @@ fn lang_serialization() {
|
|||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "unstable-msc3554")]
|
#[cfg(feature = "unstable-msc3554")]
|
||||||
fn lang_deserialization() {
|
fn lang_deserialization() {
|
||||||
use ruma_common::events::message::MessageContent;
|
|
||||||
|
|
||||||
let json_data = json!({
|
let json_data = json!({
|
||||||
"org.matrix.msc1767.message": [
|
"org.matrix.msc1767.message": [
|
||||||
{ "body": "Bonjour le monde !", "mimetype": "text/plain", "lang": "fr"},
|
{ "body": "Bonjour le monde !", "mimetype": "text/plain", "lang": "fr"},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user