events: Add support for polls
According to MSC3381
This commit is contained in:
parent
e50d59f7a4
commit
c3d573e943
@ -14,6 +14,7 @@ Improvements:
|
||||
* All push rules are now considered to not apply to events sent by the user themselves
|
||||
* Change `events::relation::BundledAnnotation` to a struct instead of an enum
|
||||
* Remove `BundledReaction`
|
||||
* Add unstable support for polls (MSC3381)
|
||||
|
||||
# 0.9.2
|
||||
|
||||
|
@ -37,6 +37,7 @@ unstable-msc2676 = []
|
||||
unstable-msc2677 = []
|
||||
unstable-msc3245 = ["unstable-msc3246"]
|
||||
unstable-msc3246 = ["unstable-msc3551", "thiserror"]
|
||||
unstable-msc3381 = ["unstable-msc1767"]
|
||||
unstable-msc3440 = []
|
||||
unstable-msc3488 = ["unstable-msc1767"]
|
||||
unstable-msc3551 = ["unstable-msc1767"]
|
||||
|
@ -146,6 +146,8 @@ pub mod notice;
|
||||
#[cfg(feature = "unstable-pdu")]
|
||||
pub mod pdu;
|
||||
pub mod policy;
|
||||
#[cfg(feature = "unstable-msc3381")]
|
||||
pub mod poll;
|
||||
pub mod presence;
|
||||
pub mod push_rules;
|
||||
#[cfg(feature = "unstable-msc2677")]
|
||||
|
@ -62,6 +62,15 @@ event_enum! {
|
||||
"m.message" => super::message,
|
||||
#[cfg(feature = "unstable-msc1767")]
|
||||
"m.notice" => super::notice,
|
||||
#[cfg(feature = "unstable-msc3381")]
|
||||
#[ruma_enum(alias = "m.poll.start")]
|
||||
"org.matrix.msc3381.poll.start" => super::poll::start,
|
||||
#[cfg(feature = "unstable-msc3381")]
|
||||
#[ruma_enum(alias = "m.poll.response")]
|
||||
"org.matrix.msc3381.poll.response" => super::poll::response,
|
||||
#[cfg(feature = "unstable-msc3381")]
|
||||
#[ruma_enum(alias = "m.poll.end")]
|
||||
"org.matrix.msc3381.poll.end" => super::poll::end,
|
||||
#[cfg(feature = "unstable-msc2677")]
|
||||
"m.reaction" => super::reaction,
|
||||
"m.room.encrypted" => super::room::encrypted,
|
||||
@ -280,6 +289,8 @@ impl AnyMessageLikeEventContent {
|
||||
mac::KeyVerificationMacEventContent, ready::KeyVerificationReadyEventContent,
|
||||
start::KeyVerificationStartEventContent,
|
||||
};
|
||||
#[cfg(feature = "unstable-msc3381")]
|
||||
use super::poll::{end::PollEndEventContent, response::PollResponseEventContent};
|
||||
|
||||
match self {
|
||||
#[rustfmt::skip]
|
||||
@ -325,6 +336,16 @@ impl AnyMessageLikeEventContent {
|
||||
Self::Image(ev) => ev.relates_to.clone().map(Into::into),
|
||||
#[cfg(feature = "unstable-msc3553")]
|
||||
Self::Video(ev) => ev.relates_to.clone().map(Into::into),
|
||||
#[cfg(feature = "unstable-msc3381")]
|
||||
Self::PollResponse(PollResponseEventContent { relates_to, .. })
|
||||
| Self::PollEnd(PollEndEventContent { relates_to, .. }) => {
|
||||
let super::poll::ReferenceRelation { event_id } = relates_to;
|
||||
Some(encrypted::Relation::Reference(encrypted::Reference {
|
||||
event_id: event_id.clone(),
|
||||
}))
|
||||
}
|
||||
#[cfg(feature = "unstable-msc3381")]
|
||||
Self::PollStart(_) => None,
|
||||
Self::CallAnswer(_)
|
||||
| Self::CallInvite(_)
|
||||
| Self::CallHangup(_)
|
||||
|
29
crates/ruma-common/src/events/poll.rs
Normal file
29
crates/ruma-common/src/events/poll.rs
Normal file
@ -0,0 +1,29 @@
|
||||
//! Modules for events in the `m.poll` namespace ([MSC3381]).
|
||||
//!
|
||||
//! This module also contains types shared by events in its child namespaces.
|
||||
//!
|
||||
//! [MSC3381]: https://github.com/matrix-org/matrix-spec-proposals/pull/3381
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::OwnedEventId;
|
||||
|
||||
pub mod end;
|
||||
pub mod response;
|
||||
pub mod start;
|
||||
|
||||
/// An `m.reference` relation.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
#[serde(tag = "rel_type", rename = "m.reference")]
|
||||
pub struct ReferenceRelation {
|
||||
/// The ID of the event this references.
|
||||
pub event_id: OwnedEventId,
|
||||
}
|
||||
|
||||
impl ReferenceRelation {
|
||||
/// Creates a new `ReferenceRelation` that references the given event ID.
|
||||
pub fn new(event_id: OwnedEventId) -> Self {
|
||||
Self { event_id }
|
||||
}
|
||||
}
|
43
crates/ruma-common/src/events/poll/end.rs
Normal file
43
crates/ruma-common/src/events/poll/end.rs
Normal file
@ -0,0 +1,43 @@
|
||||
//! Types for the [`m.poll.end`] event.
|
||||
|
||||
use ruma_macros::EventContent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::ReferenceRelation;
|
||||
use crate::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)]
|
||||
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,
|
||||
|
||||
/// Information about the poll start event this responds to.
|
||||
#[serde(rename = "m.relates_to")]
|
||||
pub relates_to: ReferenceRelation,
|
||||
}
|
||||
|
||||
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: ReferenceRelation::new(poll_start_id) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Poll end content.
|
||||
///
|
||||
/// This is currently empty.
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct PollEndContent {}
|
||||
|
||||
impl PollEndContent {
|
||||
/// Creates a new empty `PollEndContent`.
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
49
crates/ruma-common/src/events/poll/response.rs
Normal file
49
crates/ruma-common/src/events/poll/response.rs
Normal file
@ -0,0 +1,49 @@
|
||||
//! Types for the [`m.poll.response`] event.
|
||||
|
||||
use ruma_macros::EventContent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::ReferenceRelation;
|
||||
use crate::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)]
|
||||
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,
|
||||
|
||||
/// Information about the poll start event this responds to.
|
||||
#[serde(rename = "m.relates_to")]
|
||||
pub relates_to: ReferenceRelation,
|
||||
}
|
||||
|
||||
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: ReferenceRelation::new(poll_start_id) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Poll response 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<String>,
|
||||
}
|
||||
|
||||
impl PollResponseContent {
|
||||
/// Creates a new `PollResponseContent` with the given answers.
|
||||
pub fn new(answers: Vec<String>) -> Self {
|
||||
Self { answers }
|
||||
}
|
||||
}
|
175
crates/ruma-common/src/events/poll/start.rs
Normal file
175
crates/ruma-common/src/events/poll/start.rs
Normal file
@ -0,0 +1,175 @@
|
||||
//! Types for the [`m.poll.start`] event.
|
||||
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use js_int::{uint, UInt};
|
||||
use ruma_macros::EventContent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
mod poll_answers_serde;
|
||||
|
||||
use poll_answers_serde::PollAnswersDeHelper;
|
||||
|
||||
use crate::{events::message::MessageContent, 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)]
|
||||
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,
|
||||
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
impl PollStartEventContent {
|
||||
/// Creates a new `PollStartEventContent` with the given poll start content.
|
||||
pub fn new(poll_start: PollStartContent) -> Self {
|
||||
Self { poll_start, message: None }
|
||||
}
|
||||
}
|
||||
|
||||
/// Poll start content.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct PollStartContent {
|
||||
/// The question of the poll.
|
||||
pub question: MessageContent,
|
||||
|
||||
/// The kind of the poll.
|
||||
#[serde(default)]
|
||||
pub kind: PollKind,
|
||||
|
||||
/// The maximum number of responses a user is able to select.
|
||||
///
|
||||
/// Must be greater or equal to `1`.
|
||||
///
|
||||
/// Defaults to `1`.
|
||||
#[serde(
|
||||
default = "PollStartContent::default_max_selections",
|
||||
skip_serializing_if = "PollStartContent::max_selections_is_default"
|
||||
)]
|
||||
pub max_selections: UInt,
|
||||
|
||||
/// The possible answers to the poll.
|
||||
pub answers: PollAnswers,
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
fn default_max_selections() -> UInt {
|
||||
uint!(1)
|
||||
}
|
||||
|
||||
fn max_selections_is_default(max_selections: &UInt) -> bool {
|
||||
max_selections == &Self::default_max_selections()
|
||||
}
|
||||
}
|
||||
|
||||
/// The kind of poll.
|
||||
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, StringEnum)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub enum PollKind {
|
||||
/// The results are revealed once the poll is closed.
|
||||
#[ruma_enum(rename = "org.matrix.msc3381.poll.undisclosed", alias = "m.poll.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")]
|
||||
Disclosed,
|
||||
|
||||
#[doc(hidden)]
|
||||
_Custom(PrivOwnedStr),
|
||||
}
|
||||
|
||||
impl Default for PollKind {
|
||||
fn default() -> Self {
|
||||
Self::Undisclosed
|
||||
}
|
||||
}
|
||||
|
||||
/// The answers to a poll.
|
||||
///
|
||||
/// Must include between 1 and 20 `PollAnswer`s.
|
||||
///
|
||||
/// To build this, use the `TryFrom` implementations.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(try_from = "PollAnswersDeHelper")]
|
||||
pub struct PollAnswers(Vec<PollAnswer>);
|
||||
|
||||
impl PollAnswers {
|
||||
/// The smallest number of values contained in a `PollAnswers`.
|
||||
pub const MIN_LENGTH: usize = 1;
|
||||
|
||||
/// 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`.
|
||||
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, thiserror::Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum PollAnswersError {
|
||||
/// There are more than [`PollAnswers::MAX_LENGTH`] values.
|
||||
#[error("too many values")]
|
||||
TooManyValues,
|
||||
/// There are less that [`PollAnswers::MIN_LENGTH`] values.
|
||||
#[error("not enough values")]
|
||||
NotEnoughValues,
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<PollAnswer>> for PollAnswers {
|
||||
type Error = PollAnswersError;
|
||||
|
||||
fn try_from(value: Vec<PollAnswer>) -> Result<Self, Self::Error> {
|
||||
if value.len() < Self::MIN_LENGTH {
|
||||
Err(PollAnswersError::NotEnoughValues)
|
||||
} else if value.len() > Self::MAX_LENGTH {
|
||||
Err(PollAnswersError::TooManyValues)
|
||||
} else {
|
||||
Ok(Self(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[PollAnswer]> for PollAnswers {
|
||||
type Error = PollAnswersError;
|
||||
|
||||
fn try_from(value: &[PollAnswer]) -> Result<Self, Self::Error> {
|
||||
Self::try_from(value.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
/// Poll answer.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct PollAnswer {
|
||||
/// The ID of the answer.
|
||||
///
|
||||
/// This must be unique among the answers of a poll.
|
||||
pub id: String,
|
||||
|
||||
/// The text representation of the answer.
|
||||
#[serde(flatten)]
|
||||
pub answer: MessageContent,
|
||||
}
|
||||
|
||||
impl PollAnswer {
|
||||
/// Creates a new `PollAnswer` with the given id and text representation.
|
||||
pub fn new(id: String, answer: MessageContent) -> Self {
|
||||
Self { id, answer }
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
//! `Serialize` and `Deserialize` implementations for extensible events (MSC1767).
|
||||
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::{PollAnswer, PollAnswers, PollAnswersError};
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub(crate) struct PollAnswersDeHelper(Vec<PollAnswer>);
|
||||
|
||||
impl TryFrom<PollAnswersDeHelper> for PollAnswers {
|
||||
type Error = PollAnswersError;
|
||||
|
||||
fn try_from(helper: PollAnswersDeHelper) -> Result<Self, Self::Error> {
|
||||
let mut answers = helper.0;
|
||||
answers.truncate(PollAnswers::MAX_LENGTH);
|
||||
PollAnswers::try_from(answers)
|
||||
}
|
||||
}
|
@ -42,6 +42,19 @@ impl Ruleset {
|
||||
ConditionalPushRule::tombstone(),
|
||||
ConditionalPushRule::roomnotif(),
|
||||
],
|
||||
#[cfg(feature = "unstable-msc3381")]
|
||||
underride: indexset![
|
||||
ConditionalPushRule::call(),
|
||||
ConditionalPushRule::encrypted_room_one_to_one(),
|
||||
ConditionalPushRule::room_one_to_one(),
|
||||
ConditionalPushRule::message(),
|
||||
ConditionalPushRule::encrypted(),
|
||||
ConditionalPushRule::poll_start_one_to_one(),
|
||||
ConditionalPushRule::poll_start(),
|
||||
ConditionalPushRule::poll_end_one_to_one(),
|
||||
ConditionalPushRule::poll_end(),
|
||||
],
|
||||
#[cfg(not(feature = "unstable-msc3381"))]
|
||||
underride: indexset![
|
||||
ConditionalPushRule::call(),
|
||||
ConditionalPushRule::encrypted_room_one_to_one(),
|
||||
@ -274,4 +287,66 @@ impl ConditionalPushRule {
|
||||
actions: vec![Notify, SetTweak(Tweak::Highlight(false))],
|
||||
}
|
||||
}
|
||||
|
||||
/// Matches a poll start event sent in a room with exactly two members.
|
||||
///
|
||||
/// This rule should be kept in sync with `.m.rule.room_one_to_one` by the server.
|
||||
#[cfg(feature = "unstable-msc3381")]
|
||||
pub fn poll_start_one_to_one() -> Self {
|
||||
Self {
|
||||
rule_id: ".m.rule.poll_start_one_to_one".into(),
|
||||
default: true,
|
||||
enabled: true,
|
||||
conditions: vec![
|
||||
RoomMemberCount { is: RoomMemberCountIs::from(js_int::uint!(2)) },
|
||||
EventMatch { key: "type".into(), pattern: "m.poll.start".into() },
|
||||
],
|
||||
actions: vec![Notify, SetTweak(Tweak::Sound("default".into()))],
|
||||
}
|
||||
}
|
||||
|
||||
/// Matches a poll start event sent in any room.
|
||||
///
|
||||
/// This rule should be kept in sync with `.m.rule.message` by the server.
|
||||
#[cfg(feature = "unstable-msc3381")]
|
||||
pub fn poll_start() -> Self {
|
||||
Self {
|
||||
rule_id: ".m.rule.poll_start".into(),
|
||||
default: true,
|
||||
enabled: true,
|
||||
conditions: vec![EventMatch { key: "type".into(), pattern: "m.poll.start".into() }],
|
||||
actions: vec![Notify],
|
||||
}
|
||||
}
|
||||
|
||||
/// Matches a poll end event sent in a room with exactly two members.
|
||||
///
|
||||
/// This rule should be kept in sync with `.m.rule.room_one_to_one` by the server.
|
||||
#[cfg(feature = "unstable-msc3381")]
|
||||
pub fn poll_end_one_to_one() -> Self {
|
||||
Self {
|
||||
rule_id: ".m.rule.poll_end_one_to_one".into(),
|
||||
default: true,
|
||||
enabled: true,
|
||||
conditions: vec![
|
||||
RoomMemberCount { is: RoomMemberCountIs::from(js_int::uint!(2)) },
|
||||
EventMatch { key: "type".into(), pattern: "m.poll.end".into() },
|
||||
],
|
||||
actions: vec![Notify, SetTweak(Tweak::Sound("default".into()))],
|
||||
}
|
||||
}
|
||||
|
||||
/// Matches a poll end event sent in any room.
|
||||
///
|
||||
/// This rule should be kept in sync with `.m.rule.message` by the server.
|
||||
#[cfg(feature = "unstable-msc3381")]
|
||||
pub fn poll_end() -> Self {
|
||||
Self {
|
||||
rule_id: ".m.rule.poll_end".into(),
|
||||
default: true,
|
||||
enabled: true,
|
||||
conditions: vec![EventMatch { key: "type".into(), pattern: "m.poll.end".into() }],
|
||||
actions: vec![Notify],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ mod location;
|
||||
mod message;
|
||||
mod message_event;
|
||||
mod pdu;
|
||||
mod poll;
|
||||
mod redacted;
|
||||
mod redaction;
|
||||
mod relations;
|
||||
|
472
crates/ruma-common/tests/events/poll.rs
Normal file
472
crates/ruma-common/tests/events/poll.rs
Normal file
@ -0,0 +1,472 @@
|
||||
#![cfg(feature = "unstable-msc3381")]
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
use assert_matches::assert_matches;
|
||||
use assign::assign;
|
||||
use js_int::uint;
|
||||
use ruma_common::{
|
||||
event_id,
|
||||
events::{
|
||||
message::MessageContent,
|
||||
poll::{
|
||||
end::{PollEndContent, PollEndEventContent},
|
||||
response::{PollResponseContent, PollResponseEventContent},
|
||||
start::{
|
||||
PollAnswer, PollAnswers, PollAnswersError, PollKind, PollStartContent,
|
||||
PollStartEventContent,
|
||||
},
|
||||
ReferenceRelation,
|
||||
},
|
||||
AnyMessageLikeEvent, MessageLikeEvent, MessageLikeUnsigned, OriginalMessageLikeEvent,
|
||||
},
|
||||
room_id, user_id, MilliSecondsSinceUnixEpoch,
|
||||
};
|
||||
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" },
|
||||
]);
|
||||
|
||||
assert_matches!(
|
||||
from_json_value::<PollAnswers>(json_data),
|
||||
Ok(answers) if answers.answers().len() == 2
|
||||
);
|
||||
}
|
||||
|
||||
#[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" },
|
||||
]);
|
||||
|
||||
assert_matches!(
|
||||
from_json_value::<PollAnswers>(json_data),
|
||||
Ok(answers) if answers.answers().len() == 20
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn poll_answers_deserialization_not_enough() {
|
||||
let json_data = json!([]);
|
||||
|
||||
let err = from_json_value::<PollAnswers>(json_data).unwrap_err();
|
||||
assert!(err.is_data());
|
||||
assert_eq!(err.to_string(), PollAnswersError::NotEnoughValues.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn start_content_serialization() {
|
||||
let event_content = PollStartEventContent::new(PollStartContent::new(
|
||||
MessageContent::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!")),
|
||||
]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
to_json_value(&event_content).unwrap(),
|
||||
json!({
|
||||
"org.matrix.msc3381.poll.start": {
|
||||
"question": { "org.matrix.msc1767.text": "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!"},
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn start_event_serialization() {
|
||||
let event = OriginalMessageLikeEvent {
|
||||
content: PollStartEventContent::new(assign!(
|
||||
PollStartContent::new(
|
||||
MessageContent::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!")),
|
||||
]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
),
|
||||
{ max_selections: uint!(2) }
|
||||
)),
|
||||
event_id: event_id!("$event:notareal.hs").to_owned(),
|
||||
sender: user_id!("@user:notareal.hs").to_owned(),
|
||||
origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(134_829_848)),
|
||||
room_id: room_id!("!roomid:notareal.hs").to_owned(),
|
||||
unsigned: MessageLikeUnsigned::default(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
to_json_value(&event).unwrap(),
|
||||
json!({
|
||||
"content": {
|
||||
"org.matrix.msc3381.poll.start": {
|
||||
"question": { "org.matrix.msc1767.text": "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!"},
|
||||
]
|
||||
},
|
||||
},
|
||||
"event_id": "$event:notareal.hs",
|
||||
"origin_server_ts": 134_829_848,
|
||||
"room_id": "!roomid:notareal.hs",
|
||||
"sender": "@user:notareal.hs",
|
||||
"type": "org.matrix.msc3381.poll.start",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn start_event_unstable_deserialization() {
|
||||
let json_data = json!({
|
||||
"content": {
|
||||
"org.matrix.msc3381.poll.start": {
|
||||
"question": { "org.matrix.msc1767.text": "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!"},
|
||||
]
|
||||
},
|
||||
},
|
||||
"event_id": "$event:notareal.hs",
|
||||
"origin_server_ts": 134_829_848,
|
||||
"room_id": "!roomid:notareal.hs",
|
||||
"sender": "@user:notareal.hs",
|
||||
"type": "org.matrix.msc3381.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::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[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!");
|
||||
}
|
||||
|
||||
#[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!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn response_content_serialization() {
|
||||
let event_content = PollResponseEventContent::new(
|
||||
PollResponseContent::new(vec!["my-answer".to_owned()]),
|
||||
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"],
|
||||
},
|
||||
"m.relates_to": {
|
||||
"rel_type": "m.reference",
|
||||
"event_id": "$related_event:notareal.hs",
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn response_event_serialization() {
|
||||
let event = OriginalMessageLikeEvent {
|
||||
content: PollResponseEventContent::new(
|
||||
PollResponseContent::new(vec!["first-answer".to_owned(), "second-answer".to_owned()]),
|
||||
event_id!("$related_event:notareal.hs").to_owned(),
|
||||
),
|
||||
event_id: event_id!("$event:notareal.hs").to_owned(),
|
||||
sender: user_id!("@user:notareal.hs").to_owned(),
|
||||
origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(134_829_848)),
|
||||
room_id: room_id!("!roomid:notareal.hs").to_owned(),
|
||||
unsigned: MessageLikeUnsigned::default(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
to_json_value(&event).unwrap(),
|
||||
json!({
|
||||
"content": {
|
||||
"org.matrix.msc3381.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": "org.matrix.msc3381.poll.response",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn response_event_unstable_deserialization() {
|
||||
let json_data = json!({
|
||||
"content": {
|
||||
"org.matrix.msc3381.poll.response": {
|
||||
"answers": ["my-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": "org.matrix.msc3381.poll.response",
|
||||
});
|
||||
|
||||
let event = from_json_value::<AnyMessageLikeEvent>(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(), 1);
|
||||
assert_eq!(answers[0], "my-answer");
|
||||
assert_matches!(
|
||||
message_event.content.relates_to,
|
||||
ReferenceRelation { event_id, .. } if 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::<AnyMessageLikeEvent>(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");
|
||||
assert_matches!(
|
||||
message_event.content.relates_to,
|
||||
ReferenceRelation { event_id, .. } if event_id == "$related_event:notareal.hs"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn end_content_serialization() {
|
||||
let event_content = PollEndEventContent::new(
|
||||
PollEndContent::new(),
|
||||
event_id!("$related_event:notareal.hs").to_owned(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
to_json_value(&event_content).unwrap(),
|
||||
json!({
|
||||
"org.matrix.msc3381.poll.end": {},
|
||||
"m.relates_to": {
|
||||
"rel_type": "m.reference",
|
||||
"event_id": "$related_event:notareal.hs",
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn end_event_serialization() {
|
||||
let event = OriginalMessageLikeEvent {
|
||||
content: PollEndEventContent::new(
|
||||
PollEndContent::new(),
|
||||
event_id!("$related_event:notareal.hs").to_owned(),
|
||||
),
|
||||
event_id: event_id!("$event:notareal.hs").to_owned(),
|
||||
sender: user_id!("@user:notareal.hs").to_owned(),
|
||||
origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(134_829_848)),
|
||||
room_id: room_id!("!roomid:notareal.hs").to_owned(),
|
||||
unsigned: MessageLikeUnsigned::default(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
to_json_value(&event).unwrap(),
|
||||
json!({
|
||||
"content": {
|
||||
"org.matrix.msc3381.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": "org.matrix.msc3381.poll.end",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn end_event_unstable_deserialization() {
|
||||
let json_data = json!({
|
||||
"content": {
|
||||
"org.matrix.msc3381.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": "org.matrix.msc3381.poll.end",
|
||||
});
|
||||
|
||||
let event = from_json_value::<AnyMessageLikeEvent>(json_data).unwrap();
|
||||
let message_event = assert_matches!(
|
||||
event,
|
||||
AnyMessageLikeEvent::PollEnd(MessageLikeEvent::Original(message_event)) => message_event
|
||||
);
|
||||
assert_matches!(
|
||||
message_event.content.relates_to,
|
||||
ReferenceRelation { event_id, .. } if 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",
|
||||
});
|
||||
|
||||
let event = from_json_value::<AnyMessageLikeEvent>(json_data).unwrap();
|
||||
let message_event = assert_matches!(
|
||||
event,
|
||||
AnyMessageLikeEvent::PollEnd(MessageLikeEvent::Original(message_event)) => message_event
|
||||
);
|
||||
assert_matches!(
|
||||
message_event.content.relates_to,
|
||||
ReferenceRelation { event_id, .. } if event_id == "$related_event:notareal.hs"
|
||||
);
|
||||
}
|
@ -135,6 +135,7 @@ unstable-msc2677 = [
|
||||
unstable-msc2870 = ["ruma-signatures/unstable-msc2870"]
|
||||
unstable-msc3245 = ["ruma-common/unstable-msc3245"]
|
||||
unstable-msc3246 = ["ruma-common/unstable-msc3246"]
|
||||
unstable-msc3381 = ["ruma-common/unstable-msc3381"]
|
||||
unstable-msc3440 = [
|
||||
"ruma-client-api/unstable-msc3440",
|
||||
"ruma-common/unstable-msc3440",
|
||||
@ -163,6 +164,7 @@ __ci = [
|
||||
"unstable-msc2870",
|
||||
"unstable-msc3245",
|
||||
"unstable-msc3246",
|
||||
"unstable-msc3381",
|
||||
"unstable-msc3440",
|
||||
"unstable-msc3488",
|
||||
"unstable-msc3551",
|
||||
|
Loading…
x
Reference in New Issue
Block a user