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::{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#[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 New(NewUnstablePollStartEventContent),
45
46 Replacement(ReplacementUnstablePollStartEventContent),
48}
49
50impl UnstablePollStartEventContent {
51 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 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 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#[derive(Clone, Debug, Serialize)]
112#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
113pub struct NewUnstablePollStartEventContent {
114 #[serde(rename = "org.matrix.msc3381.poll.start")]
116 pub poll_start: UnstablePollStartContentBlock,
117
118 #[serde(rename = "org.matrix.msc1767.text")]
120 pub text: Option<String>,
121
122 #[serde(rename = "m.relates_to", skip_serializing_if = "Option::is_none")]
124 pub relates_to: Option<RelationWithoutReplacement>,
125}
126
127impl NewUnstablePollStartEventContent {
128 pub fn new(poll_start: UnstablePollStartContentBlock) -> Self {
130 Self { poll_start, text: None, relates_to: None }
131 }
132
133 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#[derive(Clone, Debug, Serialize, Deserialize)]
156#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
157pub struct NewUnstablePollStartEventContentWithoutRelation {
158 #[serde(rename = "org.matrix.msc3381.poll.start")]
160 pub poll_start: UnstablePollStartContentBlock,
161
162 #[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#[derive(Clone, Debug)]
176#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
177pub struct ReplacementUnstablePollStartEventContent {
178 pub poll_start: Option<UnstablePollStartContentBlock>,
180
181 pub text: Option<String>,
183
184 pub relates_to: Replacement<NewUnstablePollStartEventContentWithoutRelation>,
186}
187
188impl ReplacementUnstablePollStartEventContent {
189 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 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#[derive(Clone, Debug, Default, Serialize, Deserialize)]
237#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
238pub struct RedactedUnstablePollStartEventContent {}
239
240impl RedactedUnstablePollStartEventContent {
241 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#[derive(Debug, Clone, Serialize, Deserialize)]
260#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
261pub struct UnstablePollStartContentBlock {
262 pub question: UnstablePollQuestion,
264
265 #[serde(default, with = "unstable_poll_kind_serde")]
267 pub kind: PollKind,
268
269 #[serde(default = "PollContentBlock::default_max_selections")]
275 pub max_selections: UInt,
276
277 pub answers: UnstablePollAnswers,
279}
280
281impl UnstablePollStartContentBlock {
282 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#[derive(Debug, Clone, Serialize, Deserialize)]
295#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
296pub struct UnstablePollQuestion {
297 #[serde(rename = "org.matrix.msc1767.text")]
299 pub text: String,
300}
301
302impl UnstablePollQuestion {
303 pub fn new(text: impl Into<String>) -> Self {
305 Self { text: text.into() }
306 }
307}
308
309#[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#[derive(Clone, Debug, Serialize, Deserialize)]
350#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
351pub struct UnstablePollAnswer {
352 pub id: String,
356
357 #[serde(rename = "org.matrix.msc1767.text")]
359 pub text: String,
360}
361
362impl UnstablePollAnswer {
363 pub fn new(id: impl Into<String>, text: impl Into<String>) -> Self {
365 Self { id: id.into(), text: text.into() }
366 }
367}