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