ruma_events/poll/
unstable_start.rs

1//! Types for the `org.matrix.msc3381.poll.start` event, the unstable version of `m.poll.start`.
2
3use std::ops::Deref;
4
5use js_int::UInt;
6use ruma_macros::EventContent;
7use serde::{Deserialize, Serialize};
8
9mod content_serde;
10mod unstable_poll_answers_serde;
11mod unstable_poll_kind_serde;
12
13use ruma_common::{room_version_rules::RedactionRules, MilliSecondsSinceUnixEpoch, OwnedEventId};
14
15use self::unstable_poll_answers_serde::UnstablePollAnswersDeHelper;
16use super::{
17    compile_unstable_poll_results, generate_poll_end_fallback_text,
18    start::{PollAnswers, PollAnswersError, PollContentBlock, PollKind},
19    unstable_end::UnstablePollEndEventContent,
20    PollResponseData,
21};
22use crate::{
23    relation::Replacement, room::message::RelationWithoutReplacement, MessageLikeEventContent,
24    MessageLikeEventType, RedactContent, RedactedMessageLikeEventContent, StaticEventContent,
25};
26
27/// The payload for an unstable poll start event.
28///
29/// This is the event content that should be sent for room versions that don't support extensible
30/// events. As of Matrix 1.7, none of the stable room versions (1 through 10) support extensible
31/// events.
32///
33/// To send a poll start event for a room version that supports extensible events, use
34/// [`PollStartEventContent`].
35///
36/// [`PollStartEventContent`]: super::start::PollStartEventContent
37#[derive(Clone, Debug, Serialize, EventContent)]
38#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
39#[ruma_event(type = "org.matrix.msc3381.poll.start", kind = MessageLike, custom_redacted)]
40#[serde(untagged)]
41#[allow(clippy::large_enum_variant)]
42pub enum UnstablePollStartEventContent {
43    /// A new poll start event.
44    New(NewUnstablePollStartEventContent),
45
46    /// A replacement poll start event.
47    Replacement(ReplacementUnstablePollStartEventContent),
48}
49
50impl UnstablePollStartEventContent {
51    /// Get the poll start content of this event content.
52    pub fn poll_start(&self) -> &UnstablePollStartContentBlock {
53        match self {
54            Self::New(c) => &c.poll_start,
55            Self::Replacement(c) => &c.relates_to.new_content.poll_start,
56        }
57    }
58}
59
60impl RedactContent for UnstablePollStartEventContent {
61    type Redacted = RedactedUnstablePollStartEventContent;
62
63    fn redact(self, _rules: &RedactionRules) -> Self::Redacted {
64        RedactedUnstablePollStartEventContent::default()
65    }
66}
67
68impl From<NewUnstablePollStartEventContent> for UnstablePollStartEventContent {
69    fn from(value: NewUnstablePollStartEventContent) -> Self {
70        Self::New(value)
71    }
72}
73
74impl From<ReplacementUnstablePollStartEventContent> for UnstablePollStartEventContent {
75    fn from(value: ReplacementUnstablePollStartEventContent) -> Self {
76        Self::Replacement(value)
77    }
78}
79
80impl OriginalSyncUnstablePollStartEvent {
81    /// Compile the results for this poll with the given response into an
82    /// `UnstablePollEndEventContent`.
83    ///
84    /// It generates a default text representation of the results in English.
85    ///
86    /// This uses [`compile_unstable_poll_results()`] internally.
87    pub fn compile_results<'a>(
88        &'a self,
89        responses: impl IntoIterator<Item = PollResponseData<'a>>,
90    ) -> UnstablePollEndEventContent {
91        let poll_start = self.content.poll_start();
92
93        let full_results = compile_unstable_poll_results(
94            poll_start,
95            responses,
96            Some(MilliSecondsSinceUnixEpoch::now()),
97        );
98        let results =
99            full_results.into_iter().map(|(id, users)| (id, users.len())).collect::<Vec<_>>();
100
101        // Get the text representation of the best answers.
102        let answers =
103            poll_start.answers.iter().map(|a| (a.id.as_str(), a.text.as_str())).collect::<Vec<_>>();
104        let plain_text = generate_poll_end_fallback_text(&answers, results.into_iter());
105
106        UnstablePollEndEventContent::new(plain_text, self.event_id.clone())
107    }
108}
109
110/// A new unstable poll start event.
111#[derive(Clone, Debug, Serialize)]
112#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
113pub struct NewUnstablePollStartEventContent {
114    /// The poll content of the message.
115    #[serde(rename = "org.matrix.msc3381.poll.start")]
116    pub poll_start: UnstablePollStartContentBlock,
117
118    /// Text representation of the message, for clients that don't support polls.
119    #[serde(rename = "org.matrix.msc1767.text")]
120    pub text: Option<String>,
121
122    /// Information about related messages.
123    #[serde(rename = "m.relates_to", skip_serializing_if = "Option::is_none")]
124    pub relates_to: Option<RelationWithoutReplacement>,
125}
126
127impl NewUnstablePollStartEventContent {
128    /// Creates a `NewUnstablePollStartEventContent` with the given poll content.
129    pub fn new(poll_start: UnstablePollStartContentBlock) -> Self {
130        Self { poll_start, text: None, relates_to: None }
131    }
132
133    /// Creates a `NewUnstablePollStartEventContent` with the given plain text fallback
134    /// representation and poll content.
135    pub fn plain_text(text: impl Into<String>, poll_start: UnstablePollStartContentBlock) -> Self {
136        Self { poll_start, text: Some(text.into()), relates_to: None }
137    }
138}
139
140impl StaticEventContent for NewUnstablePollStartEventContent {
141    const TYPE: &'static str = UnstablePollStartEventContent::TYPE;
142    type IsPrefix = <UnstablePollStartEventContent as StaticEventContent>::IsPrefix;
143}
144
145impl MessageLikeEventContent for NewUnstablePollStartEventContent {
146    fn event_type(&self) -> MessageLikeEventType {
147        MessageLikeEventType::UnstablePollStart
148    }
149}
150
151/// Form of [`NewUnstablePollStartEventContent`] without relation.
152///
153/// To construct this type, construct a [`NewUnstablePollStartEventContent`] and then use one of its
154/// `::from()` / `.into()` methods.
155#[derive(Clone, Debug, Serialize, Deserialize)]
156#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
157pub struct NewUnstablePollStartEventContentWithoutRelation {
158    /// The poll content of the message.
159    #[serde(rename = "org.matrix.msc3381.poll.start")]
160    pub poll_start: UnstablePollStartContentBlock,
161
162    /// Text representation of the message, for clients that don't support polls.
163    #[serde(rename = "org.matrix.msc1767.text")]
164    pub text: Option<String>,
165}
166
167impl From<NewUnstablePollStartEventContent> for NewUnstablePollStartEventContentWithoutRelation {
168    fn from(value: NewUnstablePollStartEventContent) -> Self {
169        let NewUnstablePollStartEventContent { poll_start, text, .. } = value;
170        Self { poll_start, text }
171    }
172}
173
174/// A replacement unstable poll start event.
175#[derive(Clone, Debug)]
176#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
177pub struct ReplacementUnstablePollStartEventContent {
178    /// The poll content of the message.
179    pub poll_start: Option<UnstablePollStartContentBlock>,
180
181    /// Text representation of the message, for clients that don't support polls.
182    pub text: Option<String>,
183
184    /// Information about related messages.
185    pub relates_to: Replacement<NewUnstablePollStartEventContentWithoutRelation>,
186}
187
188impl ReplacementUnstablePollStartEventContent {
189    /// Creates a `ReplacementUnstablePollStartEventContent` with the given poll content that
190    /// replaces the event with the given ID.
191    ///
192    /// The constructed content does not have a fallback by default.
193    pub fn new(poll_start: UnstablePollStartContentBlock, replaces: OwnedEventId) -> Self {
194        Self {
195            poll_start: None,
196            text: None,
197            relates_to: Replacement {
198                event_id: replaces,
199                new_content: NewUnstablePollStartEventContent::new(poll_start).into(),
200            },
201        }
202    }
203
204    /// Creates a `ReplacementUnstablePollStartEventContent` with the given plain text fallback
205    /// representation and poll content that replaces the event with the given ID.
206    ///
207    /// The constructed content does not have a fallback by default.
208    pub fn plain_text(
209        text: impl Into<String>,
210        poll_start: UnstablePollStartContentBlock,
211        replaces: OwnedEventId,
212    ) -> Self {
213        Self {
214            poll_start: None,
215            text: None,
216            relates_to: Replacement {
217                event_id: replaces,
218                new_content: NewUnstablePollStartEventContent::plain_text(text, poll_start).into(),
219            },
220        }
221    }
222}
223
224impl StaticEventContent for ReplacementUnstablePollStartEventContent {
225    const TYPE: &'static str = UnstablePollStartEventContent::TYPE;
226    type IsPrefix = <UnstablePollStartEventContent as StaticEventContent>::IsPrefix;
227}
228
229impl MessageLikeEventContent for ReplacementUnstablePollStartEventContent {
230    fn event_type(&self) -> MessageLikeEventType {
231        MessageLikeEventType::UnstablePollStart
232    }
233}
234
235/// Redacted form of UnstablePollStartEventContent
236#[derive(Clone, Debug, Default, Serialize, Deserialize)]
237#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
238pub struct RedactedUnstablePollStartEventContent {}
239
240impl RedactedUnstablePollStartEventContent {
241    /// Creates an empty RedactedUnstablePollStartEventContent.
242    pub fn new() -> RedactedUnstablePollStartEventContent {
243        Self::default()
244    }
245}
246
247impl StaticEventContent for RedactedUnstablePollStartEventContent {
248    const TYPE: &'static str = UnstablePollStartEventContent::TYPE;
249    type IsPrefix = <UnstablePollStartEventContent as StaticEventContent>::IsPrefix;
250}
251
252impl RedactedMessageLikeEventContent for RedactedUnstablePollStartEventContent {
253    fn event_type(&self) -> MessageLikeEventType {
254        MessageLikeEventType::UnstablePollStart
255    }
256}
257
258/// An unstable block for poll start content.
259#[derive(Debug, Clone, Serialize, Deserialize)]
260#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
261pub struct UnstablePollStartContentBlock {
262    /// The question of the poll.
263    pub question: UnstablePollQuestion,
264
265    /// The kind of the poll.
266    #[serde(default, with = "unstable_poll_kind_serde")]
267    pub kind: PollKind,
268
269    /// The maximum number of responses a user is able to select.
270    ///
271    /// Must be greater or equal to `1`.
272    ///
273    /// Defaults to `1`.
274    #[serde(default = "PollContentBlock::default_max_selections")]
275    pub max_selections: UInt,
276
277    /// The possible answers to the poll.
278    pub answers: UnstablePollAnswers,
279}
280
281impl UnstablePollStartContentBlock {
282    /// Creates a new `PollStartContent` with the given question and answers.
283    pub fn new(question: impl Into<String>, answers: UnstablePollAnswers) -> Self {
284        Self {
285            question: UnstablePollQuestion::new(question),
286            kind: Default::default(),
287            max_selections: PollContentBlock::default_max_selections(),
288            answers,
289        }
290    }
291}
292
293/// An unstable poll question.
294#[derive(Debug, Clone, Serialize, Deserialize)]
295#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
296pub struct UnstablePollQuestion {
297    /// The text representation of the question.
298    #[serde(rename = "org.matrix.msc1767.text")]
299    pub text: String,
300}
301
302impl UnstablePollQuestion {
303    /// Creates a new `UnstablePollQuestion` with the given plain text.
304    pub fn new(text: impl Into<String>) -> Self {
305        Self { text: text.into() }
306    }
307}
308
309/// The unstable answers to a poll.
310///
311/// Must include between 1 and 20 `UnstablePollAnswer`s.
312///
313/// To build this, use one of the `TryFrom` implementations.
314#[derive(Clone, Debug, Deserialize, Serialize)]
315#[serde(try_from = "UnstablePollAnswersDeHelper")]
316pub struct UnstablePollAnswers(Vec<UnstablePollAnswer>);
317
318impl TryFrom<Vec<UnstablePollAnswer>> for UnstablePollAnswers {
319    type Error = PollAnswersError;
320
321    fn try_from(value: Vec<UnstablePollAnswer>) -> Result<Self, Self::Error> {
322        if value.len() < PollAnswers::MIN_LENGTH {
323            Err(PollAnswersError::NotEnoughValues)
324        } else if value.len() > PollAnswers::MAX_LENGTH {
325            Err(PollAnswersError::TooManyValues)
326        } else {
327            Ok(Self(value))
328        }
329    }
330}
331
332impl TryFrom<&[UnstablePollAnswer]> for UnstablePollAnswers {
333    type Error = PollAnswersError;
334
335    fn try_from(value: &[UnstablePollAnswer]) -> Result<Self, Self::Error> {
336        Self::try_from(value.to_owned())
337    }
338}
339
340impl Deref for UnstablePollAnswers {
341    type Target = [UnstablePollAnswer];
342
343    fn deref(&self) -> &Self::Target {
344        &self.0
345    }
346}
347
348/// Unstable poll answer.
349#[derive(Clone, Debug, Serialize, Deserialize)]
350#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
351pub struct UnstablePollAnswer {
352    /// The ID of the answer.
353    ///
354    /// This must be unique among the answers of a poll.
355    pub id: String,
356
357    /// The text representation of the answer.
358    #[serde(rename = "org.matrix.msc1767.text")]
359    pub text: String,
360}
361
362impl UnstablePollAnswer {
363    /// Creates a new `PollAnswer` with the given id and text representation.
364    pub fn new(id: impl Into<String>, text: impl Into<String>) -> Self {
365        Self { id: id.into(), text: text.into() }
366    }
367}