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
 | ||||
| //! [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) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -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") | ||||
|             }) | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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"}, | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user