events: Update types according to changes in MSCs 1767, 3954, 3955 and 3956

This commit is contained in:
Kévin Commaille 2023-01-03 21:42:41 +01:00 committed by Kévin Commaille
parent ba237a9cfd
commit 8477efb2ef
26 changed files with 790 additions and 875 deletions

View File

@ -38,12 +38,15 @@ unstable-msc3245 = ["unstable-msc3246"]
unstable-msc3246 = ["unstable-msc3551"]
unstable-msc3381 = ["unstable-msc1767"]
unstable-msc3488 = ["unstable-msc1767"]
unstable-msc3551 = ["unstable-msc1767"]
unstable-msc3551 = ["unstable-msc3956"]
unstable-msc3552 = ["unstable-msc3551"]
unstable-msc3553 = ["unstable-msc3552"]
unstable-msc3554 = ["unstable-msc1767"]
unstable-msc3931 = []
unstable-msc3932 = ["unstable-msc3931"]
unstable-msc3954 = ["unstable-msc1767"]
unstable-msc3955 = ["unstable-msc1767"]
unstable-msc3956 = ["unstable-msc1767"]
unstable-pdu = []
unstable-sanitize = ["dep:html5ever", "dep:phf"]
unstable-unspecified = []

View File

@ -125,8 +125,10 @@ pub mod audio;
pub mod call;
pub mod direct;
pub mod dummy;
#[cfg(feature = "unstable-msc1767")]
#[cfg(feature = "unstable-msc3954")]
pub mod emote;
#[cfg(feature = "unstable-msc3956")]
pub mod encrypted;
#[cfg(feature = "unstable-msc3551")]
pub mod file;
pub mod forwarded_room_key;
@ -140,8 +142,6 @@ pub mod key;
pub mod location;
#[cfg(feature = "unstable-msc1767")]
pub mod message;
#[cfg(feature = "unstable-msc1767")]
pub mod notice;
#[cfg(feature = "unstable-pdu")]
pub mod pdu;
pub mod policy;

View File

@ -13,12 +13,12 @@ mod waveform_serde;
use waveform_serde::WaveformSerDeHelper;
use super::{file::FileContent, message::MessageContent, room::message::Relation};
use super::{file::FileContent, message::TextContentBlock, room::message::Relation};
/// The payload for an extensible audio message.
///
/// This is the new primary type introduced in [MSC3246] and should not be sent before the end of
/// the transition period. See the documentation of the [`message`] module for more information.
/// This is the new primary type introduced in [MSC3246] and should only be sent in rooms with a
/// version that supports it. See the documentation of the [`message`] module for more information.
///
/// [MSC3246]: https://github.com/matrix-org/matrix-spec-proposals/pull/3246
/// [`message`]: super::message
@ -26,9 +26,9 @@ use super::{file::FileContent, message::MessageContent, room::message::Relation}
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[ruma_event(type = "m.audio", kind = MessageLike, without_relation)]
pub struct AudioEventContent {
/// The text representation of the message.
#[serde(flatten)]
pub message: MessageContent,
/// The text representations of the message.
#[serde(rename = "org.matrix.msc1767.text")]
pub text: TextContentBlock,
/// The file content of the message.
#[serde(rename = "m.file")]
@ -48,20 +48,21 @@ pub struct AudioEventContent {
}
impl AudioEventContent {
/// Creates a new `AudioEventContent` with the given plain text message and file.
pub fn plain(message: impl Into<String>, file: FileContent) -> Self {
/// Creates a new `AudioEventContent` with the given text fallback and file.
pub fn new(text: TextContentBlock, file: FileContent) -> Self {
Self { text, file, audio: Default::default(), relates_to: None }
}
/// Creates a new `AudioEventContent` with the given plain text fallback representation and
/// file.
pub fn plain(text_fallback: impl Into<String>, file: FileContent) -> Self {
Self {
message: MessageContent::plain(message),
text: TextContentBlock::plain(text_fallback),
file,
audio: Default::default(),
relates_to: None,
}
}
/// Creates a new `AudioEventContent` with the given message and file.
pub fn with_message(message: MessageContent, file: FileContent) -> Self {
Self { message, file, audio: Default::default(), relates_to: None }
}
}
/// Audio content.

View File

@ -1,26 +1,38 @@
//! Types for extensible emote message events ([MSC1767]).
//! Types for extensible emote message events ([MSC3954]).
//!
//! [MSC1767]: https://github.com/matrix-org/matrix-spec-proposals/pull/1767
//! [MSC3954]: https://github.com/matrix-org/matrix-spec-proposals/pull/3954
use ruma_macros::EventContent;
use serde::{Deserialize, Serialize};
use super::{message::MessageContent, room::message::Relation};
use super::{message::TextContentBlock, room::message::Relation};
/// The payload for an extensible emote message.
///
/// This is the new primary type introduced in [MSC1767] and should not be sent before the end of
/// the transition period. See the documentation of the [`message`] module for more information.
/// This is the new primary type introduced in [MSC3954] and should only be sent in rooms with a
/// version that supports it. See the documentation of the [`message`] module for more information.
///
/// [MSC1767]: https://github.com/matrix-org/matrix-spec-proposals/pull/1767
/// To construct an `EmoteEventContent` with a custom [`TextContentBlock`], convert it with
/// `EmoteEventContent::from()` / `.into()`.
///
/// [MSC3954]: https://github.com/matrix-org/matrix-spec-proposals/pull/3954
/// [`message`]: super::message
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[ruma_event(type = "m.emote", kind = MessageLike, without_relation)]
#[ruma_event(type = "org.matrix.msc1767.emote", kind = MessageLike, without_relation)]
pub struct EmoteEventContent {
/// The message's text content.
#[serde(flatten)]
pub message: MessageContent,
#[serde(rename = "org.matrix.msc1767.text")]
pub text: TextContentBlock,
/// Whether this message is automated.
#[cfg(feature = "unstable-msc3955")]
#[serde(
default,
skip_serializing_if = "crate::serde::is_default",
rename = "org.matrix.msc1767.automated"
)]
pub automated: bool,
/// Information about related messages.
#[serde(
@ -34,20 +46,46 @@ pub struct EmoteEventContent {
impl EmoteEventContent {
/// A convenience constructor to create a plain text emote.
pub fn plain(body: impl Into<String>) -> Self {
Self { message: MessageContent::plain(body), relates_to: None }
Self {
text: TextContentBlock::plain(body),
#[cfg(feature = "unstable-msc3955")]
automated: false,
relates_to: None,
}
}
/// A convenience constructor to create an HTML emote.
pub fn html(body: impl Into<String>, html_body: impl Into<String>) -> Self {
Self { message: MessageContent::html(body, html_body), relates_to: None }
Self {
text: TextContentBlock::html(body, html_body),
#[cfg(feature = "unstable-msc3955")]
automated: false,
relates_to: None,
}
}
/// A convenience constructor to create a Markdown emote.
/// A convenience constructor to create an emote from Markdown.
///
/// Returns an HTML emote if some Markdown formatting was detected, otherwise returns a plain
/// text emote.
/// The content includes an HTML message if some Markdown formatting was detected, otherwise
/// only a plain text message is included.
#[cfg(feature = "markdown")]
pub fn markdown(body: impl AsRef<str> + Into<String>) -> Self {
Self { message: MessageContent::markdown(body), relates_to: None }
Self {
text: TextContentBlock::markdown(body),
#[cfg(feature = "unstable-msc3955")]
automated: false,
relates_to: None,
}
}
}
impl From<TextContentBlock> for EmoteEventContent {
fn from(text: TextContentBlock) -> Self {
Self {
text,
#[cfg(feature = "unstable-msc3955")]
automated: false,
relates_to: None,
}
}
}

View File

@ -0,0 +1,60 @@
//! Types for extensible encrypted events ([MSC3956]).
//!
//! [MSC3956]: https://github.com/matrix-org/matrix-spec-proposals/pull/3956
use ruma_macros::EventContent;
use serde::{Deserialize, Serialize};
use super::room::encrypted::{EncryptedEventScheme, Relation};
/// The payload for an extensible encrypted message.
///
/// This is the new primary type introduced in [MSC3956] and should only be sent in rooms with a
/// version that supports it. See the documentation of the [`message`] module for more information.
///
/// [MSC3956]: https://github.com/matrix-org/matrix-spec-proposals/pull/3956
/// [`message`]: super::message
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[ruma_event(type = "org.matrix.msc1767.encrypted", kind = MessageLike)]
pub struct EncryptedEventContent {
/// The encrypted content.
#[serde(rename = "org.matrix.msc1767.encrypted")]
pub encrypted: EncryptedContentBlock,
/// Information about related events.
#[serde(
flatten,
skip_serializing_if = "Option::is_none",
deserialize_with = "super::room::encrypted::relation_serde::deserialize_relation"
)]
pub relates_to: Option<Relation>,
}
impl EncryptedEventContent {
/// Creates a new `EncryptedEventContent` with the given scheme and relation.
pub fn new(scheme: EncryptedEventScheme, relates_to: Option<Relation>) -> Self {
Self { encrypted: scheme.into(), relates_to }
}
}
impl From<EncryptedEventScheme> for EncryptedEventContent {
fn from(scheme: EncryptedEventScheme) -> Self {
Self { encrypted: scheme.into(), relates_to: None }
}
}
/// A block for encrypted content.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct EncryptedContentBlock {
/// Algorithm-specific fields.
#[serde(flatten)]
pub scheme: EncryptedEventScheme,
}
impl From<EncryptedEventScheme> for EncryptedContentBlock {
fn from(scheme: EncryptedEventScheme) -> Self {
Self { scheme }
}
}

View File

