From 61c23491c6477920e2be91382b831a53037b262e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Thu, 5 Jan 2023 14:46:17 +0100 Subject: [PATCH] events: Update types according to changes in MSC3381 --- crates/ruma-common/src/events/enums.rs | 6 +- crates/ruma-common/src/events/message.rs | 6 +- crates/ruma-common/src/events/poll/end.rs | 97 +++++- .../ruma-common/src/events/poll/response.rs | 77 +++-- crates/ruma-common/src/events/poll/start.rs | 86 +++-- crates/ruma-common/tests/events/poll.rs | 307 ++++++++---------- 6 files changed, 338 insertions(+), 241 deletions(-) diff --git a/crates/ruma-common/src/events/enums.rs b/crates/ruma-common/src/events/enums.rs index 7a6961de..f3a88c38 100644 --- a/crates/ruma-common/src/events/enums.rs +++ b/crates/ruma-common/src/events/enums.rs @@ -72,13 +72,13 @@ event_enum! { "org.matrix.msc1767.message" => super::message, #[cfg(feature = "unstable-msc3381")] #[ruma_enum(alias = "m.poll.start")] - "org.matrix.msc3381.poll.start" => super::poll::start, + "org.matrix.msc3381.v2.poll.start" => super::poll::start, #[cfg(feature = "unstable-msc3381")] #[ruma_enum(alias = "m.poll.response")] - "org.matrix.msc3381.poll.response" => super::poll::response, + "org.matrix.msc3381.v2.poll.response" => super::poll::response, #[cfg(feature = "unstable-msc3381")] #[ruma_enum(alias = "m.poll.end")] - "org.matrix.msc3381.poll.end" => super::poll::end, + "org.matrix.msc3381.v2.poll.end" => super::poll::end, #[cfg(feature = "unstable-msc2677")] "m.reaction" => super::reaction, "m.room.encrypted" => super::room::encrypted, diff --git a/crates/ruma-common/src/events/message.rs b/crates/ruma-common/src/events/message.rs index 04a15c4a..1a13f0de 100644 --- a/crates/ruma-common/src/events/message.rs +++ b/crates/ruma-common/src/events/message.rs @@ -271,8 +271,8 @@ pub struct TextRepresentation { #[cfg(feature = "unstable-msc3554")] #[serde( rename = "org.matrix.msc3554.lang", - default = "Text::default_lang", - skip_serializing_if = "Text::is_default_lang" + default = "TextRepresentation::default_lang", + skip_serializing_if = "TextRepresentation::is_default_lang" )] pub lang: String, } @@ -316,10 +316,12 @@ impl TextRepresentation { mime == "text/plain" } + #[cfg(feature = "unstable-msc3554")] fn default_lang() -> String { "en".to_owned() } + #[cfg(feature = "unstable-msc3554")] fn is_default_lang(lang: &str) -> bool { lang == "en" } diff --git a/crates/ruma-common/src/events/poll/end.rs b/crates/ruma-common/src/events/poll/end.rs index 491a0ad5..dc8b3e2c 100644 --- a/crates/ruma-common/src/events/poll/end.rs +++ b/crates/ruma-common/src/events/poll/end.rs @@ -1,18 +1,43 @@ //! Types for the [`m.poll.end`] event. +use std::{ + collections::{btree_map, BTreeMap}, + ops::Deref, +}; + +use js_int::UInt; use ruma_macros::EventContent; use serde::{Deserialize, Serialize}; -use crate::{events::relation::Reference, OwnedEventId}; +use crate::{ + events::{message::TextContentBlock, relation::Reference}, + OwnedEventId, +}; /// The payload for a poll end event. #[derive(Clone, Debug, Serialize, Deserialize, EventContent)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] -#[ruma_event(type = "org.matrix.msc3381.poll.end", alias = "m.poll.end", kind = MessageLike)] +#[ruma_event(type = "org.matrix.msc3381.v2.poll.end", alias = "m.poll.end", kind = MessageLike)] pub struct PollEndEventContent { - /// The poll end content of the message. - #[serde(rename = "org.matrix.msc3381.poll.end", alias = "m.poll.end")] - pub poll_end: PollEndContent, + /// The text representation of the results. + #[serde(rename = "org.matrix.msc1767.text")] + pub text: TextContentBlock, + + /// The sender's perspective of the results. + #[serde( + rename = "org.matrix.msc3381.v2.poll.results", + skip_serializing_if = "Option::is_none" + )] + pub poll_results: Option, + + /// 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 the poll start event this responds to. #[serde(rename = "m.relates_to")] @@ -20,23 +45,63 @@ pub struct PollEndEventContent { } impl PollEndEventContent { - /// Creates a new `PollEndEventContent` that responds to the given poll start event ID, - /// with the given poll end content. - pub fn new(poll_end: PollEndContent, poll_start_id: OwnedEventId) -> Self { - Self { poll_end, relates_to: Reference::new(poll_start_id) } + /// Creates a new `PollEndEventContent` with the given fallback representation and + /// that responds to the given poll start event ID. + pub fn new(text: TextContentBlock, poll_start_id: OwnedEventId) -> Self { + Self { + text, + poll_results: None, + #[cfg(feature = "unstable-msc3955")] + automated: false, + relates_to: Reference::new(poll_start_id), + } + } + + /// Creates a new `PollEndEventContent` with the given plain text fallback representation and + /// that responds to the given poll start event ID. + pub fn with_plain_text(plain_text: impl Into, poll_start_id: OwnedEventId) -> Self { + Self { + text: TextContentBlock::plain(plain_text), + poll_results: None, + #[cfg(feature = "unstable-msc3955")] + automated: false, + relates_to: Reference::new(poll_start_id), + } } } -/// Poll end content. +/// A block for the results of a poll. /// -/// This is currently empty. +/// This is a map of answer ID to number of votes. #[derive(Clone, Debug, Default, Serialize, Deserialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] -pub struct PollEndContent {} +pub struct PollResultsContentBlock(BTreeMap); -impl PollEndContent { - /// Creates a new empty `PollEndContent`. - pub fn new() -> Self { - Self {} +impl From> for PollResultsContentBlock { + fn from(value: BTreeMap) -> Self { + Self(value) + } +} + +impl IntoIterator for PollResultsContentBlock { + type Item = (String, UInt); + type IntoIter = btree_map::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl FromIterator<(String, UInt)> for PollResultsContentBlock { + fn from_iter>(iter: T) -> Self { + Self(BTreeMap::from_iter(iter)) + } +} + +impl Deref for PollResultsContentBlock { + type Target = BTreeMap; + + fn deref(&self) -> &Self::Target { + &self.0 } } diff --git a/crates/ruma-common/src/events/poll/response.rs b/crates/ruma-common/src/events/poll/response.rs index 445ea417..5176358a 100644 --- a/crates/ruma-common/src/events/poll/response.rs +++ b/crates/ruma-common/src/events/poll/response.rs @@ -1,5 +1,7 @@ //! Types for the [`m.poll.response`] event. +use std::{ops::Deref, vec}; + use ruma_macros::EventContent; use serde::{Deserialize, Serialize}; @@ -8,11 +10,20 @@ use crate::{events::relation::Reference, OwnedEventId}; /// The payload for a poll response event. #[derive(Clone, Debug, Serialize, Deserialize, EventContent)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] -#[ruma_event(type = "org.matrix.msc3381.poll.response", alias = "m.poll.response", kind = MessageLike)] +#[ruma_event(type = "org.matrix.msc3381.v2.poll.response", alias = "m.poll.response", kind = MessageLike)] pub struct PollResponseEventContent { - /// The poll response content of the message. - #[serde(rename = "org.matrix.msc3381.poll.response", alias = "m.poll.response")] - pub poll_response: PollResponseContent, + /// The user's selection. + #[serde(rename = "org.matrix.msc3381.v2.selections")] + pub selections: SelectionsContentBlock, + + /// 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 the poll start event this responds to. #[serde(rename = "m.relates_to")] @@ -22,27 +33,53 @@ pub struct PollResponseEventContent { impl PollResponseEventContent { /// Creates a new `PollResponseEventContent` that responds to the given poll start event ID, /// with the given poll response content. - pub fn new(poll_response: PollResponseContent, poll_start_id: OwnedEventId) -> Self { - Self { poll_response, relates_to: Reference::new(poll_start_id) } + pub fn new(selections: SelectionsContentBlock, poll_start_id: OwnedEventId) -> Self { + Self { + selections, + #[cfg(feature = "unstable-msc3955")] + automated: false, + relates_to: Reference::new(poll_start_id), + } } } -/// Poll response content. +/// A block for selections content. #[derive(Clone, Debug, Serialize, Deserialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] -pub struct PollResponseContent { - /// The IDs of the selected answers of the poll. - /// - /// It should be truncated to `max_selections` from the related poll start event. - /// - /// If this is an empty array or includes unknown IDs, this vote should be considered as - /// spoiled. - pub answers: Vec, -} +pub struct SelectionsContentBlock(Vec); -impl PollResponseContent { - /// Creates a new `PollResponseContent` with the given answers. - pub fn new(answers: Vec) -> Self { - Self { answers } +impl SelectionsContentBlock { + /// Whether this `SelectionsContentBlock` is empty. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl From> for SelectionsContentBlock { + fn from(value: Vec) -> Self { + Self(value) + } +} + +impl IntoIterator for SelectionsContentBlock { + type Item = String; + type IntoIter = vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl FromIterator for SelectionsContentBlock { + fn from_iter>(iter: T) -> Self { + Self(Vec::from_iter(iter)) + } +} + +impl Deref for SelectionsContentBlock { + type Target = [String]; + + fn deref(&self) -> &Self::Target { + &self.0 } } diff --git a/crates/ruma-common/src/events/poll/start.rs b/crates/ruma-common/src/events/poll/start.rs index f1d42569..54693fd8 100644 --- a/crates/ruma-common/src/events/poll/start.rs +++ b/crates/ruma-common/src/events/poll/start.rs @@ -1,5 +1,7 @@ //! Types for the [`m.poll.start`] event. +use std::ops::Deref; + use js_int::{uint, UInt}; use ruma_macros::EventContent; use serde::{Deserialize, Serialize}; @@ -13,37 +15,59 @@ use crate::{events::message::TextContentBlock, serde::StringEnum, PrivOwnedStr}; /// The payload for a poll start event. #[derive(Clone, Debug, Serialize, Deserialize, EventContent)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] -#[ruma_event(type = "org.matrix.msc3381.poll.start", alias = "m.poll.start", kind = MessageLike)] +#[ruma_event(type = "org.matrix.msc3381.v2.poll.start", alias = "m.poll.start", kind = MessageLike)] pub struct PollStartEventContent { - /// The poll start content of the message. - #[serde(rename = "org.matrix.msc3381.poll.start", alias = "m.poll.start")] - pub poll_start: PollStartContent, + /// The poll content of the message. + #[serde(rename = "org.matrix.msc3381.v2.poll")] + pub poll: PollContentBlock, - /// Optional fallback text representation of the message, for clients that don't support polls. - #[serde( - rename = "org.matrix.msc1767.text", - default, - skip_serializing_if = "TextContentBlock::is_empty" - )] + /// Text representation of the message, for clients that don't support polls. + #[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, } impl PollStartEventContent { - /// Creates a new `PollStartEventContent` with the given poll start content. - pub fn new(poll_start: PollStartContent) -> Self { - Self { poll_start, text: Default::default() } + /// Creates a new `PollStartEventContent` with the given fallback representation and poll + /// content. + pub fn new(text: TextContentBlock, poll: PollContentBlock) -> Self { + Self { + poll, + text, + #[cfg(feature = "unstable-msc3955")] + automated: false, + } + } + + /// Creates a new `PollStartEventContent` with the given plain text fallback + /// representation and poll content. + pub fn with_plain_text(plain_text: impl Into, poll: PollContentBlock) -> Self { + Self { + poll, + text: TextContentBlock::plain(plain_text), + #[cfg(feature = "unstable-msc3955")] + automated: false, + } } } -/// Poll start content. +/// A block for poll content. #[derive(Clone, Debug, Serialize, Deserialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] -pub struct PollStartContent { +pub struct PollContentBlock { /// The question of the poll. pub question: PollQuestion, /// The kind of the poll. - #[serde(default)] + #[serde(default, skip_serializing_if = "crate::serde::is_default")] pub kind: PollKind, /// The maximum number of responses a user is able to select. @@ -52,8 +76,8 @@ pub struct PollStartContent { /// /// Defaults to `1`. #[serde( - default = "PollStartContent::default_max_selections", - skip_serializing_if = "PollStartContent::max_selections_is_default" + default = "PollContentBlock::default_max_selections", + skip_serializing_if = "PollContentBlock::max_selections_is_default" )] pub max_selections: UInt, @@ -61,12 +85,12 @@ pub struct PollStartContent { pub answers: PollAnswers, } -impl PollStartContent { - /// Creates a new `PollStartContent` with the given question, kind, and answers. - pub fn new(question: TextContentBlock, kind: PollKind, answers: PollAnswers) -> Self { +impl PollContentBlock { + /// Creates a new `PollStartContent` with the given question and answers. + pub fn new(question: TextContentBlock, answers: PollAnswers) -> Self { Self { question: question.into(), - kind, + kind: Default::default(), max_selections: Self::default_max_selections(), answers, } @@ -103,11 +127,11 @@ impl From for PollQuestion { pub enum PollKind { /// The results are revealed once the poll is closed. #[default] - #[ruma_enum(rename = "org.matrix.msc3381.poll.undisclosed", alias = "m.poll.undisclosed")] + #[ruma_enum(rename = "org.matrix.msc3381.v2.undisclosed")] Undisclosed, /// The votes are visible up until and including when the poll is closed. - #[ruma_enum(rename = "org.matrix.msc3381.poll.disclosed", alias = "m.poll.disclosed")] + #[ruma_enum(rename = "org.matrix.msc3381.v2.disclosed")] Disclosed, #[doc(hidden)] @@ -129,11 +153,6 @@ impl PollAnswers { /// The largest number of values contained in a `PollAnswers`. pub const MAX_LENGTH: usize = 20; - - /// The answers of this `PollAnswers`. - pub fn answers(&self) -> &[PollAnswer] { - &self.0 - } } /// An error encountered when trying to convert to a `PollAnswers`. @@ -170,6 +189,14 @@ impl TryFrom<&[PollAnswer]> for PollAnswers { } } +impl Deref for PollAnswers { + type Target = [PollAnswer]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + /// Poll answer. #[derive(Clone, Debug, Serialize, Deserialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] @@ -177,6 +204,7 @@ pub struct PollAnswer { /// The ID of the answer. /// /// This must be unique among the answers of a poll. + #[serde(rename = "org.matrix.msc3381.v2.id")] pub id: String, /// The text representation of the answer. diff --git a/crates/ruma-common/tests/events/poll.rs b/crates/ruma-common/tests/events/poll.rs index 296c86fd..97966f0f 100644 --- a/crates/ruma-common/tests/events/poll.rs +++ b/crates/ruma-common/tests/events/poll.rs @@ -1,17 +1,18 @@ #![cfg(feature = "unstable-msc3381")] +use std::collections::BTreeMap; + use assert_matches::assert_matches; -use assign::assign; use js_int::uint; use ruma_common::{ event_id, events::{ message::TextContentBlock, poll::{ - end::{PollEndContent, PollEndEventContent}, - response::{PollResponseContent, PollResponseEventContent}, + end::PollEndEventContent, + response::PollResponseEventContent, start::{ - PollAnswer, PollAnswers, PollAnswersError, PollKind, PollStartContent, + PollAnswer, PollAnswers, PollAnswersError, PollContentBlock, PollKind, PollStartEventContent, }, }, @@ -24,43 +25,43 @@ 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", "org.matrix.msc1767.text": [{ "body": "First answer" }] }, - { "id": "bbb", "org.matrix.msc1767.text": [{ "body": "Second answer" }] }, + { "org.matrix.msc3381.v2.id": "aaa", "org.matrix.msc1767.text": [{ "body": "First answer" }] }, + { "org.matrix.msc3381.v2.id": "bbb", "org.matrix.msc1767.text": [{ "body": "Second answer" }] }, ]); let answers = from_json_value::(json_data).unwrap(); - assert_eq!(answers.answers().len(), 2); + assert_eq!(answers.len(), 2); } #[test] fn poll_answers_deserialization_truncate() { let json_data = json!([ - { "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" }] }, + { "org.matrix.msc3381.v2.id": "aaa", "org.matrix.msc1767.text": [{ "body": "1st answer" }] }, + { "org.matrix.msc3381.v2.id": "bbb", "org.matrix.msc1767.text": [{ "body": "2nd answer" }] }, + { "org.matrix.msc3381.v2.id": "ccc", "org.matrix.msc1767.text": [{ "body": "3rd answer" }] }, + { "org.matrix.msc3381.v2.id": "ddd", "org.matrix.msc1767.text": [{ "body": "4th answer" }] }, + { "org.matrix.msc3381.v2.id": "eee", "org.matrix.msc1767.text": [{ "body": "5th answer" }] }, + { "org.matrix.msc3381.v2.id": "fff", "org.matrix.msc1767.text": [{ "body": "6th answer" }] }, + { "org.matrix.msc3381.v2.id": "ggg", "org.matrix.msc1767.text": [{ "body": "7th answer" }] }, + { "org.matrix.msc3381.v2.id": "hhh", "org.matrix.msc1767.text": [{ "body": "8th answer" }] }, + { "org.matrix.msc3381.v2.id": "iii", "org.matrix.msc1767.text": [{ "body": "9th answer" }] }, + { "org.matrix.msc3381.v2.id": "jjj", "org.matrix.msc1767.text": [{ "body": "10th answer" }] }, + { "org.matrix.msc3381.v2.id": "kkk", "org.matrix.msc1767.text": [{ "body": "11th answer" }] }, + { "org.matrix.msc3381.v2.id": "lll", "org.matrix.msc1767.text": [{ "body": "12th answer" }] }, + { "org.matrix.msc3381.v2.id": "mmm", "org.matrix.msc1767.text": [{ "body": "13th answer" }] }, + { "org.matrix.msc3381.v2.id": "nnn", "org.matrix.msc1767.text": [{ "body": "14th answer" }] }, + { "org.matrix.msc3381.v2.id": "ooo", "org.matrix.msc1767.text": [{ "body": "15th answer" }] }, + { "org.matrix.msc3381.v2.id": "ppp", "org.matrix.msc1767.text": [{ "body": "16th answer" }] }, + { "org.matrix.msc3381.v2.id": "qqq", "org.matrix.msc1767.text": [{ "body": "17th answer" }] }, + { "org.matrix.msc3381.v2.id": "rrr", "org.matrix.msc1767.text": [{ "body": "18th answer" }] }, + { "org.matrix.msc3381.v2.id": "sss", "org.matrix.msc1767.text": [{ "body": "19th answer" }] }, + { "org.matrix.msc3381.v2.id": "ttt", "org.matrix.msc1767.text": [{ "body": "20th answer" }] }, + { "org.matrix.msc3381.v2.id": "uuu", "org.matrix.msc1767.text": [{ "body": "21th answer" }] }, + { "org.matrix.msc3381.v2.id": "vvv", "org.matrix.msc1767.text": [{ "body": "22th answer" }] }, ]); let answers = from_json_value::(json_data).unwrap(); - assert_eq!(answers.answers().len(), 20); + assert_eq!(answers.len(), 20); } #[test] @@ -74,40 +75,10 @@ fn poll_answers_deserialization_not_enough() { #[test] fn start_content_serialization() { - let event_content = PollStartEventContent::new(PollStartContent::new( - TextContentBlock::plain("How's the weather?"), - PollKind::Undisclosed, - vec![ - 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(), - )); - - assert_eq!( - to_json_value(&event_content).unwrap(), - json!({ - "org.matrix.msc3381.poll.start": { - "question": { "org.matrix.msc1767.text": [{ "body": "How's the weather?" }] }, - "kind": "org.matrix.msc3381.poll.undisclosed", - "answers": [ - { "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!" }] }, - ], - }, - }) - ); -} - -#[test] -fn start_event_serialization() { - let content = PollStartEventContent::new(assign!( - PollStartContent::new( + let event_content = PollStartEventContent::with_plain_text( + "How's the weather?\n1. Not bad…\n2. Fine.\n3. Amazing!", + PollContentBlock::new( TextContentBlock::plain("How's the weather?"), - PollKind::Disclosed, vec![ PollAnswer::new("not-bad".to_owned(), TextContentBlock::plain("Not bad…")), PollAnswer::new("fine".to_owned(), TextContentBlock::plain("Fine.")), @@ -116,20 +87,59 @@ fn start_event_serialization() { .try_into() .unwrap(), ), - { max_selections: uint!(2) } - )); + ); + + assert_eq!( + to_json_value(&event_content).unwrap(), + json!({ + "org.matrix.msc1767.text": [ + { "body": "How's the weather?\n1. Not bad…\n2. Fine.\n3. Amazing!" } + ], + "org.matrix.msc3381.v2.poll": { + "question": { "org.matrix.msc1767.text": [{ "body": "How's the weather?" }] }, + "answers": [ + { "org.matrix.msc3381.v2.id": "not-bad", "org.matrix.msc1767.text": [{ "body": "Not bad…" }] }, + { "org.matrix.msc3381.v2.id": "fine", "org.matrix.msc1767.text": [{ "body": "Fine." }] }, + { "org.matrix.msc3381.v2.id": "amazing", "org.matrix.msc1767.text": [{ "body": "Amazing!" }] }, + ], + }, + }) + ); +} + +#[test] +fn start_event_serialization() { + let mut poll = PollContentBlock::new( + TextContentBlock::plain("How's the weather?"), + vec![ + 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(), + ); + poll.kind = PollKind::Disclosed; + poll.max_selections = uint!(2); + let content = PollStartEventContent::with_plain_text( + "How's the weather?\n1. Not bad…\n2. Fine.\n3. Amazing!", + poll, + ); assert_eq!( to_json_value(&content).unwrap(), json!({ - "org.matrix.msc3381.poll.start": { + "org.matrix.msc1767.text": [ + { "body": "How's the weather?\n1. Not bad…\n2. Fine.\n3. Amazing!" } + ], + "org.matrix.msc3381.v2.poll": { "question": { "org.matrix.msc1767.text": [{ "body": "How's the weather?" }] }, - "kind": "org.matrix.msc3381.poll.disclosed", + "kind": "org.matrix.msc3381.v2.disclosed", "max_selections": 2, "answers": [ - { "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!" }] }, + { "org.matrix.msc3381.v2.id": "not-bad", "org.matrix.msc1767.text": [{ "body": "Not bad…" }] }, + { "org.matrix.msc3381.v2.id": "fine", "org.matrix.msc1767.text": [{ "body": "Fine." }] }, + { "org.matrix.msc3381.v2.id": "amazing", "org.matrix.msc1767.text": [{ "body": "Amazing!" }] }, ] }, }) @@ -140,14 +150,16 @@ fn start_event_serialization() { fn start_event_deserialization() { let json_data = json!({ "content": { - "org.matrix.msc3381.poll.start": { + "org.matrix.msc1767.text": [ + { "body": "How's the weather?\n1. Not bad…\n2. Fine.\n3. Amazing!" } + ], + "org.matrix.msc3381.v2.poll": { "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": [{ "body": "Not bad…" }] }, - { "id": "fine", "org.matrix.msc1767.text": [{ "body": "Fine." }] }, - { "id": "amazing", "org.matrix.msc1767.text": [{ "body": "Amazing!" }] }, + { "org.matrix.msc3381.v2.id": "not-bad", "org.matrix.msc1767.text": [{ "body": "Not bad…" }] }, + { "org.matrix.msc3381.v2.id": "fine", "org.matrix.msc1767.text": [{ "body": "Fine." }] }, + { "org.matrix.msc3381.v2.id": "amazing", "org.matrix.msc1767.text": [{ "body": "Amazing!" }] }, ] }, }, @@ -155,7 +167,7 @@ fn start_event_deserialization() { "origin_server_ts": 134_829_848, "room_id": "!roomid:notareal.hs", "sender": "@user:notareal.hs", - "type": "org.matrix.msc3381.poll.start", + "type": "org.matrix.msc3381.v2.poll.start", }); let event = from_json_value::(json_data).unwrap(); @@ -163,11 +175,15 @@ fn start_event_deserialization() { event, AnyMessageLikeEvent::PollStart(MessageLikeEvent::Original(message_event)) => message_event ); - let poll_start = message_event.content.poll_start; - 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!( + message_event.content.text[0].body, + "How's the weather?\n1. Not bad…\n2. Fine.\n3. Amazing!" + ); + let poll = message_event.content.poll; + assert_eq!(poll.question.text[0].body, "How's the weather?"); + assert_eq!(poll.kind, PollKind::Undisclosed); + assert_eq!(poll.max_selections, uint!(2)); + let answers = poll.answers; assert_eq!(answers.len(), 3); assert_eq!(answers[0].id, "not-bad"); assert_eq!(answers[0].text[0].body, "Not bad…"); @@ -180,16 +196,14 @@ fn start_event_deserialization() { #[test] fn response_content_serialization() { let event_content = PollResponseEventContent::new( - PollResponseContent::new(vec!["my-answer".to_owned()]), + vec!["my-answer".to_owned()].into(), event_id!("$related_event:notareal.hs").to_owned(), ); assert_eq!( to_json_value(&event_content).unwrap(), json!({ - "org.matrix.msc3381.poll.response": { - "answers": ["my-answer"], - }, + "org.matrix.msc3381.v2.selections": ["my-answer"], "m.relates_to": { "rel_type": "m.reference", "event_id": "$related_event:notareal.hs", @@ -201,16 +215,14 @@ fn response_content_serialization() { #[test] fn response_event_serialization() { let content = PollResponseEventContent::new( - PollResponseContent::new(vec!["first-answer".to_owned(), "second-answer".to_owned()]), + vec!["first-answer".to_owned(), "second-answer".to_owned()].into(), event_id!("$related_event:notareal.hs").to_owned(), ); assert_eq!( to_json_value(&content).unwrap(), json!({ - "org.matrix.msc3381.poll.response": { - "answers": ["first-answer", "second-answer"], - }, + "org.matrix.msc3381.v2.selections": ["first-answer", "second-answer"], "m.relates_to": { "rel_type": "m.reference", "event_id": "$related_event:notareal.hs", @@ -220,12 +232,10 @@ fn response_event_serialization() { } #[test] -fn response_event_unstable_deserialization() { +fn response_event_deserialization() { let json_data = json!({ "content": { - "org.matrix.msc3381.poll.response": { - "answers": ["my-answer"], - }, + "org.matrix.msc3381.v2.selections": ["my-answer"], "m.relates_to": { "rel_type": "m.reference", "event_id": "$related_event:notareal.hs", @@ -235,7 +245,7 @@ fn response_event_unstable_deserialization() { "origin_server_ts": 134_829_848, "room_id": "!roomid:notareal.hs", "sender": "@user:notareal.hs", - "type": "org.matrix.msc3381.poll.response", + "type": "org.matrix.msc3381.v2.poll.response", }); let event = from_json_value::(json_data).unwrap(); @@ -244,45 +254,9 @@ fn response_event_unstable_deserialization() { AnyMessageLikeEvent::PollResponse(MessageLikeEvent::Original(message_event)) => message_event ); - let answers = message_event.content.poll_response.answers; - assert_eq!(answers.len(), 1); - assert_eq!(answers[0], "my-answer"); - let event_id = assert_matches!( - message_event.content.relates_to, - Reference { event_id, .. } => event_id - ); - assert_eq!(event_id, "$related_event:notareal.hs"); -} - -#[test] -fn response_event_stable_deserialization() { - let json_data = json!({ - "content": { - "m.poll.response": { - "answers": ["first-answer", "second-answer"], - }, - "m.relates_to": { - "rel_type": "m.reference", - "event_id": "$related_event:notareal.hs", - } - }, - "event_id": "$event:notareal.hs", - "origin_server_ts": 134_829_848, - "room_id": "!roomid:notareal.hs", - "sender": "@user:notareal.hs", - "type": "m.poll.response", - }); - - let event = from_json_value::(json_data).unwrap(); - let message_event = assert_matches!( - event, - AnyMessageLikeEvent::PollResponse(MessageLikeEvent::Original(message_event)) - => message_event - ); - let answers = message_event.content.poll_response.answers; - assert_eq!(answers.len(), 2); - assert_eq!(answers[0], "first-answer"); - assert_eq!(answers[1], "second-answer"); + let selections = message_event.content.selections; + assert_eq!(selections.len(), 1); + assert_eq!(selections[0], "my-answer"); let event_id = assert_matches!( message_event.content.relates_to, Reference { event_id, .. } => event_id @@ -292,15 +266,17 @@ fn response_event_stable_deserialization() { #[test] fn end_content_serialization() { - let event_content = PollEndEventContent::new( - PollEndContent::new(), + let event_content = PollEndEventContent::with_plain_text( + "The poll has closed. Top answer: Amazing!", event_id!("$related_event:notareal.hs").to_owned(), ); assert_eq!( to_json_value(&event_content).unwrap(), json!({ - "org.matrix.msc3381.poll.end": {}, + "org.matrix.msc1767.text": [ + { "body": "The poll has closed. Top answer: Amazing!" } + ], "m.relates_to": { "rel_type": "m.reference", "event_id": "$related_event:notareal.hs", @@ -311,15 +287,30 @@ fn end_content_serialization() { #[test] fn end_event_serialization() { - let content = PollEndEventContent::new( - PollEndContent::new(), + let mut content = PollEndEventContent::with_plain_text( + "The poll has closed. Top answer: Amazing!", event_id!("$related_event:notareal.hs").to_owned(), ); + content.poll_results = Some( + BTreeMap::from([ + ("not-bad".to_owned(), uint!(1)), + ("fine".to_owned(), uint!(5)), + ("amazing".to_owned(), uint!(14)), + ]) + .into(), + ); assert_eq!( to_json_value(&content).unwrap(), json!({ - "org.matrix.msc3381.poll.end": {}, + "org.matrix.msc1767.text": [ + { "body": "The poll has closed. Top answer: Amazing!" }, + ], + "org.matrix.msc3381.v2.poll.results": { + "not-bad": 1, + "fine": 5, + "amazing": 14, + }, "m.relates_to": { "rel_type": "m.reference", "event_id": "$related_event:notareal.hs", @@ -329,10 +320,12 @@ fn end_event_serialization() { } #[test] -fn end_event_unstable_deserialization() { +fn end_event_deserialization() { let json_data = json!({ "content": { - "org.matrix.msc3381.poll.end": {}, + "org.matrix.msc1767.text": [ + { "body": "The poll has closed. Top answer: Amazing!" }, + ], "m.relates_to": { "rel_type": "m.reference", "event_id": "$related_event:notareal.hs", @@ -342,36 +335,7 @@ fn end_event_unstable_deserialization() { "origin_server_ts": 134_829_848, "room_id": "!roomid:notareal.hs", "sender": "@user:notareal.hs", - "type": "org.matrix.msc3381.poll.end", - }); - - let event = from_json_value::(json_data).unwrap(); - let message_event = assert_matches!( - event, - AnyMessageLikeEvent::PollEnd(MessageLikeEvent::Original(message_event)) => message_event - ); - let event_id = assert_matches!( - message_event.content.relates_to, - Reference { event_id, .. } => event_id - ); - assert_eq!(event_id, "$related_event:notareal.hs"); -} - -#[test] -fn end_event_stable_deserialization() { - let json_data = json!({ - "content": { - "m.poll.end": {}, - "m.relates_to": { - "rel_type": "m.reference", - "event_id": "$related_event:notareal.hs", - } - }, - "event_id": "$event:notareal.hs", - "origin_server_ts": 134_829_848, - "room_id": "!roomid:notareal.hs", - "sender": "@user:notareal.hs", - "type": "m.poll.end", + "type": "org.matrix.msc3381.v2.poll.end", }); let event = from_json_value::(json_data).unwrap(); @@ -379,6 +343,7 @@ fn end_event_stable_deserialization() { event, AnyMessageLikeEvent::PollEnd(MessageLikeEvent::Original(message_event)) => message_event ); + assert_eq!(message_event.content.text[0].body, "The poll has closed. Top answer: Amazing!"); let event_id = assert_matches!( message_event.content.relates_to, Reference { event_id, .. } => event_id