1use 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#[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 New(NewUnstablePollStartEventContent),
46
47 Replacement(ReplacementUnstablePollStartEventContent),
49}
50
51impl UnstablePollStartEventContent {
52 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 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 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#[derive(Clone, Debug, Serialize)]
113#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
114pub struct NewUnstablePollStartEventContent {
115 #[serde(rename = "org.matrix.msc3381.poll.start")]
117 pub poll_start: UnstablePollStartContentBlock,
118
119 #[serde(rename = "org.matrix.msc1767.text")]
121 pub text: Option<String>,
122
123 #[serde(rename = "m.relates_to", skip_serializing_if = "Option::is_none")]
125 pub relates_to: Option<RelationWithoutReplacement>,
126}
127
128impl NewUnstablePollStartEventContent {
129 pub fn new(poll_start: UnstablePollStartContentBlock) -> Self {
131 Self { poll_start, text: None, relates_to: None }
132 }
133
134 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#[derive(Clone, Debug, Serialize, Deserialize)]
160#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
161pub struct NewUnstablePollStartEventContentWithoutRelation {
162 #[serde(rename = "org.matrix.msc3381.poll.start")]
164 pub poll_start: UnstablePollStartContentBlock,
165
166 #[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#[derive(Clone, Debug)]
180#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
181pub struct ReplacementUnstablePollStartEventContent {
182 pub poll_start: Option<UnstablePollStartContentBlock>,
184
185 pub text: Option<String>,
187
188 pub relates_to: Replacement<NewUnstablePollStartEventContentWithoutRelation>,
190}
191
192impl ReplacementUnstablePollStartEventContent {
193 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 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#[derive(Clone, Debug, Default, Serialize, Deserialize)]
244#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
245pub struct RedactedUnstablePollStartEventContent {}
246
247impl RedactedUnstablePollStartEventContent {
248 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#[derive(Debug, Clone, Serialize, Deserialize)]
270#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
271pub struct UnstablePollStartContentBlock {
272 pub question: UnstablePollQuestion,
274
275 #[serde(default, with = "unstable_poll_kind_serde")]
277 pub kind: PollKind,
278
279 #[serde(default = "PollContentBlock::default_max_selections")]
285 pub max_selections: UInt,
286
287 pub answers: UnstablePollAnswers,
289}
290
291impl UnstablePollStartContentBlock {
292 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#[derive(Debug, Clone, Serialize, Deserialize)]
305#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
306pub struct UnstablePollQuestion {
307 #[serde(rename = "org.matrix.msc1767.text")]
309 pub text: String,
310}
311
312impl UnstablePollQuestion {
313 pub fn new(text: impl Into<String>) -> Self {
315 Self { text: text.into() }
316 }
317}
318
319#[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#[derive(Clone, Debug, Serialize, Deserialize)]
360#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
361pub struct UnstablePollAnswer {
362 pub id: String,
366
367 #[serde(rename = "org.matrix.msc1767.text")]
369 pub text: String,
370}
371
372impl UnstablePollAnswer {
373 pub fn new(id: impl Into<String>, text: impl Into<String>) -> Self {
375 Self { id: id.into(), text: text.into() }
376 }
377}