@ -45,8 +45,12 @@ event_enum! {
"m.call.reject" => super::call::reject,
#[cfg(feature = "unstable-msc2746")]
"m.call.select_answer" => super::call::select_answer,
#[cfg(feature = "unstable-msc1767")]
"m.emote" => super::emote,
#[cfg(feature = "unstable-msc3954")]
#[ruma_enum(alias = "m.emote")]
"org.matrix.msc1767.emote" => super::emote,
#[cfg(feature = "unstable-msc3956")]
#[ruma_enum(alias = "m.encrypted")]
"org.matrix.msc1767.encrypted" => super::encrypted,
#[cfg(feature = "unstable-msc3551")]
"m.file" => super::file,
#[cfg(feature = "unstable-msc3552")]
@ -61,9 +65,8 @@ event_enum! {
#[cfg(feature = "unstable-msc3488")]
"m.location" => super::location,
#[cfg(feature = "unstable-msc1767")]
"m.message" => super::message,
#[cfg(feature = "unstable-msc1767")]
"m.notice" => super::notice,
#[ruma_enum(alias = "m.message")]
"org.matrix.msc1767.message" => super::message,
#[cfg(feature = "unstable-msc3381")]
#[ruma_enum(alias = "m.poll.start")]
"org.matrix.msc3381.poll.start" => super::poll::start,
@ -317,10 +320,10 @@ impl AnyMessageLikeEventContent {
Self::RoomMessage(ev) => ev.relates_to.clone().map(Into::into),
#[cfg(feature = "unstable-msc1767")]
Self::Message(ev) => ev.relates_to.clone().map(Into::into),
#[cfg(feature = "unstable-msc1767")]
Self::Notice(ev) => ev.relates_to.clone().map(Into::into),
#[cfg(feature = "unstable-msc1767")]
#[cfg(feature = "unstable-msc3954")]
Self::Emote(ev) => ev.relates_to.clone().map(Into::into),
#[cfg(feature = "unstable-msc3956")]
Self::Encrypted(ev) => ev.relates_to.clone(),
#[cfg(feature = "unstable-msc3245")]
Self::Voice(ev) => ev.relates_to.clone().map(Into::into),
#[cfg(feature = "unstable-msc3246")]

View File

@ -9,15 +9,15 @@ use ruma_macros::EventContent;
use serde::{Deserialize, Serialize};
use super::{
message::MessageContent,
message::TextContentBlock,
room::{message::Relation, EncryptedFile, JsonWebKey},
};
use crate::{serde::Base64, OwnedMxcUri};
/// The payload for an extensible file message.
///
/// This is the new primary type introduced in [MSC3551] and should not be sent before the end of
/// the transition period. See the documentation of the [`message`] module for more information.
/// This is the new primary type introduced in [MSC3551] and should only be sent in rooms with a
/// version that supports it. See the documentation of the [`message`] module for more information.
///
/// [MSC3551]: https://github.com/matrix-org/matrix-spec-proposals/pull/3551
/// [`message`]: super::message
@ -26,8 +26,8 @@ use crate::{serde::Base64, OwnedMxcUri};
#[ruma_event(type = "m.file", kind = MessageLike, without_relation)]
pub struct FileEventContent {
/// The text representation of the message.
#[serde(flatten)]
pub message: MessageContent,
#[serde(rename = "org.matrix.msc1767.text")]
pub text: TextContentBlock,
/// The file content of the message.
#[serde(rename = "m.file")]
@ -43,55 +43,55 @@ pub struct FileEventContent {
}
impl FileEventContent {
/// Creates a new non-encrypted `FileEventContent` with the given plain text message, url and
/// file info.
/// Creates a new non-encrypted `FileEventContent` with the given fallback representation, url
/// and file info.
pub fn plain(
message: impl Into<String>,
text: TextContentBlock,
url: OwnedMxcUri,
info: Option<Box<FileContentInfo>>,
) -> Self {
Self { text, file: FileContent::plain(url, info), relates_to: None }
}
/// Creates a new non-encrypted `FileEventContent` with the given plain text fallback
/// representation, url and file info.
pub fn plain_with_text(
text: impl Into<String>,
url: OwnedMxcUri,
info: Option<Box<FileContentInfo>>,
) -> Self {
Self {
message: MessageContent::plain(message),
text: TextContentBlock::plain(text),
file: FileContent::plain(url, info),
relates_to: None,
}
}
/// Creates a new non-encrypted `FileEventContent` with the given message, url and
/// file info.
pub fn plain_message(
message: MessageContent,
url: OwnedMxcUri,
info: Option<Box<FileContentInfo>>,
) -> Self {
Self { message, file: FileContent::plain(url, info), relates_to: None }
}
/// Creates a new encrypted `FileEventContent` with the given plain text message, url,
/// Creates a new encrypted `FileEventContent` with the given fallback representation, url,
/// encryption info and file info.
pub fn encrypted(
message: impl Into<String>,
text: TextContentBlock,
url: OwnedMxcUri,
encryption_info: EncryptedContent,
info: Option<Box<FileContentInfo>>,
) -> Self {
Self { text, file: FileContent::encrypted(url, encryption_info, info), relates_to: None }
}
/// Creates a new encrypted `FileEventContent` with the given plain text fallback
/// representation, url, encryption info and file info.
pub fn encrypted_with_text(
text: impl Into<String>,
url: OwnedMxcUri,
encryption_info: EncryptedContent,
info: Option<Box<FileContentInfo>>,
) -> Self {
Self {
message: MessageContent::plain(message),
text: TextContentBlock::plain(text),
file: FileContent::encrypted(url, encryption_info, info),
relates_to: None,
}
}
/// Creates a new encrypted `FileEventContent` with the given message, url,
/// encryption info and file info.
pub fn encrypted_message(
message: MessageContent,
url: OwnedMxcUri,
encryption_info: EncryptedContent,
info: Option<Box<FileContentInfo>>,
) -> Self {
Self { message, file: FileContent::encrypted(url, encryption_info, info), relates_to: None }
}
}
/// File content.

View File

@ -8,15 +8,15 @@ use serde::{Deserialize, Serialize};
use super::{
file::{EncryptedContent, FileContent},
message::MessageContent,
message::TextContentBlock,
room::message::Relation,
};
use crate::OwnedMxcUri;
/// The payload for an extensible image message.
///
/// This is the new primary type introduced in [MSC3552] and should not be sent before the end of
/// the transition period. See the documentation of the [`message`] module for more information.
/// This is the new primary type introduced in [MSC3552] and should only be sent in rooms with a
/// version that supports it. See the documentation of the [`message`] module for more information.
///
/// [MSC3552]: https://github.com/matrix-org/matrix-spec-proposals/pull/3552
/// [`message`]: super::message
@ -25,8 +25,8 @@ use crate::OwnedMxcUri;
#[ruma_event(type = "m.image", kind = MessageLike, without_relation)]
pub struct ImageEventContent {
/// The text representation of the message.
#[serde(flatten)]
pub message: MessageContent,
#[serde(rename = "org.matrix.msc1767.text")]
pub text: TextContentBlock,
/// The file content of the message.
#[serde(rename = "m.file")]
@ -41,13 +41,8 @@ pub struct ImageEventContent {
pub thumbnail: Vec<ThumbnailContent>,
/// The captions of the message.
#[serde(
rename = "m.caption",
with = "super::message::content_serde::as_vec",
default,
skip_serializing_if = "Option::is_none"
)]
pub caption: Option<MessageContent>,
#[serde(rename = "m.caption", default, skip_serializing_if = "TextContentBlock::is_empty")]
pub caption: TextContentBlock,
/// Information about related messages.
#[serde(
@ -59,10 +54,11 @@ pub struct ImageEventContent {
}
impl ImageEventContent {
/// Creates a new `ImageEventContent` with the given plain text message and file.
pub fn plain(message: impl Into<String>, file: FileContent) -> Self {
/// Creates a new `ImageEventContent` with the given fallback representation and
/// file.
pub fn new(text: TextContentBlock, file: FileContent) -> Self {
Self {
message: MessageContent::plain(message),
text,
file,
image: Default::default(),
thumbnail: Default::default(),
@ -71,10 +67,11 @@ impl ImageEventContent {
}
}
/// Creates a new non-encrypted `ImageEventContent` with the given message and file.
pub fn with_message(message: MessageContent, file: FileContent) -> Self {
/// Creates a new `ImageEventContent` with the given plain text fallback representation and
/// file.
pub fn plain(text_fallback: impl Into<String>, file: FileContent) -> Self {
Self {
message,
text: TextContentBlock::plain(text_fallback),
file,
image: Default::default(),
thumbnail: Default::default(),

View File

@ -8,13 +8,14 @@ use serde::{Deserialize, Serialize};
mod zoomlevel_serde;
use super::{message::MessageContent, room::message::Relation};
use super::{message::TextContentBlock, room::message::Relation};
use crate::{MilliSecondsSinceUnixEpoch, PrivOwnedStr};
/// The payload for an extensible location message.
///
/// This is the new primary type introduced in [MSC3488] and should not be sent before the end of
/// the transition period. See the documentation of the [`message`] module for more information.
/// This is the new primary type introduced in [MSC3488] and can be sent in rooms with a version
/// that doesn't support extensible events. See the documentation of the [`message`] module for more
/// information.
///
/// [MSC3488]: https://github.com/matrix-org/matrix-spec-proposals/pull/3488
/// [`message`]: super::message
@ -23,8 +24,8 @@ use crate::{MilliSecondsSinceUnixEpoch, PrivOwnedStr};
#[ruma_event(type = "m.location", kind = MessageLike, without_relation)]
pub struct LocationEventContent {
/// The text representation of the message.
#[serde(flatten)]
pub message: MessageContent,
#[serde(rename = "org.matrix.msc1767.text")]
pub text: TextContentBlock,
/// The location info of the message.
#[serde(rename = "m.location")]
@ -48,21 +49,22 @@ pub struct LocationEventContent {
}
impl LocationEventContent {
/// Creates a new `LocationEventContent` with the given plain text representation and location.
pub fn plain(message: impl Into<String>, location: LocationContent) -> Self {
/// Creates a new `LocationEventContent` with the given fallback representation and location.
pub fn new(text: TextContentBlock, location: LocationContent) -> Self {
Self { text, location, asset: Default::default(), ts: None, relates_to: None }
}
/// Creates a new `LocationEventContent` with the given plain text fallback representation and
/// location.
pub fn plain(text: impl Into<String>, location: LocationContent) -> Self {
Self {
message: MessageContent::plain(message),
text: TextContentBlock::plain(text),
location,
asset: Default::default(),
ts: None,
relates_to: None,
}
}
/// Creates a new `LocationEventContent` with the given text representation and location.
pub fn with_message(message: MessageContent, location: LocationContent) -> Self {
Self { message, location, asset: Default::default(), ts: None, relates_to: None }
}
}
/// Location content.

View File

@ -2,74 +2,116 @@
//!
//! # Extensible events
//!
//! MSCs [1767] (Text, Emote and Notice), [3551] (Files), [3552] (Images and Stickers), [3553]
//! (Videos), [3246] (Audio), and [3488] (Location) introduce new primary types called extensible
//! events. These types are meant to replace the `m.room.message` primary type and its `msgtype`s.
//! Other MSCs introduce new types with an `m.room.message` fallback, like [MSC3245] (Voice
//! Messages), and types that only have an extensible events format, like [MSC3381] (Polls).
//! [MSC1767] defines a new structure for events that is made of two parts: a type and zero or more
//! reusable content blocks.
//!
//! # Transition Period
//! This allows to construct new event types from a list of known content blocks that allows in turn
//! clients to be able to render unknown event types by using the known content blocks as a
//! fallback. When a new type is defined, all the content blocks it can or must contain are defined
//! too.
//!
//! MSC1767 defines a transition period that will start after the extensible events are released in
//! a Matrix version. It should last approximately one year, but the end of that period will be
//! formalized in a new Matrix version.
//! There are also some content blocks called "mixins" that can apply to any event when they are
//! defined.
//!
//! The new primary types should not be sent over the Matrix network before the end of the
//! transition period. Instead, transitional `m.room.message` events should be sent. These
//! transitional events include the content of the now legacy `m.room.message` event and the content
//! of the new extensible event types in a single event.
//! # MSCs
//!
//! # How to use them
//! This is a list of MSCs defining the extensible events and deprecating the corresponding legacy
//! types. Note that "primary type" means the `type` field at the root of the event and "message
//! type" means the `msgtype` field in the content of the `m.room.message` primary type.
//!
//! - [MSC1767]: Text messages, where the `m.message` primary type replaces the `m.text` message
//! type.
//! - [MSC3954]: Emotes, where the `m.emote` primary type replaces the `m.emote` message type.
//! - [MSC3955]: Automated events, where the `m.automated` mixin replaces the `m.notice` message
//! type.
//! - [MSC3956]: Encrypted events, where the `m.encrypted` primary type replaces the
//! `m.room.encrypted` primary type.
//! - [MSC3551]: Files, where the `m.file` primary type replaces the `m.file` message type.
//! - [MSC3552]: Images and Stickers, where the `m.image` primary type replaces the `m.image`
//! message type and the `m.sticker` primary type.
//! - [MSC3553]: Videos, where the `m.video` primary type replaces the `m.video` message type.
//! - [MSC3927]: Audio, where the `m.audio` primary type replaces the `m.audio` message type.
//! - [MSC3488]: Location, where the `m.location` primary type replaces the `m.location` message
//! type.
//!
//! There are also the following MSCs that introduce new features with extensible events:
//!
//! - [MSC3245]: Voice Messages.
//! - [MSC3246]: Audio Waveform.
//! - [MSC3381]: Polls.
//!
//! # How to use them in Matrix
//!
//! The extensible events types are meant to be used separately than the legacy types. As such,
//! their use is reserved for room versions that support it.
//!
//! Currently no stable room version supports extensible events so they can only be sent with
//! unstable room versions that support them.
//!
//! An exception is made for some new extensible events types that don't have a legacy type. They
//! can be used with stable room versions without support for extensible types, but they might be
//! ignored by clients that have no support for extensible events. The types that support this must
//! advertise it in their MSC.
//!
//! Note that if a room version supports extensible events, it doesn't support the legacy types
//! anymore and those should be ignored. There is not yet a definition of the deprecated legacy
//! types in extensible events rooms.
//!
//! # How to use them in Ruma
//!
//! First, you can enable the `unstable-extensible-events` feature from the `ruma` crate, that
//! will enable all the MSCs for the extensible events that correspond to the legacy `msgtype`s
//! (1767, 3246, 3488, 3551, 3552, 3553). It is also possible to enable only the MSCs you want with
//! the `unstable-mscXXXX` features (where `XXXX` is the number of the MSC).
//! will enable all the MSCs for the extensible events that correspond to the legacy types. It
//! is also possible to enable only the MSCs you want with the `unstable-mscXXXX` features (where
//! `XXXX` is the number of the MSC). When enabling an MSC, all MSC dependencies are enabled at the
//! same time to avoid issues.
//!
//! The recommended way to send transitional extensible events while they are unstable and during
//! the transition period is to use the constructors and helper methods of
//! [`RoomMessageEventContent`]. The data will be duplicated in both the legacy and extensible
//! events fields as needed.
//! Currently the extensible events use the unstable prefixes as defined in the corresponding MSCs.
//!
//! [MSC1767]: https://github.com/matrix-org/matrix-spec-proposals/pull/1767
//! [1767]: https://github.com/matrix-org/matrix-spec-proposals/pull/1767
//! [3551]: https://github.com/matrix-org/matrix-spec-proposals/pull/3551
//! [3552]: https://github.com/matrix-org/matrix-spec-proposals/pull/3552
//! [3553]: https://github.com/matrix-org/matrix-spec-proposals/pull/3553
//! [3246]: https://github.com/matrix-org/matrix-spec-proposals/pull/3246
//! [3488]: https://github.com/matrix-org/matrix-spec-proposals/pull/3488
//! [MSC3954]: https://github.com/matrix-org/matrix-spec-proposals/pull/3954
//! [MSC3955]: https://github.com/matrix-org/matrix-spec-proposals/pull/3955
//! [MSC3956]: https://github.com/matrix-org/matrix-spec-proposals/pull/3956
//! [MSC3551]: https://github.com/matrix-org/matrix-spec-proposals/pull/3551
//! [MSC3552]: https://github.com/matrix-org/matrix-spec-proposals/pull/3552
//! [MSC3553]: https://github.com/matrix-org/matrix-spec-proposals/pull/3553
//! [MSC3927]: https://github.com/matrix-org/matrix-spec-proposals/pull/3927
//! [MSC3488]: https://github.com/matrix-org/matrix-spec-proposals/pull/3488
//! [MSC3245]: https://github.com/matrix-org/matrix-spec-proposals/pull/3245
//! [MSC3246]: https://github.com/matrix-org/matrix-spec-proposals/pull/3246
//! [MSC3381]: https://github.com/matrix-org/matrix-spec-proposals/pull/3381
//! [`RoomMessageEventContent`]: super::room::message::RoomMessageEventContent
use std::ops::Deref;
use ruma_macros::EventContent;
use serde::{Deserialize, Serialize};
use thiserror::Error;
pub(crate) mod content_serde;
use content_serde::MessageContentSerDeHelper;
use super::room::message::Relation;
/// The payload for an extensible text message.
///
/// This is the new primary type introduced in [MSC1767] and should not be sent before the end of
/// the transition period. See the documentation of the [`message`] module for more information.
/// This is the new primary type introduced in [MSC1767] and should only be sent in rooms with a
/// version that supports it. See the documentation of the [`message`] module for more information.
///
/// To construct a `MessageEventContent` with a custom [`MessageContent`], convert it with
/// To construct a `MessageEventContent` with a custom [`TextContentBlock`], convert it with
/// `MessageEventContent::from()` / `.into()`.
///
/// [MSC1767]: https://github.com/matrix-org/matrix-spec-proposals/pull/1767
/// [`message`]: super::message
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[ruma_event(type = "m.message", kind = MessageLike, without_relation)]
#[ruma_event(type = "org.matrix.msc1767.message", kind = MessageLike, without_relation)]
pub struct MessageEventContent {
/// The message's text content.
#[serde(flatten)]
pub message: MessageContent,
#[serde(rename = "org.matrix.msc1767.text")]
pub text: TextContentBlock,
/// Whether this message is automated.
#[cfg(feature = "unstable-msc3955")]
#[serde(
default,
skip_serializing_if = "crate::serde::is_default",
rename = "org.matrix.msc1767.automated"
)]
pub automated: bool,
/// Information about related messages.
#[serde(
@ -83,77 +125,89 @@ pub struct MessageEventContent {
impl MessageEventContent {
/// A convenience constructor to create a plain text message.
pub fn plain(body: impl Into<String>) -> Self {
Self { message: MessageContent::plain(body), relates_to: None }
}
/// A convenience constructor to create an HTML message.
pub fn html(body: impl Into<String>, html_body: impl Into<String>) -> Self {
Self { message: MessageContent::html(body, html_body), relates_to: None }
}
/// A convenience constructor to create a Markdown message.
///
/// Returns an HTML message if some Markdown formatting was detected, otherwise returns a plain
/// text message.
#[cfg(feature = "markdown")]
pub fn markdown(body: impl AsRef<str> + Into<String>) -> Self {
Self { message: MessageContent::markdown(body), relates_to: None }
}
}
impl From<MessageContent> for MessageEventContent {
fn from(message: MessageContent) -> Self {
Self { message, relates_to: None }
}
}
/// Text message content.
///
/// A `MessageContent` must contain at least one message to be used as a fallback text
/// representation.
///
/// To construct a `MessageContent` with custom MIME types, construct a `Vec<Text>` first and use
/// its `.try_from()` / `.try_into()` implementation that will only fail if the `Vec` is empty.
#[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))
Self {
text: TextContentBlock::plain(body),
#[cfg(feature = "unstable-msc3955")]
automated: false,
relates_to: None,
}
}
/// A convenience constructor to create a plain text message.
pub fn plain(body: impl Into<String>) -> Self {
Self(vec![Text::plain(body)])
}
/// A convenience constructor to create an HTML message.
pub fn html(body: impl Into<String>, html_body: impl Into<String>) -> Self {
Self(vec![Text::html(html_body), Text::plain(body)])
Self {
text: TextContentBlock::html(body, html_body),
#[cfg(feature = "unstable-msc3955")]
automated: false,
relates_to: None,
}
}
/// A convenience constructor to create a Markdown message.
/// A convenience constructor to create a message from Markdown.
///
/// Returns an HTML message if some Markdown formatting was detected, otherwise returns a plain
/// text message.
/// The content includes an HTML message if some Markdown formatting was detected, otherwise
/// only a plain text message is included.
#[cfg(feature = "markdown")]
pub fn markdown(body: impl AsRef<str> + Into<String>) -> Self {
Self {
text: TextContentBlock::markdown(body),
#[cfg(feature = "unstable-msc3955")]
automated: false,
relates_to: None,
}
}
}
impl From<TextContentBlock> for MessageEventContent {
fn from(text: TextContentBlock) -> Self {
Self {
text,
#[cfg(feature = "unstable-msc3955")]
automated: false,
relates_to: None,
}
}
}
/// A block for text content with optional markup.
///
/// This is an array of [`TextRepresentation`].
///
/// To construct a `TextContentBlock` with custom MIME types, construct a `Vec<TextRepresentation>`
/// first and use its `::from()` / `.into()` implementation.
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct TextContentBlock(Vec<TextRepresentation>);
impl TextContentBlock {
/// A convenience constructor to create a plain text message.
pub fn plain(body: impl Into<String>) -> Self {
Self(vec![TextRepresentation::plain(body)])
}
/// A convenience constructor to create an HTML message with a plain text fallback.
pub fn html(body: impl Into<String>, html_body: impl Into<String>) -> Self {
Self(vec![TextRepresentation::html(html_body), TextRepresentation::plain(body)])
}
/// A convenience constructor to create a message from Markdown.
///
/// The content includes an HTML message if some Markdown formatting was detected, otherwise
/// only a plain text message is included.
#[cfg(feature = "markdown")]
pub fn markdown(body: impl AsRef<str> + Into<String>) -> Self {
let mut message = Vec::with_capacity(2);
if let Some(html_body) = Text::markdown(&body) {
if let Some(html_body) = TextRepresentation::markdown(&body) {
message.push(html_body);
}
message.push(Text::plain(body));
message.push(TextRepresentation::plain(body));
Self(message)
}
/// Whether this content block is empty.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Get the plain text representation of this message.
pub fn find_plain(&self) -> Option<&str> {
self.iter()
@ -169,38 +223,39 @@ impl MessageContent {
}
}
/// 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)
impl From<Vec<TextRepresentation>> for TextContentBlock {
fn from(representations: Vec<TextRepresentation>) -> Self {
Self(representations)
}
}
impl Deref for MessageContent {
type Target = [Text];
impl FromIterator<TextRepresentation> for TextContentBlock {
fn from_iter<T: IntoIterator<Item = TextRepresentation>>(iter: T) -> Self {
Self(iter.into_iter().collect())
}
}
impl Deref for TextContentBlock {
type Target = [TextRepresentation];
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// Text message content.
/// Text content with optional markup.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct Text {
pub struct TextRepresentation {
/// The MIME type of the `body`.
///
/// This must follow the format defined in [RFC6838].
///
/// [RFC6838]: https://datatracker.ietf.org/doc/html/rfc6838
#[serde(default = "Text::default_mimetype")]
#[serde(
default = "TextRepresentation::default_mimetype",
skip_serializing_if = "TextRepresentation::is_default_mimetype"
)]
pub mimetype: String,
/// The text content.
@ -216,8 +271,8 @@ pub struct Text {
pub lang: Option<String>,
}
impl Text {
/// Creates a new `Text` with the given MIME type and body.
impl TextRepresentation {
/// Creates a new `TextRepresentation` with the given MIME type and body.
pub fn new(mimetype: impl Into<String>, body: impl Into<String>) -> Self {
Self {
mimetype: mimetype.into(),
@ -250,13 +305,8 @@ impl Text {
fn default_mimetype() -> String {
"text/plain".to_owned()
}
}
/// The error type returned when a conversion to an extensible event type fails.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum TryFromExtensibleError {
/// A field is missing.
#[error("missing field `{0}`")]
MissingField(String),
fn is_default_mimetype(mime: &str) -> bool {
mime == "text/plain"
}
}

View File

@ -1,129 +0,0 @@
//! `Serialize` and `Deserialize` implementations for extensible events (MSC1767).
use serde::{ser::SerializeStruct, Deserialize, Serialize};
use super::{MessageContent, Text, TryFromExtensibleError};
#[derive(Debug, Default, Deserialize)]
pub(crate) struct MessageContentSerDeHelper {
/// Plain text short form, stable name.
#[serde(rename = "m.text")]
text_stable: Option<String>,
/// Plain text short form, unstable name.
#[serde(rename = "org.matrix.msc1767.text")]
text_unstable: Option<String>,
/// HTML short form, stable name.
#[serde(rename = "m.html")]
html_stable: Option<String>,
/// HTML short form, unstable name.
#[serde(rename = "org.matrix.msc1767.html")]
html_unstable: Option<String>,
/// Long form, stable name.
#[serde(rename = "m.message")]
message_stable: Option<Vec<Text>>,
/// Long form, unstable name.
#[serde(rename = "org.matrix.msc1767.message")]
message_unstable: Option<Vec<Text>>,
}
impl TryFrom<MessageContentSerDeHelper> for MessageContent {
type Error = TryFromExtensibleError;
fn try_from(helper: MessageContentSerDeHelper) -> Result<Self, Self::Error> {
let MessageContentSerDeHelper {
text_stable,
text_unstable,
html_stable,
html_unstable,
message_stable,
message_unstable,
} = helper;
if let Some(message) = message_stable.or(message_unstable) {
Ok(Self(message))
} else {
let message: Vec<_> = html_stable
.or(html_unstable)
.map(Text::html)
.into_iter()
.chain(text_stable.or(text_unstable).map(Text::plain))
.collect();
if !message.is_empty() {
Ok(Self(message))
} else {
Err(TryFromExtensibleError::MissingField("m.message, m.text or m.html".to_owned()))
}
}
}
}
impl Serialize for MessageContent {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
#[cfg(feature = "unstable-msc3554")]
let has_shortcut = |message: &Text| {
matches!(&*message.mimetype, "text/plain" | "text/html") && message.lang.is_none()
};
#[cfg(not(feature = "unstable-msc3554"))]
let has_shortcut =
|message: &Text| matches!(&*message.mimetype, "text/plain" | "text/html");
if self.iter().all(has_shortcut) {
let mut st = serializer.serialize_struct("MessageContent", self.len())?;
for message in self.iter() {
if message.mimetype == "text/plain" {
st.serialize_field("org.matrix.msc1767.text", &message.body)?;
} else if message.mimetype == "text/html" {
st.serialize_field("org.matrix.msc1767.html", &message.body)?;
}
}
st.end()
} else {
let mut st = serializer.serialize_struct("MessageContent", 1)?;
st.serialize_field("org.matrix.msc1767.message", &self.0)?;
st.end()
}
}
}
pub(crate) mod as_vec {
use serde::{de, ser::SerializeSeq, Deserialize, Deserializer, Serializer};
use crate::events::message::{MessageContent, Text};
/// Serializes a `Option<MessageContent>` as a `Vec<Text>`.
pub fn serialize<S>(content: &Option<MessageContent>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if let Some(content) = content {
let mut seq = serializer.serialize_seq(Some(content.len()))?;
for e in content.iter() {
seq.serialize_element(e)?;
}
seq.end()
} else {
serializer.serialize_seq(Some(0))?.end()
}
}
/// Deserializes a `Vec<Text>` to an `Option<MessageContent>`.
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<MessageContent>, D::Error>
where
D: Deserializer<'de>,
{
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,53 +0,0 @@
//! Types for extensible notice message events ([MSC1767]).
//!
//! [MSC1767]: https://github.com/matrix-org/matrix-spec-proposals/pull/1767
use ruma_macros::EventContent;
use serde::{Deserialize, Serialize};
use super::{message::MessageContent, room::message::Relation};
/// The payload for an extensible notice message.
///
/// This is the new primary type introduced in [MSC1767] and should not be sent before the end of
/// the transition period. See the documentation of the [`message`] module for more information.
///
/// [MSC1767]: https://github.com/matrix-org/matrix-spec-proposals/pull/1767
/// [`message`]: super::message
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[ruma_event(type = "m.notice", kind = MessageLike, without_relation)]
pub struct NoticeEventContent {
/// The message's text content.
#[serde(flatten)]
pub message: MessageContent,
/// Information about related messages.
#[serde(
flatten,
skip_serializing_if = "Option::is_none",
deserialize_with = "crate::events::room::message::relation_serde::deserialize_relation"
)]
pub relates_to: Option<Relation<NoticeEventContentWithoutRelation>>,
}
impl NoticeEventContent {
/// A convenience constructor to create a plain text notice.
pub fn plain(body: impl Into<String>) -> Self {
Self { message: MessageContent::plain(body), relates_to: None }
}
/// A convenience constructor to create an HTML notice.
pub fn html(body: impl Into<String>, html_body: impl Into<String>) -> Self {
Self { message: MessageContent::html(body, html_body), relates_to: None }
}
/// A convenience constructor to create a Markdown notice.
///
/// Returns an HTML notice if some Markdown formatting was detected, otherwise returns a plain
/// text notice.
#[cfg(feature = "markdown")]
pub fn markdown(body: impl AsRef<str> + Into<String>) -> Self {
Self { message: MessageContent::markdown(body), relates_to: None }
}
}

View File

@ -8,7 +8,7 @@ mod poll_answers_serde;
use poll_answers_serde::PollAnswersDeHelper;
use crate::{events::message::MessageContent, serde::StringEnum, PrivOwnedStr};
use crate::{events::message::TextContentBlock, serde::StringEnum, PrivOwnedStr};
/// The payload for a poll start event.
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
@ -20,14 +20,18 @@ pub struct PollStartEventContent {
pub poll_start: PollStartContent,
/// Optional fallback text representation of the message, for clients that don't support polls.
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub message: Option<MessageContent>,
#[serde(
rename = "org.matrix.msc1767.text",
default,
skip_serializing_if = "TextContentBlock::is_empty"
)]
pub text: TextContentBlock,
}
impl PollStartEventContent {
/// Creates a new `PollStartEventContent` with the given poll start content.
pub fn new(poll_start: PollStartContent) -> Self {
Self { poll_start, message: None }
Self { poll_start, text: Default::default() }
}
}
@ -36,7 +40,7 @@ impl PollStartEventContent {
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct PollStartContent {
/// The question of the poll.
pub question: MessageContent,
pub question: PollQuestion,
/// The kind of the poll.
#[serde(default)]
@ -59,8 +63,13 @@ pub struct PollStartContent {
impl PollStartContent {
/// Creates a new `PollStartContent` with the given question, kind, and answers.
pub fn new(question: MessageContent, kind: PollKind, answers: PollAnswers) -> Self {
Self { question, kind, max_selections: Self::default_max_selections(), answers }
pub fn new(question: TextContentBlock, kind: PollKind, answers: PollAnswers) -> Self {
Self {
question: question.into(),
kind,
max_selections: Self::default_max_selections(),
answers,
}
}
fn default_max_selections() -> UInt {
@ -72,6 +81,21 @@ impl PollStartContent {
}
}
/// The question of a poll.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct PollQuestion {
/// The text representation of the question.
#[serde(rename = "org.matrix.msc1767.text")]
pub text: TextContentBlock,
}
impl From<TextContentBlock> for PollQuestion {
fn from(text: TextContentBlock) -> Self {
Self { text }
}
}
/// The kind of poll.
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
#[derive(Clone, Default, PartialEq, Eq, StringEnum)]
@ -156,13 +180,13 @@ pub struct PollAnswer {
pub id: String,
/// The text representation of the answer.
#[serde(flatten)]
pub answer: MessageContent,
#[serde(rename = "org.matrix.msc1767.text")]
pub text: TextContentBlock,
}
impl PollAnswer {
/// Creates a new `PollAnswer` with the given id and text representation.
pub fn new(id: String, answer: MessageContent) -> Self {
Self { id, answer }
pub fn new(id: String, text: TextContentBlock) -> Self {
Self { id, text }
}
}

View File

@ -16,7 +16,7 @@ use crate::{
OwnedDeviceId, OwnedEventId,
};
mod relation_serde;
pub(crate) mod relation_serde;
/// The content of an `m.room.encrypted` event.
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]

View File

@ -5,7 +5,7 @@ use super::Annotation;
use super::{InReplyTo, Reference, Relation, Replacement, Thread};
use crate::OwnedEventId;
pub(super) fn deserialize_relation<'de, D>(deserializer: D) -> Result<Option<Relation>, D::Error>
pub(crate) fn deserialize_relation<'de, D>(deserializer: D) -> Result<Option<Relation>, D::Error>
where
D: Deserializer<'de>,
{

View File

@ -9,13 +9,13 @@ use ruma_macros::EventContent;
use serde::{Deserialize, Serialize};
use super::{
file::FileContent, image::ThumbnailContent, message::MessageContent, room::message::Relation,
file::FileContent, image::ThumbnailContent, message::TextContentBlock, room::message::Relation,
};
/// The payload for an extensible video message.
///
/// This is the new primary type introduced in [MSC3553] and should not be sent before the end of
/// the transition period. See the documentation of the [`message`] module for more information.
/// This is the new primary type introduced in [MSC3553] and should only be sent in rooms with a
/// version that supports it. See the documentation of the [`message`] module for more information.
///
/// [MSC3553]: https://github.com/matrix-org/matrix-spec-proposals/pull/3553
/// [`message`]: super::message
@ -24,8 +24,8 @@ use super::{
#[ruma_event(type = "m.video", kind = MessageLike, without_relation)]
pub struct VideoEventContent {
/// The text representation of the message.
#[serde(flatten)]
pub message: MessageContent,
#[serde(rename = "org.matrix.msc1767.text")]
pub text: TextContentBlock,
/// The file content of the message.
#[serde(rename = "m.file")]
@ -40,13 +40,8 @@ pub struct VideoEventContent {
pub thumbnail: Vec<ThumbnailContent>,
/// The captions of the message.
#[serde(
rename = "m.caption",
with = "super::message::content_serde::as_vec",
default,
skip_serializing_if = "Option::is_none"
)]
pub caption: Option<MessageContent>,
#[serde(rename = "m.caption", default, skip_serializing_if = "TextContentBlock::is_empty")]
pub caption: TextContentBlock,
/// Information about related messages.
#[serde(
@ -58,10 +53,10 @@ pub struct VideoEventContent {
}
impl VideoEventContent {
/// Creates a new `VideoEventContent` with the given plain text message and file.
pub fn plain(message: impl Into<String>, file: FileContent) -> Self {
/// Creates a new `VideoEventContent` with the given fallback representation and file.
pub fn new(text: TextContentBlock, file: FileContent) -> Self {
Self {
message: MessageContent::plain(message),
text,
file,
video: Default::default(),
thumbnail: Default::default(),
@ -70,10 +65,11 @@ impl VideoEventContent {
}
}
/// Creates a new `VideoEventContent` with the given message and file.
pub fn with_message(message: MessageContent, file: FileContent) -> Self {
/// Creates a new `VideoEventContent` with the given plain text fallback representation and
/// file.
pub fn plain(text: impl Into<String>, file: FileContent) -> Self {
Self {
message,
text: TextContentBlock::plain(text),
file,
video: Default::default(),
thumbnail: Default::default(),

View File

@ -6,13 +6,14 @@ use ruma_macros::EventContent;
use serde::{Deserialize, Serialize};
use super::{
audio::AudioContent, file::FileContent, message::MessageContent, room::message::Relation,
audio::AudioContent, file::FileContent, message::TextContentBlock, room::message::Relation,
};
/// The payload for an extensible voice message.
///
/// This is the new primary type introduced in [MSC3245] and should not be sent before the end of
/// the transition period. See the documentation of the [`message`] module for more information.
/// This is the new primary type introduced in [MSC3245] and can be sent in rooms with a version
/// that doesn't support extensible events. See the documentation of the [`message`] module for more
/// information.
///
/// [MSC3245]: https://github.com/matrix-org/matrix-spec-proposals/pull/3245
/// [`message`]: super::message
@ -21,8 +22,8 @@ use super::{
#[ruma_event(type = "m.voice", kind = MessageLike, without_relation)]
pub struct VoiceEventContent {
/// The text representation of the message.
#[serde(flatten)]
pub message: MessageContent,
#[serde(rename = "org.matrix.msc1767.text")]
pub text: TextContentBlock,
/// The file content of the message.
#[serde(rename = "m.file")]
@ -46,21 +47,16 @@ pub struct VoiceEventContent {
}
impl VoiceEventContent {
/// Creates a new `VoiceEventContent` with the given plain text representation and file.
pub fn plain(message: impl Into<String>, file: FileContent) -> Self {
Self {
message: MessageContent::plain(message),
file,
audio: Default::default(),
voice: Default::default(),
relates_to: None,
}
/// Creates a new `VoiceEventContent` with the given fallback representation and file.
pub fn new(text: TextContentBlock, file: FileContent) -> Self {
Self { text, file, audio: Default::default(), voice: Default::default(), relates_to: None }
}
/// Creates a new `VoiceEventContent` with the given message and file.
pub fn with_message(message: MessageContent, file: FileContent) -> Self {
/// Creates a new `VoiceEventContent` with the given plain text fallback representation and
/// file.
pub fn plain(text: impl Into<String>, file: FileContent) -> Self {
Self {
message,
text: TextContentBlock::plain(text),
file,
audio: Default::default(),
voice: Default::default(),

View File

@ -10,7 +10,7 @@ use ruma_common::{
events::{
audio::{Amplitude, AudioContent, AudioEventContent, Waveform, WaveformError},
file::{EncryptedContentInit, FileContent, FileContentInfo},
message::MessageContent,
message::TextContentBlock,
relation::InReplyTo,
room::{message::Relation, JsonWebKeyInit},
AnyMessageLikeEvent, MessageLikeEvent,
@ -63,7 +63,9 @@ fn plain_content_serialization() {
assert_eq!(
to_json_value(&event_content).unwrap(),
json!({
"org.matrix.msc1767.text": "Upload: my_sound.ogg",
"org.matrix.msc1767.text": [
{ "body": "Upload: my_sound.ogg" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
},
@ -103,7 +105,9 @@ fn encrypted_content_serialization() {
assert_eq!(
to_json_value(&event_content).unwrap(),
json!({
"org.matrix.msc1767.text": "Upload: my_sound.ogg",
"org.matrix.msc1767.text": [
{ "body": "Upload: my_sound.ogg" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
"key": {
@ -127,8 +131,8 @@ fn encrypted_content_serialization() {
#[test]
fn event_serialization() {
let content = assign!(
AudioEventContent::with_message(
MessageContent::html(
AudioEventContent::new(
TextContentBlock::html(
"Upload: my_mix.mp3",
"Upload: <strong>my_mix.mp3</strong>",
),
@ -160,8 +164,10 @@ fn event_serialization() {
assert_eq!(
to_json_value(&content).unwrap(),
json!({
"org.matrix.msc1767.html": "Upload: <strong>my_mix.mp3</strong>",
"org.matrix.msc1767.text": "Upload: my_mix.mp3",
"org.matrix.msc1767.text": [
{ "mimetype": "text/html", "body": "Upload: <strong>my_mix.mp3</strong>" },
{ "body": "Upload: my_mix.mp3"},
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
"name": "my_mix.mp3",
@ -183,7 +189,9 @@ fn event_serialization() {
#[test]
fn plain_content_deserialization() {
let json_data = json!({
"m.text": "Upload: my_new_song.webm",
"org.matrix.msc1767.text": [
{ "body": "Upload: my_new_song.webm" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
},
@ -246,8 +254,8 @@ fn plain_content_deserialization() {
});
let content = from_json_value::<AudioEventContent>(json_data).unwrap();
assert_eq!(content.message.find_plain(), Some("Upload: my_new_song.webm"));
assert_eq!(content.message.find_html(), None);
assert_eq!(content.text.find_plain(), Some("Upload: my_new_song.webm"));
assert_eq!(content.text.find_html(), None);
assert_eq!(content.file.url, "mxc://notareal.hs/abcdef");
let waveform = content.audio.waveform.unwrap();
assert_eq!(waveform.amplitudes().len(), 52);
@ -256,7 +264,9 @@ fn plain_content_deserialization() {
#[test]
fn encrypted_content_deserialization() {
let json_data = json!({
"m.text": "Upload: my_file.txt",
"org.matrix.msc1767.text": [
{ "body": "Upload: my_file.txt" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
"key": {
@ -276,8 +286,8 @@ fn encrypted_content_deserialization() {
});
let content = from_json_value::<AudioEventContent>(json_data).unwrap();
assert_eq!(content.message.find_plain(), Some("Upload: my_file.txt"));
assert_eq!(content.message.find_html(), None);
assert_eq!(content.text.find_plain(), Some("Upload: my_file.txt"));
assert_eq!(content.text.find_html(), None);
assert_eq!(content.file.url, "mxc://notareal.hs/abcdef");
assert!(content.file.encryption_info.is_some());
}
@ -286,7 +296,9 @@ fn encrypted_content_deserialization() {
fn message_event_deserialization() {
let json_data = json!({
"content": {
"m.text": "Upload: airplane_sound.opus",
"org.matrix.msc1767.text": [
{ "body": "Upload: airplane_sound.opus" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
"name": "airplane_sound.opus",
@ -316,8 +328,8 @@ fn message_event_deserialization() {
assert!(message_event.unsigned.is_empty());
let content = message_event.content;
assert_eq!(content.message.find_plain(), Some("Upload: airplane_sound.opus"));
assert_eq!(content.message.find_html(), None);
assert_eq!(content.text.find_plain(), Some("Upload: airplane_sound.opus"));
assert_eq!(content.text.find_html(), None);
assert_eq!(content.file.url, "mxc://notareal.hs/abcdef");
let info = content.file.info.unwrap();
assert_eq!(info.name.as_deref(), Some("airplane_sound.opus"));

View File

@ -7,7 +7,7 @@ use ruma_common::{
event_id,
events::{
file::{EncryptedContentInit, FileContentInfo, FileEventContent},
message::MessageContent,
message::TextContentBlock,
relation::InReplyTo,
room::{message::Relation, JsonWebKeyInit},
AnyMessageLikeEvent, MessageLikeEvent,
@ -20,7 +20,7 @@ use serde_json::{from_value as from_json_value, json, to_value as to_json_value}
#[test]
fn plain_content_serialization() {
let event_content = FileEventContent::plain(
let event_content = FileEventContent::plain_with_text(
"Upload: my_file.txt",
mxc_uri!("mxc://notareal.hs/abcdef").to_owned(),
None,
@ -29,7 +29,9 @@ fn plain_content_serialization() {
assert_eq!(
to_json_value(&event_content).unwrap(),
json!({
"org.matrix.msc1767.text": "Upload: my_file.txt",
"org.matrix.msc1767.text": [
{ "body": "Upload: my_file.txt" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
}
@ -39,7 +41,7 @@ fn plain_content_serialization() {
#[test]
fn encrypted_content_serialization() {
let event_content = FileEventContent::encrypted(
let event_content = FileEventContent::encrypted_with_text(
"Upload: my_file.txt",
mxc_uri!("mxc://notareal.hs/abcdef").to_owned(),
EncryptedContentInit {
@ -66,7 +68,9 @@ fn encrypted_content_serialization() {
assert_eq!(
to_json_value(&event_content).unwrap(),
json!({
"org.matrix.msc1767.text": "Upload: my_file.txt",
"org.matrix.msc1767.text": [
{ "body": "Upload: my_file.txt" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
"key": {
@ -89,8 +93,8 @@ fn encrypted_content_serialization() {
#[test]
fn file_event_serialization() {
let content = assign!(
FileEventContent::plain_message(
MessageContent::html(
FileEventContent::plain(
TextContentBlock::html(
"Upload: my_file.txt",
"Upload: <strong>my_file.txt</strong>",
),
@ -114,8 +118,10 @@ fn file_event_serialization() {
assert_eq!(
to_json_value(&content).unwrap(),
json!({
"org.matrix.msc1767.html": "Upload: <strong>my_file.txt</strong>",
"org.matrix.msc1767.text": "Upload: my_file.txt",
"org.matrix.msc1767.text": [
{ "mimetype": "text/html", "body": "Upload: <strong>my_file.txt</strong>" },
{ "body": "Upload: my_file.txt" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
"name": "my_file.txt",
@ -134,22 +140,26 @@ fn file_event_serialization() {
#[test]
fn plain_content_deserialization() {
let json_data = json!({
"m.text": "Upload: my_file.txt",
"org.matrix.msc1767.text": [
{ "body": "Upload: my_file.txt" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
}
});
let content = from_json_value::<FileEventContent>(json_data).unwrap();
assert_eq!(content.message.find_plain(), Some("Upload: my_file.txt"));
assert_eq!(content.message.find_html(), None);
assert_eq!(content.text.find_plain(), Some("Upload: my_file.txt"));
assert_eq!(content.text.find_html(), None);
assert_eq!(content.file.url, "mxc://notareal.hs/abcdef");
}
#[test]
fn encrypted_content_deserialization() {
let json_data = json!({
"m.text": "Upload: my_file.txt",
"org.matrix.msc1767.text": [
{ "body": "Upload: my_file.txt" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
"key": {
@ -168,8 +178,8 @@ fn encrypted_content_deserialization() {
});
let content = from_json_value::<FileEventContent>(json_data).unwrap();
assert_eq!(content.message.find_plain(), Some("Upload: my_file.txt"));
assert_eq!(content.message.find_html(), None);
assert_eq!(content.text.find_plain(), Some("Upload: my_file.txt"));
assert_eq!(content.text.find_html(), None);
assert_eq!(content.file.url, "mxc://notareal.hs/abcdef");
assert!(content.file.encryption_info.is_some());
}
@ -178,7 +188,7 @@ fn encrypted_content_deserialization() {
fn message_event_deserialization() {
let json_data = json!({
"content": {
"m.message": [
"org.matrix.msc1767.text": [
{ "body": "Upload: <strong>my_file.txt</strong>", "mimetype": "text/html"},
{ "body": "Upload: my_file.txt", "mimetype": "text/plain"},
],
@ -202,8 +212,8 @@ fn message_event_deserialization() {
);
assert_eq!(message_event.event_id, "$event:notareal.hs");
let content = message_event.content;
assert_eq!(content.message.find_plain(), Some("Upload: my_file.txt"));
assert_eq!(content.message.find_html(), Some("Upload: <strong>my_file.txt</strong>"));
assert_eq!(content.text.find_plain(), Some("Upload: my_file.txt"));
assert_eq!(content.text.find_html(), Some("Upload: <strong>my_file.txt</strong>"));
assert_eq!(content.file.url, "mxc://notareal.hs/abcdef");
let info = content.file.info.unwrap();
assert_eq!(info.name.as_deref(), Some("my_file.txt"));

View File

@ -11,7 +11,7 @@ use ruma_common::{
ImageContent, ImageEventContent, ThumbnailContent, ThumbnailFileContent,
ThumbnailFileContentInfo,
},
message::MessageContent,
message::TextContentBlock,
relation::InReplyTo,
room::{message::Relation, JsonWebKeyInit},
AnyMessageLikeEvent, MessageLikeEvent,
@ -32,7 +32,9 @@ fn plain_content_serialization() {
assert_eq!(
to_json_value(&event_content).unwrap(),
json!({
"org.matrix.msc1767.text": "Upload: my_image.jpg",
"org.matrix.msc1767.text": [
{ "body": "Upload: my_image.jpg" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
},
@ -72,7 +74,9 @@ fn encrypted_content_serialization() {
assert_eq!(
to_json_value(&event_content).unwrap(),
json!({
"org.matrix.msc1767.text": "Upload: my_image.jpg",
"org.matrix.msc1767.text": [
{ "body": "Upload: my_image.jpg" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
"key": {
@ -96,8 +100,8 @@ fn encrypted_content_serialization() {
#[test]
fn image_event_serialization() {
let content = assign!(
ImageEventContent::with_message(
MessageContent::html(
ImageEventContent::new(
TextContentBlock::html(
"Upload: my_house.jpg",
"Upload: <strong>my_house.jpg</strong>",
),
@ -125,7 +129,7 @@ fn image_event_serialization() {
),
None
)],
caption: Some(MessageContent::plain("This is my house")),
caption: TextContentBlock::plain("This is my house"),
relates_to: Some(Relation::Reply {
in_reply_to: InReplyTo::new(event_id!("$replyevent:example.com").to_owned()),
}),
@ -135,8 +139,10 @@ fn image_event_serialization() {
assert_eq!(
to_json_value(&content).unwrap(),
json!({
"org.matrix.msc1767.html": "Upload: <strong>my_house.jpg</strong>",
"org.matrix.msc1767.text": "Upload: my_house.jpg",
"org.matrix.msc1767.text": [
{ "mimetype": "text/html", "body": "Upload: <strong>my_house.jpg</strong>" },
{ "body": "Upload: my_house.jpg" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
"name": "my_house.jpg",
@ -157,7 +163,6 @@ fn image_event_serialization() {
"m.caption": [
{
"body": "This is my house",
"mimetype": "text/plain",
}
],
"m.relates_to": {
@ -172,7 +177,9 @@ fn image_event_serialization() {
#[test]
fn plain_content_deserialization() {
let json_data = json!({
"m.text": "Upload: my_cat.png",
"org.matrix.msc1767.text": [
{ "body": "Upload: my_cat.png" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
},
@ -187,21 +194,23 @@ fn plain_content_deserialization() {
});
let content = from_json_value::<ImageEventContent>(json_data).unwrap();
assert_eq!(content.message.find_plain(), Some("Upload: my_cat.png"));
assert_eq!(content.message.find_html(), None);
assert_eq!(content.text.find_plain(), Some("Upload: my_cat.png"));
assert_eq!(content.text.find_html(), None);
assert_eq!(content.file.url, "mxc://notareal.hs/abcdef");
assert_matches!(content.file.encryption_info, None);
assert_eq!(content.image.width, Some(uint!(668)));
assert_eq!(content.image.height, None);
assert_eq!(content.thumbnail.len(), 0);
let caption = content.caption.unwrap();
assert_eq!(caption.find_plain(), Some("Look at my cat!"));
assert_eq!(content.caption.len(), 1);
assert_eq!(content.caption.find_plain(), Some("Look at my cat!"));
}
#[test]
fn encrypted_content_deserialization() {
let json_data = json!({
"org.matrix.msc1767.text": "Upload: my_cat.png",
"org.matrix.msc1767.text": [
{ "body": "Upload: my_cat.png" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
"key": {
@ -226,22 +235,24 @@ fn encrypted_content_deserialization() {
});
let content = from_json_value::<ImageEventContent>(json_data).unwrap();
assert_eq!(content.message.find_plain(), Some("Upload: my_cat.png"));
assert_eq!(content.message.find_html(), None);
assert_eq!(content.text.find_plain(), Some("Upload: my_cat.png"));
assert_eq!(content.text.find_html(), None);
assert_eq!(content.file.url, "mxc://notareal.hs/abcdef");
assert!(content.file.encryption_info.is_some());
assert_eq!(content.image.width, None);
assert_eq!(content.image.height, None);
assert_eq!(content.thumbnail.len(), 1);
assert_eq!(content.thumbnail[0].file.url, "mxc://notareal.hs/thumbnail");
assert_matches!(content.caption, None);
assert!(content.caption.is_empty());
}
#[test]
fn message_event_deserialization() {
let json_data = json!({
"content": {
"org.matrix.msc1767.text": "Upload: my_gnome.webp",
"org.matrix.msc1767.text": [
{ "body": "Upload: my_gnome.webp" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
"name": "my_gnome.webp",
@ -271,8 +282,8 @@ fn message_event_deserialization() {
assert_eq!(message_event.sender, "@user:notareal.hs");
assert!(message_event.unsigned.is_empty());
let content = message_event.content;
assert_eq!(content.message.find_plain(), Some("Upload: my_gnome.webp"));
assert_eq!(content.message.find_html(), None);
assert_eq!(content.text.find_plain(), Some("Upload: my_gnome.webp"));
assert_eq!(content.text.find_html(), None);
assert_eq!(content.file.url, "mxc://notareal.hs/abcdef");
let info = content.file.info.unwrap();
assert_eq!(info.name.as_deref(), Some("my_gnome.webp"));

View File

@ -7,7 +7,7 @@ use ruma_common::{
event_id,
events::{
location::{AssetType, LocationContent, LocationEventContent, ZoomLevel, ZoomLevelError},
message::MessageContent,
message::TextContentBlock,
relation::InReplyTo,
room::message::Relation,
AnyMessageLikeEvent, MessageLikeEvent,
@ -28,7 +28,9 @@ fn plain_content_serialization() {
assert_eq!(
to_json_value(&event_content).unwrap(),
json!({
"org.matrix.msc1767.text": "Alice was at geo:51.5008,0.1247;u=35",
"org.matrix.msc1767.text": [
{ "body": "Alice was at geo:51.5008,0.1247;u=35" },
],
"m.location": {
"uri": "geo:51.5008,0.1247;u=35",
},
@ -39,8 +41,8 @@ fn plain_content_serialization() {
#[test]
fn event_serialization() {
let content = assign!(
LocationEventContent::with_message(
MessageContent::html(
LocationEventContent::new(
TextContentBlock::html(
"Alice was at geo:51.5008,0.1247;u=35 as of Sat Nov 13 18:50:58 2021",
"Alice was at <strong>geo:51.5008,0.1247;u=35</strong> as of <em>Sat Nov 13 18:50:58 2021</em>",
),
@ -63,8 +65,15 @@ fn event_serialization() {
assert_eq!(
to_json_value(&content).unwrap(),
json!({
"org.matrix.msc1767.html": "Alice was at <strong>geo:51.5008,0.1247;u=35</strong> as of <em>Sat Nov 13 18:50:58 2021</em>",
"org.matrix.msc1767.text": "Alice was at geo:51.5008,0.1247;u=35 as of Sat Nov 13 18:50:58 2021",
"org.matrix.msc1767.text": [
{
"mimetype": "text/html",
"body": "Alice was at <strong>geo:51.5008,0.1247;u=35</strong> as of <em>Sat Nov 13 18:50:58 2021</em>"
},
{
"body": "Alice was at geo:51.5008,0.1247;u=35 as of Sat Nov 13 18:50:58 2021"
},
],
"m.location": {
"uri": "geo:51.5008,0.1247;u=35",
"description": "Alice's whereabouts",
@ -83,7 +92,9 @@ fn event_serialization() {
#[test]
fn plain_content_deserialization() {
let json_data = json!({
"m.text": "Alice was at geo:51.5008,0.1247;u=35",
"org.matrix.msc1767.text": [
{ "body": "Alice was at geo:51.5008,0.1247;u=35" },
],
"m.location": {
"uri": "geo:51.5008,0.1247;u=35",
},
@ -91,8 +102,8 @@ fn plain_content_deserialization() {
let ev = from_json_value::<LocationEventContent>(json_data).unwrap();
assert_eq!(ev.message.find_plain(), Some("Alice was at geo:51.5008,0.1247;u=35"));
assert_eq!(ev.message.find_html(), None);
assert_eq!(ev.text.find_plain(), Some("Alice was at geo:51.5008,0.1247;u=35"));
assert_eq!(ev.text.find_html(), None);
assert_eq!(ev.location.uri, "geo:51.5008,0.1247;u=35");
assert_eq!(ev.location.description, None);
assert_matches!(ev.location.zoom_level, None);
@ -132,7 +143,7 @@ fn zoomlevel_deserialization_too_high() {
fn message_event_deserialization() {
let json_data = json!({
"content": {
"org.matrix.msc1767.message": [
"org.matrix.msc1767.text": [
{ "body": "Alice was at geo:51.5008,0.1247;u=35 as of Sat Nov 13 18:50:58 2021" },
],
"m.location": {
@ -160,10 +171,10 @@ fn message_event_deserialization() {
assert_matches!(ev, AnyMessageLikeEvent::Location(MessageLikeEvent::Original(ev)) => ev);
assert_eq!(
ev.content.message.find_plain(),
ev.content.text.find_plain(),
Some("Alice was at geo:51.5008,0.1247;u=35 as of Sat Nov 13 18:50:58 2021")
);
assert_eq!(ev.content.message.find_html(), None);
assert_eq!(ev.content.text.find_html(), None);
assert_eq!(ev.content.location.uri, "geo:51.5008,0.1247;u=35");
assert_eq!(ev.content.location.description.as_deref(), Some("Alice's whereabouts"));
assert_eq!(ev.content.location.zoom_level.unwrap().get(), uint!(4));

View File

@ -7,8 +7,7 @@ use ruma_common::{
event_id,
events::{
emote::EmoteEventContent,
message::{MessageContent, MessageEventContent, Text},
notice::NoticeEventContent,
message::{MessageEventContent, TextContentBlock, TextRepresentation},
relation::InReplyTo,
room::message::Relation,
AnyMessageLikeEvent, MessageLikeEvent,
@ -18,17 +17,6 @@ use ruma_common::{
};
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
#[test]
fn try_from_valid() {
let message = MessageContent::try_from(vec![Text::plain("A message")]).unwrap();
assert_eq!(message.len(), 1);
}
#[test]
fn try_from_invalid() {
assert_matches!(MessageContent::try_from(vec![]), Err(_));
}
#[test]
fn html_content_serialization() {
let message_event_content =
@ -37,8 +25,10 @@ fn html_content_serialization() {
assert_eq!(
to_json_value(&message_event_content).unwrap(),
json!({
"org.matrix.msc1767.html": "Hello, <em>World</em>!",
"org.matrix.msc1767.text": "Hello, World!",
"org.matrix.msc1767.text": [
{ "mimetype": "text/html", "body": "Hello, <em>World</em>!" },
{ "body": "Hello, World!" },
],
})
);
}
@ -51,7 +41,9 @@ fn plain_text_content_serialization() {
assert_eq!(
to_json_value(&message_event_content).unwrap(),
json!({
"org.matrix.msc1767.text": "> <@test:example.com> test\n\ntest reply",
"org.matrix.msc1767.text": [
{ "body": "> <@test:example.com> test\n\ntest reply" },
],
})
);
}
@ -59,9 +51,9 @@ fn plain_text_content_serialization() {
#[test]
fn unknown_mimetype_content_serialization() {
let message_event_content = MessageEventContent::from(
MessageContent::try_from(vec![
Text::plain("> <@test:example.com> test\n\ntest reply"),
Text::new(
TextContentBlock::try_from(vec![
TextRepresentation::plain("> <@test:example.com> test\n\ntest reply"),
TextRepresentation::new(
"application/json",
r#"{ "quote": "<@test:example.com> test", "reply": "test reply" }"#,
),
@ -72,16 +64,15 @@ fn unknown_mimetype_content_serialization() {
assert_eq!(
to_json_value(&message_event_content).unwrap(),
json!({
"org.matrix.msc1767.message": [
"org.matrix.msc1767.text": [
{
"body": "> <@test:example.com> test\n\ntest reply",
"mimetype": "text/plain",
},
{
"body": r#"{ "quote": "<@test:example.com> test", "reply": "test reply" }"#,
"mimetype": "application/json",
},
]
],
})
);
}
@ -94,8 +85,15 @@ fn markdown_content_serialization() {
assert_eq!(
to_json_value(&formatted_message).unwrap(),
json!({
"org.matrix.msc1767.html": "<p>Testing <strong>bold</strong> and <em>italic</em>!</p>\n",
"org.matrix.msc1767.text": "Testing **bold** and _italic_!",
"org.matrix.msc1767.text": [
{
"mimetype": "text/html",
"body": "<p>Testing <strong>bold</strong> and <em>italic</em>!</p>\n",
},
{
"body": "Testing **bold** and _italic_!",
},
],
})
);
@ -104,7 +102,9 @@ fn markdown_content_serialization() {
assert_eq!(
to_json_value(&plain_message_simple).unwrap(),
json!({
"org.matrix.msc1767.text": "Testing a simple phrase…",
"org.matrix.msc1767.text": [
{ "body": "Testing a simple phrase…" },
],
})
);
@ -114,8 +114,15 @@ fn markdown_content_serialization() {
assert_eq!(
to_json_value(&plain_message_paragraphs).unwrap(),
json!({
"org.matrix.msc1767.html": "<p>Testing</p>\n<p>Several</p>\n<p>Paragraphs.</p>\n",
"org.matrix.msc1767.text": "Testing\n\nSeveral\n\nParagraphs.",
"org.matrix.msc1767.text": [
{
"mimetype": "text/html",
"body": "<p>Testing</p>\n<p>Several</p>\n<p>Paragraphs.</p>\n",
},
{
"body": "Testing\n\nSeveral\n\nParagraphs.",
},
],
})
);
}
@ -132,7 +139,9 @@ fn relates_to_content_serialization() {
});
let json_data = json!({
"org.matrix.msc1767.text": "> <@test:example.com> test\n\ntest reply",
"org.matrix.msc1767.text": [
{ "body": "> <@test:example.com> test\n\ntest reply" },
],
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$15827405538098VGFWH:example.com"
@ -148,110 +157,66 @@ fn message_event_serialization() {
let content = MessageEventContent::plain("Hello, World!");
assert_eq!(
to_json_value(&content).unwrap(),
json!({ "org.matrix.msc1767.text": "Hello, World!" })
json!({
"org.matrix.msc1767.text": [
{ "body": "Hello, World!" },
],
})
);
}
#[test]
fn plain_text_content_unstable_deserialization() {
fn plain_text_content_deserialization() {
let json_data = json!({
"org.matrix.msc1767.text": "This is my body",
"org.matrix.msc1767.text": [
{ "body": "This is my body" },
],
});
let content = from_json_value::<MessageEventContent>(json_data).unwrap();
assert_eq!(content.message.find_plain(), Some("This is my body"));
assert_eq!(content.message.find_html(), None);
assert_eq!(content.text.find_plain(), Some("This is my body"));
assert_eq!(content.text.find_html(), None);
#[cfg(feature = "unstable-msc3955")]
assert!(!content.automated);
}
#[test]
fn plain_text_content_stable_deserialization() {
fn html_content_deserialization() {
let json_data = json!({
"m.text": "This is my body",
});
let content = from_json_value::<MessageEventContent>(json_data).unwrap();
assert_eq!(content.message.find_plain(), Some("This is my body"));
assert_eq!(content.message.find_html(), None);
}
#[test]
fn html_content_unstable_deserialization() {
let json_data = json!({
"org.matrix.msc1767.html": "Hello, <em>New World</em>!",
});
let content = from_json_value::<MessageEventContent>(json_data).unwrap();
assert_eq!(content.message.find_plain(), None);
assert_eq!(content.message.find_html(), Some("Hello, <em>New World</em>!"));
}
#[test]
fn html_content_stable_deserialization() {
let json_data = json!({
"m.html": "Hello, <em>New World</em>!",
});
let content = from_json_value::<MessageEventContent>(json_data).unwrap();
assert_eq!(content.message.find_plain(), None);
assert_eq!(content.message.find_html(), Some("Hello, <em>New World</em>!"));
}
#[test]
fn html_and_text_content_unstable_deserialization() {
let json_data = json!({
"org.matrix.msc1767.html": "Hello, <em>New World</em>!",
"org.matrix.msc1767.text": "Hello, New World!",
});
let content = from_json_value::<MessageEventContent>(json_data).unwrap();
assert_eq!(content.message.find_plain(), Some("Hello, New World!"));
assert_eq!(content.message.find_html(), Some("Hello, <em>New World</em>!"));
}
#[test]
fn html_and_text_content_stable_deserialization() {
let json_data = json!({
"m.html": "Hello, <em>New World</em>!",
"m.text": "Hello, New World!",
});
let content = from_json_value::<MessageEventContent>(json_data).unwrap();
assert_eq!(content.message.find_plain(), Some("Hello, New World!"));
assert_eq!(content.message.find_html(), Some("Hello, <em>New World</em>!"));
}
#[test]
fn message_content_unstable_deserialization() {
let json_data = json!({
"org.matrix.msc1767.message": [
{ "body": "Hello, <em>New World</em>!", "mimetype": "text/html"},
{ "body": "Hello, New World!" },
"org.matrix.msc1767.text": [
{ "mimetype": "text/html", "body": "Hello, <em>New World</em>!" },
]
});
let content = from_json_value::<MessageEventContent>(json_data).unwrap();
assert_eq!(content.message.find_plain(), Some("Hello, New World!"));
assert_eq!(content.message.find_html(), Some("Hello, <em>New World</em>!"));
assert_eq!(content.text.find_plain(), None);
assert_eq!(content.text.find_html(), Some("Hello, <em>New World</em>!"));
#[cfg(feature = "unstable-msc3955")]
assert!(!content.automated);
}
#[test]
fn message_content_stable_deserialization() {
fn html_and_text_content_deserialization() {
let json_data = json!({
"m.message": [
{ "body": "Hello, <em>New World</em>!", "mimetype": "text/html"},
"org.matrix.msc1767.text": [
{ "mimetype": "text/html", "body": "Hello, <em>New World</em>!" },
{ "body": "Hello, New World!" },
]
],
});
let content = from_json_value::<MessageEventContent>(json_data).unwrap();
assert_eq!(content.message.find_plain(), Some("Hello, New World!"));
assert_eq!(content.message.find_html(), Some("Hello, <em>New World</em>!"));
assert_eq!(content.text.find_plain(), Some("Hello, New World!"));
assert_eq!(content.text.find_html(), Some("Hello, <em>New World</em>!"));
#[cfg(feature = "unstable-msc3955")]
assert!(!content.automated);
}
#[test]
fn relates_to_content_deserialization() {
let json_data = json!({
"org.matrix.msc1767.text": "> <@test:example.com> test\n\ntest reply",
"org.matrix.msc1767.text": [
{ "body": "> <@test:example.com> test\n\ntest reply" },
],
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$15827405538098VGFWH:example.com"
@ -260,8 +225,8 @@ fn relates_to_content_deserialization() {
});
let content = from_json_value::<MessageEventContent>(json_data).unwrap();
assert_eq!(content.message.find_plain(), Some("> <@test:example.com> test\n\ntest reply"));
assert_eq!(content.message.find_html(), None);
assert_eq!(content.text.find_plain(), Some("> <@test:example.com> test\n\ntest reply"));
assert_eq!(content.text.find_html(), None);
let event_id = assert_matches!(
content.relates_to,
@ -274,13 +239,15 @@ fn relates_to_content_deserialization() {
fn message_event_deserialization() {
let json_data = json!({
"content": {
"org.matrix.msc1767.text": "Hello, World!",
"org.matrix.msc1767.text": [
{ "body": "Hello, World!" },
],
},
"event_id": "$event:notareal.hs",
"origin_server_ts": 134_829_848,
"room_id": "!roomid:notareal.hs",
"sender": "@user:notareal.hs",
"type": "m.message",
"type": "org.matrix.msc1767.message",
});
let message_event = assert_matches!(
@ -288,7 +255,7 @@ fn message_event_deserialization() {
Ok(AnyMessageLikeEvent::Message(MessageLikeEvent::Original(message_event))) => message_event
);
assert_eq!(message_event.event_id, "$event:notareal.hs");
assert_eq!(message_event.content.message.find_plain(), Some("Hello, World!"));
assert_eq!(message_event.content.text.find_plain(), Some("Hello, World!"));
assert_eq!(message_event.origin_server_ts, MilliSecondsSinceUnixEpoch(uint!(134_829_848)));
assert_eq!(message_event.room_id, "!roomid:notareal.hs");
assert_eq!(message_event.sender, "@user:notareal.hs");
@ -296,79 +263,7 @@ fn message_event_deserialization() {
}
#[test]
fn notice_event_serialization() {
let content = NoticeEventContent::plain("Hello, I'm a robot!");
assert_eq!(
to_json_value(&content).unwrap(),
json!({ "org.matrix.msc1767.text": "Hello, I'm a robot!" })
);
}
#[test]
fn notice_event_stable_deserialization() {
let json_data = json!({
"content": {
"m.message": [
{ "body": "Hello, I'm a <em>robot</em>!", "mimetype": "text/html"},
{ "body": "Hello, I'm a robot!" },
]
},
"event_id": "$event:notareal.hs",
"origin_server_ts": 134_829_848,
"room_id": "!roomid:notareal.hs",
"sender": "@user:notareal.hs",
"type": "m.notice",
});
let message_event = assert_matches!(
from_json_value::<AnyMessageLikeEvent>(json_data),
Ok(AnyMessageLikeEvent::Notice(MessageLikeEvent::Original(message_event))) => message_event
);
assert_eq!(message_event.event_id, "$event:notareal.hs");
assert_eq!(message_event.origin_server_ts, MilliSecondsSinceUnixEpoch(uint!(134_829_848)));
assert_eq!(message_event.room_id, "!roomid:notareal.hs");
assert_eq!(message_event.sender, "@user:notareal.hs");
assert!(message_event.unsigned.is_empty());
let message = message_event.content.message;
assert_eq!(message.find_plain(), Some("Hello, I'm a robot!"));
assert_eq!(message.find_html(), Some("Hello, I'm a <em>robot</em>!"));
}
#[test]
fn notice_event_unstable_deserialization() {
let json_data = json!({
"content": {
"org.matrix.msc1767.message": [
{ "body": "Hello, I'm a <em>robot</em>!", "mimetype": "text/html"},
{ "body": "Hello, I'm a robot!" },
]
},
"event_id": "$event:notareal.hs",
"origin_server_ts": 134_829_848,
"room_id": "!roomid:notareal.hs",
"sender": "@user:notareal.hs",
"type": "m.notice",
});
let message_event = assert_matches!(
from_json_value::<AnyMessageLikeEvent>(json_data),
Ok(AnyMessageLikeEvent::Notice(MessageLikeEvent::Original(message_event))) => message_event
);
assert_eq!(message_event.event_id, "$event:notareal.hs");
assert_eq!(message_event.origin_server_ts, MilliSecondsSinceUnixEpoch(uint!(134_829_848)));
assert_eq!(message_event.room_id, "!roomid:notareal.hs");
assert_eq!(message_event.sender, "@user:notareal.hs");
assert!(message_event.unsigned.is_empty());
let message = message_event.content.message;
assert_eq!(message.find_plain(), Some("Hello, I'm a robot!"));
assert_eq!(message.find_html(), Some("Hello, I'm a <em>robot</em>!"));
}
#[test]
#[cfg(feature = "unstable-msc3954")]
fn emote_event_serialization() {
let content =
EmoteEventContent::html("is testing some code…", "is testing some <code>code</code>…");
@ -376,23 +271,28 @@ fn emote_event_serialization() {
assert_eq!(
to_json_value(&content).unwrap(),
json!({
"org.matrix.msc1767.html": "is testing some <code>code</code>…",
"org.matrix.msc1767.text": "is testing some code…",
"org.matrix.msc1767.text": [
{ "mimetype": "text/html", "body": "is testing some <code>code</code>…" },
{ "body": "is testing some code…" },
],
})
);
}
#[test]
fn emote_event_stable_deserialization() {
#[cfg(feature = "unstable-msc3954")]
fn emote_event_deserialization() {
let json_data = json!({
"content": {
"m.text": "is testing some code…",
"org.matrix.msc1767.text": [
{ "body": "is testing some code…" },
],
},
"event_id": "$event:notareal.hs",
"origin_server_ts": 134_829_848,
"room_id": "!roomid:notareal.hs",
"sender": "@user:notareal.hs",
"type": "m.emote",
"type": "org.matrix.msc1767.emote",
});
let message_event = assert_matches!(
@ -406,75 +306,76 @@ fn emote_event_stable_deserialization() {
assert_eq!(message_event.sender, "@user:notareal.hs");
assert!(message_event.unsigned.is_empty());
let message = message_event.content.message;
assert_eq!(message.find_plain(), Some("is testing some code…"));
assert_eq!(message.find_html(), None);
}
#[test]
fn emote_event_unstable_deserialization() {
let json_data = json!({
"content": {
"org.matrix.msc1767.text": "is testing some code…",
},
"event_id": "$event:notareal.hs",
"origin_server_ts": 134_829_848,
"room_id": "!roomid:notareal.hs",
"sender": "@user:notareal.hs",
"type": "m.emote",
});
let message_event = assert_matches!(
from_json_value::<AnyMessageLikeEvent>(json_data),
Ok(AnyMessageLikeEvent::Emote(MessageLikeEvent::Original(message_event))) => message_event
);
assert_eq!(message_event.event_id, "$event:notareal.hs");
assert_eq!(message_event.origin_server_ts, MilliSecondsSinceUnixEpoch(uint!(134_829_848)));
assert_eq!(message_event.room_id, "!roomid:notareal.hs");
assert_eq!(message_event.sender, "@user:notareal.hs");
assert!(message_event.unsigned.is_empty());
let message = message_event.content.message;
assert_eq!(message.find_plain(), Some("is testing some code…"));
assert_eq!(message.find_html(), None);
let text = message_event.content.text;
assert_eq!(text.find_plain(), Some("is testing some code…"));
assert_eq!(text.find_html(), None);
}
#[test]
#[cfg(feature = "unstable-msc3554")]
fn lang_serialization() {
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()) }),
let content = TextContentBlock::try_from(vec![
assign!(TextRepresentation::plain("Bonjour le monde !"), { lang: Some("fr".into()) }),
assign!(TextRepresentation::plain("Hallo Welt!"), { lang: Some("de".into()) }),
assign!(TextRepresentation::plain("Hello World!"), { lang: Some("en".into()) }),
])
.unwrap();
assert_eq!(
to_json_value(content).unwrap(),
json!({
"org.matrix.msc1767.message": [
{ "body": "Bonjour le monde !", "mimetype": "text/plain", "lang": "fr"},
{ "body": "Hallo Welt!", "mimetype": "text/plain", "lang": "de"},
{ "body": "Hello World!", "mimetype": "text/plain", "lang": "en"},
]
})
json!([
{ "body": "Bonjour le monde !", "lang": "fr"},
{ "body": "Hallo Welt!", "lang": "de"},
{ "body": "Hello World!", "lang": "en"},
])
);
}
#[test]
#[cfg(feature = "unstable-msc3554")]
fn lang_deserialization() {
let json_data = json!({
"org.matrix.msc1767.message": [
{ "body": "Bonjour le monde !", "mimetype": "text/plain", "lang": "fr"},
{ "body": "Hallo Welt!", "mimetype": "text/plain", "lang": "de"},
{ "body": "Hello World!", "mimetype": "text/plain", "lang": "en"},
]
});
let json_data = json!([
{ "body": "Bonjour le monde !", "lang": "fr"},
{ "body": "Hallo Welt!", "lang": "de"},
{ "body": "Hello World!", "lang": "en"},
]);
let content = from_json_value::<MessageContent>(json_data).unwrap();
let content = from_json_value::<TextContentBlock>(json_data).unwrap();
assert_eq!(content[0].lang.as_deref(), Some("fr"));
assert_eq!(content[1].lang.as_deref(), Some("de"));
assert_eq!(content[2].lang.as_deref(), Some("en"));
}
#[test]
#[cfg(feature = "unstable-msc3955")]
fn automated_content_serialization() {
let mut message_event_content =
MessageEventContent::plain("> <@test:example.com> test\n\ntest reply");
message_event_content.automated = true;
assert_eq!(
to_json_value(&message_event_content).unwrap(),
json!({
"org.matrix.msc1767.text": [
{ "body": "> <@test:example.com> test\n\ntest reply" },
],
"org.matrix.msc1767.automated": true,
})
);
}
#[test]
#[cfg(feature = "unstable-msc3955")]
fn automated_content_deserialization() {
let json_data = json!({
"org.matrix.msc1767.text": [
{ "mimetype": "text/html", "body": "Hello, <em>New World</em>!" },
],
"org.matrix.msc1767.automated": true,
});
let content = from_json_value::<MessageEventContent>(json_data).unwrap();
assert_eq!(content.text.find_plain(), None);
assert_eq!(content.text.find_html(), Some("Hello, <em>New World</em>!"));
assert!(content.automated);
}

View File

@ -6,7 +6,7 @@ use js_int::uint;
use ruma_common::{
event_id,
events::{
message::MessageContent,
message::TextContentBlock,
poll::{
end::{PollEndContent, PollEndEventContent},
response::{PollResponseContent, PollResponseEventContent},
@ -24,8 +24,8 @@ use serde_json::{from_value as from_json_value, json, to_value as to_json_value}
#[test]
fn poll_answers_deserialization_valid() {
let json_data = json!([
{ "id": "aaa", "m.text": "First answer" },
{ "id": "bbb", "m.text": "Second answer" },
{ "id": "aaa", "org.matrix.msc1767.text": [{ "body": "First answer" }] },
{ "id": "bbb", "org.matrix.msc1767.text": [{ "body": "Second answer" }] },
]);
let answers = from_json_value::<PollAnswers>(json_data).unwrap();
@ -35,28 +35,28 @@ fn poll_answers_deserialization_valid() {
#[test]
fn poll_answers_deserialization_truncate() {
let json_data = json!([
{ "id": "aaa", "m.text": "1st answer" },
{ "id": "bbb", "m.text": "2nd answer" },
{ "id": "ccc", "m.text": "3rd answer" },
{ "id": "ddd", "m.text": "4th answer" },
{ "id": "eee", "m.text": "5th answer" },
{ "id": "fff", "m.text": "6th answer" },
{ "id": "ggg", "m.text": "7th answer" },
{ "id": "hhh", "m.text": "8th answer" },
{ "id": "iii", "m.text": "9th answer" },
{ "id": "jjj", "m.text": "10th answer" },
{ "id": "kkk", "m.text": "11th answer" },
{ "id": "lll", "m.text": "12th answer" },
{ "id": "mmm", "m.text": "13th answer" },
{ "id": "nnn", "m.text": "14th answer" },
{ "id": "ooo", "m.text": "15th answer" },
{ "id": "ppp", "m.text": "16th answer" },
{ "id": "qqq", "m.text": "17th answer" },
{ "id": "rrr", "m.text": "18th answer" },
{ "id": "sss", "m.text": "19th answer" },
{ "id": "ttt", "m.text": "20th answer" },
{ "id": "uuu", "m.text": "21th answer" },
{ "id": "vvv", "m.text": "22th answer" },
{ "id": "aaa", "org.matrix.msc1767.text": [{ "body": "1st answer" }] },
{ "id": "bbb", "org.matrix.msc1767.text": [{ "body": "2nd answer" }] },
{ "id": "ccc", "org.matrix.msc1767.text": [{ "body": "3rd answer" }] },
{ "id": "ddd", "org.matrix.msc1767.text": [{ "body": "4th answer" }] },
{ "id": "eee", "org.matrix.msc1767.text": [{ "body": "5th answer" }] },
{ "id": "fff", "org.matrix.msc1767.text": [{ "body": "6th answer" }] },
{ "id": "ggg", "org.matrix.msc1767.text": [{ "body": "7th answer" }] },
{ "id": "hhh", "org.matrix.msc1767.text": [{ "body": "8th answer" }] },
{ "id": "iii", "org.matrix.msc1767.text": [{ "body": "9th answer" }] },
{ "id": "jjj", "org.matrix.msc1767.text": [{ "body": "10th answer" }] },
{ "id": "kkk", "org.matrix.msc1767.text": [{ "body": "11th answer" }] },
{ "id": "lll", "org.matrix.msc1767.text": [{ "body": "12th answer" }] },
{ "id": "mmm", "org.matrix.msc1767.text": [{ "body": "13th answer" }] },
{ "id": "nnn", "org.matrix.msc1767.text": [{ "body": "14th answer" }] },
{ "id": "ooo", "org.matrix.msc1767.text": [{ "body": "15th answer" }] },
{ "id": "ppp", "org.matrix.msc1767.text": [{ "body": "16th answer" }] },
{ "id": "qqq", "org.matrix.msc1767.text": [{ "body": "17th answer" }] },
{ "id": "rrr", "org.matrix.msc1767.text": [{ "body": "18th answer" }] },
{ "id": "sss", "org.matrix.msc1767.text": [{ "body": "19th answer" }] },
{ "id": "ttt", "org.matrix.msc1767.text": [{ "body": "20th answer" }] },
{ "id": "uuu", "org.matrix.msc1767.text": [{ "body": "21th answer" }] },
{ "id": "vvv", "org.matrix.msc1767.text": [{ "body": "22th answer" }] },
]);
let answers = from_json_value::<PollAnswers>(json_data).unwrap();
@ -75,12 +75,12 @@ fn poll_answers_deserialization_not_enough() {
#[test]
fn start_content_serialization() {
let event_content = PollStartEventContent::new(PollStartContent::new(
MessageContent::plain("How's the weather?"),
TextContentBlock::plain("How's the weather?"),
PollKind::Undisclosed,
vec![
PollAnswer::new("not-bad".to_owned(), MessageContent::plain("Not bad…")),
PollAnswer::new("fine".to_owned(), MessageContent::plain("Fine.")),
PollAnswer::new("amazing".to_owned(), MessageContent::plain("Amazing!")),
PollAnswer::new("not-bad".to_owned(), TextContentBlock::plain("Not bad…")),
PollAnswer::new("fine".to_owned(), TextContentBlock::plain("Fine.")),
PollAnswer::new("amazing".to_owned(), TextContentBlock::plain("Amazing!")),
]
.try_into()
.unwrap(),
@ -90,12 +90,12 @@ fn start_content_serialization() {
to_json_value(&event_content).unwrap(),
json!({
"org.matrix.msc3381.poll.start": {
"question": { "org.matrix.msc1767.text": "How's the weather?" },
"question": { "org.matrix.msc1767.text": [{ "body": "How's the weather?" }] },
"kind": "org.matrix.msc3381.poll.undisclosed",
"answers": [
{ "id": "not-bad", "org.matrix.msc1767.text": "Not bad…"},
{ "id": "fine", "org.matrix.msc1767.text": "Fine."},
{ "id": "amazing", "org.matrix.msc1767.text": "Amazing!"},
{ "id": "not-bad", "org.matrix.msc1767.text": [{ "body": "Not bad…" }] },
{ "id": "fine", "org.matrix.msc1767.text": [{ "body": "Fine." }] },
{ "id": "amazing", "org.matrix.msc1767.text": [{ "body": "Amazing!" }] },
],
},
})
@ -106,12 +106,12 @@ fn start_content_serialization() {
fn start_event_serialization() {
let content = PollStartEventContent::new(assign!(
PollStartContent::new(
MessageContent::plain("How's the weather?"),
TextContentBlock::plain("How's the weather?"),
PollKind::Disclosed,
vec![
PollAnswer::new("not-bad".to_owned(), MessageContent::plain("Not bad…")),
PollAnswer::new("fine".to_owned(), MessageContent::plain("Fine.")),
PollAnswer::new("amazing".to_owned(), MessageContent::plain("Amazing!")),
PollAnswer::new("not-bad".to_owned(), TextContentBlock::plain("Not bad…")),
PollAnswer::new("fine".to_owned(), TextContentBlock::plain("Fine.")),
PollAnswer::new("amazing".to_owned(), TextContentBlock::plain("Amazing!")),
]
.try_into()
.unwrap(),
@ -123,13 +123,13 @@ fn start_event_serialization() {
to_json_value(&content).unwrap(),
json!({
"org.matrix.msc3381.poll.start": {
"question": { "org.matrix.msc1767.text": "How's the weather?" },
"question": { "org.matrix.msc1767.text": [{ "body": "How's the weather?" }] },
"kind": "org.matrix.msc3381.poll.disclosed",
"max_selections": 2,
"answers": [
{ "id": "not-bad", "org.matrix.msc1767.text": "Not bad…"},
{ "id": "fine", "org.matrix.msc1767.text": "Fine."},
{ "id": "amazing", "org.matrix.msc1767.text": "Amazing!"},
{ "id": "not-bad", "org.matrix.msc1767.text": [{ "body": "Not bad…" }] },
{ "id": "fine", "org.matrix.msc1767.text": [{ "body": "Fine." }] },
{ "id": "amazing", "org.matrix.msc1767.text": [{ "body": "Amazing!" }] },
]
},
})
@ -137,17 +137,17 @@ fn start_event_serialization() {
}
#[test]
fn start_event_unstable_deserialization() {
fn start_event_deserialization() {
let json_data = json!({
"content": {
"org.matrix.msc3381.poll.start": {
"question": { "org.matrix.msc1767.text": "How's the weather?" },
"question": { "org.matrix.msc1767.text": [{ "body": "How's the weather?" }] },
"kind": "org.matrix.msc3381.poll.undisclosed",
"max_selections": 2,
"answers": [
{ "id": "not-bad", "org.matrix.msc1767.text": "Not bad…"},
{ "id": "fine", "org.matrix.msc1767.text": "Fine."},
{ "id": "amazing", "org.matrix.msc1767.text": "Amazing!"},
{ "id": "not-bad", "org.matrix.msc1767.text": [{ "body": "Not bad…" }] },
{ "id": "fine", "org.matrix.msc1767.text": [{ "body": "Fine." }] },
{ "id": "amazing", "org.matrix.msc1767.text": [{ "body": "Amazing!" }] },
]
},
},
@ -164,57 +164,17 @@ fn start_event_unstable_deserialization() {
AnyMessageLikeEvent::PollStart(MessageLikeEvent::Original(message_event)) => message_event
);
let poll_start = message_event.content.poll_start;
assert_eq!(poll_start.question[0].body, "How's the weather?");
assert_eq!(poll_start.question.text[0].body, "How's the weather?");
assert_eq!(poll_start.kind, PollKind::Undisclosed);
assert_eq!(poll_start.max_selections, uint!(2));
let answers = poll_start.answers.answers();
assert_eq!(answers.len(), 3);
assert_eq!(answers[0].id, "not-bad");
assert_eq!(answers[0].answer[0].body, "Not bad…");
assert_eq!(answers[0].text[0].body, "Not bad…");
assert_eq!(answers[1].id, "fine");
assert_eq!(answers[1].answer[0].body, "Fine.");
assert_eq!(answers[1].text[0].body, "Fine.");
assert_eq!(answers[2].id, "amazing");
assert_eq!(answers[2].answer[0].body, "Amazing!");
}
#[test]
fn start_event_stable_deserialization() {
let json_data = json!({
"content": {
"m.poll.start": {
"question": { "m.text": "How's the weather?" },
"kind": "m.poll.disclosed",
"answers": [
{ "id": "not-bad", "m.text": "Not bad…"},
{ "id": "fine", "m.text": "Fine."},
{ "id": "amazing", "m.text": "Amazing!"},
]
},
},
"event_id": "$event:notareal.hs",
"origin_server_ts": 134_829_848,
"room_id": "!roomid:notareal.hs",
"sender": "@user:notareal.hs",
"type": "m.poll.start",
});
let event = from_json_value::<AnyMessageLikeEvent>(json_data).unwrap();
let message_event = assert_matches!(
event,
AnyMessageLikeEvent::PollStart(MessageLikeEvent::Original(message_event)) => message_event
);
let poll_start = message_event.content.poll_start;
assert_eq!(poll_start.question[0].body, "How's the weather?");
assert_eq!(poll_start.kind, PollKind::Disclosed);
assert_eq!(poll_start.max_selections, uint!(1));
let answers = poll_start.answers.answers();
assert_eq!(answers.len(), 3);
assert_eq!(answers[0].id, "not-bad");
assert_eq!(answers[0].answer[0].body, "Not bad…");
assert_eq!(answers[1].id, "fine");
assert_eq!(answers[1].answer[0].body, "Fine.");
assert_eq!(answers[2].id, "amazing");
assert_eq!(answers[2].answer[0].body, "Amazing!");
assert_eq!(answers[2].text[0].body, "Amazing!");
}
#[test]

View File

@ -10,7 +10,7 @@ use ruma_common::{
events::{
file::{EncryptedContentInit, FileContent, FileContentInfo},
image::{ThumbnailContent, ThumbnailFileContent, ThumbnailFileContentInfo},
message::MessageContent,
message::TextContentBlock,
relation::InReplyTo,
room::{message::Relation, JsonWebKeyInit},
video::{VideoContent, VideoEventContent},
@ -32,7 +32,9 @@ fn plain_content_serialization() {
assert_eq!(
to_json_value(&event_content).unwrap(),
json!({
"org.matrix.msc1767.text": "Upload: my_video.webm",
"org.matrix.msc1767.text": [
{"body": "Upload: my_video.webm" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
},
@ -72,7 +74,9 @@ fn encrypted_content_serialization() {
assert_eq!(
to_json_value(&event_content).unwrap(),
json!({
"org.matrix.msc1767.text": "Upload: my_video.webm",
"org.matrix.msc1767.text": [
{ "body": "Upload: my_video.webm" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
"key": {
@ -96,8 +100,8 @@ fn encrypted_content_serialization() {
#[test]
fn event_serialization() {
let content = assign!(
VideoEventContent::with_message(
MessageContent::html(
VideoEventContent::new(
TextContentBlock::html(
"Upload: my_lava_lamp.webm",
"Upload: <strong>my_lava_lamp.webm</strong>",
),
@ -132,7 +136,7 @@ fn event_serialization() {
),
None
)],
caption: Some(MessageContent::plain("This is my awesome vintage lava lamp")),
caption: TextContentBlock::plain("This is my awesome vintage lava lamp"),
relates_to: Some(Relation::Reply {
in_reply_to: InReplyTo::new(event_id!("$replyevent:example.com").to_owned()),
}),
@ -142,8 +146,10 @@ fn event_serialization() {
assert_eq!(
to_json_value(&content).unwrap(),
json!({
"org.matrix.msc1767.html": "Upload: <strong>my_lava_lamp.webm</strong>",
"org.matrix.msc1767.text": "Upload: my_lava_lamp.webm",
"org.matrix.msc1767.text": [
{ "mimetype": "text/html", "body": "Upload: <strong>my_lava_lamp.webm</strong>" },
{ "body": "Upload: my_lava_lamp.webm" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
"name": "my_lava_lamp.webm",
@ -165,7 +171,6 @@ fn event_serialization() {
"m.caption": [
{
"body": "This is my awesome vintage lava lamp",
"mimetype": "text/plain",
}
],
"m.relates_to": {
@ -180,7 +185,9 @@ fn event_serialization() {
#[test]
fn plain_content_deserialization() {
let json_data = json!({
"m.text": "Video: my_cat.mp4",
"org.matrix.msc1767.text": [
{ "body": "Video: my_cat.mp4" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
},
@ -195,23 +202,24 @@ fn plain_content_deserialization() {
});
let content = from_json_value::<VideoEventContent>(json_data).unwrap();
assert_eq!(content.message.find_plain(), Some("Video: my_cat.mp4"));
assert_eq!(content.message.find_html(), None);
assert_eq!(content.text.find_plain(), Some("Video: my_cat.mp4"));
assert_eq!(content.text.find_html(), None);
assert_eq!(content.file.url, "mxc://notareal.hs/abcdef");
assert_matches!(content.file.encryption_info, None);
assert_eq!(content.video.width, None);
assert_eq!(content.video.height, None);
assert_eq!(content.video.duration, Some(Duration::from_millis(5_668)));
assert_eq!(content.thumbnail.len(), 0);
let caption = content.caption.unwrap();
assert_eq!(caption.find_plain(), Some("Look at my cat!"));
assert_eq!(caption.find_html(), None);
assert_eq!(content.caption.find_plain(), Some("Look at my cat!"));
assert_eq!(content.caption.find_html(), None);
}
#[test]
fn encrypted_content_deserialization() {
let json_data = json!({
"m.text": "Video: my_cat.mp4",
"org.matrix.msc1767.text": [
{ "body": "Video: my_cat.mp4" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
"key": {
@ -236,8 +244,8 @@ fn encrypted_content_deserialization() {
});
let content = from_json_value::<VideoEventContent>(json_data).unwrap();
assert_eq!(content.message.find_plain(), Some("Video: my_cat.mp4"));
assert_eq!(content.message.find_html(), None);
assert_eq!(content.text.find_plain(), Some("Video: my_cat.mp4"));
assert_eq!(content.text.find_html(), None);
assert_eq!(content.file.url, "mxc://notareal.hs/abcdef");
assert!(content.file.encryption_info.is_some());
assert_eq!(content.video.width, None);
@ -245,14 +253,16 @@ fn encrypted_content_deserialization() {
assert_eq!(content.video.duration, None);
assert_eq!(content.thumbnail.len(), 1);
assert_eq!(content.thumbnail[0].file.url, "mxc://notareal.hs/thumbnail");
assert_matches!(content.caption, None);
assert!(content.caption.is_empty());
}
#[test]
fn message_event_deserialization() {
let json_data = json!({
"content": {
"m.text": "Upload: my_gnome.webm",
"org.matrix.msc1767.text": [
{ "body": "Upload: my_gnome.webm" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
"name": "my_gnome.webm",
@ -282,8 +292,8 @@ fn message_event_deserialization() {
assert!(ev.unsigned.is_empty());
let content = ev.content;
assert_eq!(content.message.find_plain(), Some("Upload: my_gnome.webm"));
assert_eq!(content.message.find_html(), None);
assert_eq!(content.text.find_plain(), Some("Upload: my_gnome.webm"));
assert_eq!(content.text.find_html(), None);
assert_eq!(content.file.url, "mxc://notareal.hs/abcdef");
assert_eq!(content.video.width, Some(uint!(1300)));
assert_eq!(content.video.height, Some(uint!(837)));

View File

@ -54,7 +54,9 @@ fn event_serialization() {
assert_eq!(
to_json_value(&content).unwrap(),
json!({
"org.matrix.msc1767.text": "Voice message",
"org.matrix.msc1767.text": [
{ "body": "Voice message" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
"name": "voice_message.ogg",
@ -78,7 +80,9 @@ fn event_serialization() {
fn message_event_deserialization() {
let json_data = json!({
"content": {
"m.text": "Voice message",
"org.matrix.msc1767.text": [
{ "body": "Voice message" },
],
"m.file": {
"url": "mxc://notareal.hs/abcdef",
"name": "voice_message.ogg",
@ -108,8 +112,8 @@ fn message_event_deserialization() {
assert!(ev.unsigned.is_empty());
let content = ev.content;
assert_eq!(content.message.find_plain(), Some("Voice message"));
assert_eq!(content.message.find_html(), None);
assert_eq!(content.text.find_plain(), Some("Voice message"));
assert_eq!(content.text.find_html(), None);
assert_eq!(content.file.url, "mxc://notareal.hs/abcdef");
assert_eq!(content.audio.duration, Some(Duration::from_millis(5_300)));
assert_matches!(content.audio.waveform, None);

View File

@ -117,6 +117,8 @@ unstable-extensible-events = [
"unstable-msc3246",
"unstable-msc3488",
"unstable-msc3553",
"unstable-msc3954",
"unstable-msc3955",
]
unstable-msc1767 = ["ruma-common/unstable-msc1767"]
unstable-msc2246 = ["ruma-client-api?/unstable-msc2246"]
@ -153,6 +155,9 @@ unstable-msc3618 = ["ruma-federation-api?/unstable-msc3618"]
unstable-msc3723 = ["ruma-federation-api?/unstable-msc3723"]
unstable-msc3931 = ["ruma-common/unstable-msc3931"]
unstable-msc3932 = ["ruma-common/unstable-msc3932"]
unstable-msc3954 = ["ruma-common/unstable-msc3954"]
unstable-msc3955 = ["ruma-common/unstable-msc3955"]
unstable-msc3956 = ["ruma-common/unstable-msc3956"]
unstable-pdu = ["ruma-common/unstable-pdu"]
unstable-sanitize = ["ruma-common/unstable-sanitize"]
unstable-unspecified = [
@ -190,6 +195,9 @@ __ci = [
"unstable-msc3618",
"unstable-msc3723",
"unstable-msc3932",
"unstable-msc3954",
"unstable-msc3955",
"unstable-msc3956",
]
[dependencies]