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