use std::ops::Deref;
use js_int::{uint, UInt};
use ruma_common::{serde::StringEnum, MilliSecondsSinceUnixEpoch};
use ruma_macros::EventContent;
use serde::{Deserialize, Serialize};
use crate::PrivOwnedStr;
mod poll_answers_serde;
use poll_answers_serde::PollAnswersDeHelper;
use super::{
compile_poll_results,
end::{PollEndEventContent, PollResultsContentBlock},
generate_poll_end_fallback_text, PollResponseData,
};
use crate::{message::TextContentBlock, room::message::Relation};
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
#[ruma_event(type = "m.poll.start", kind = MessageLike, without_relation)]
pub struct PollStartEventContent {
#[serde(rename = "m.poll")]
pub poll: PollContentBlock,
#[serde(rename = "m.text")]
pub text: TextContentBlock,
#[serde(
flatten,
skip_serializing_if = "Option::is_none",
deserialize_with = "crate::room::message::relation_serde::deserialize_relation"
)]
pub relates_to: Option<Relation<PollStartEventContentWithoutRelation>>,
#[cfg(feature = "unstable-msc3955")]
#[serde(
default,
skip_serializing_if = "ruma_common::serde::is_default",
rename = "org.matrix.msc1767.automated"
)]
pub automated: bool,
}
impl PollStartEventContent {
pub fn new(text: TextContentBlock, poll: PollContentBlock) -> Self {
Self {
poll,
text,
relates_to: None,
#[cfg(feature = "unstable-msc3955")]
automated: false,
}
}
pub fn with_plain_text(plain_text: impl Into<String>, poll: PollContentBlock) -> Self {
Self::new(TextContentBlock::plain(plain_text), poll)
}
}
impl OriginalSyncPollStartEvent {
pub fn compile_results<'a>(
&'a self,
responses: impl IntoIterator<Item = PollResponseData<'a>>,
) -> PollEndEventContent {
let full_results = compile_poll_results(
&self.content.poll,
responses,
Some(MilliSecondsSinceUnixEpoch::now()),
);
let results =
full_results.into_iter().map(|(id, users)| (id, users.len())).collect::<Vec<_>>();
let poll_results = PollResultsContentBlock::from_iter(
results
.iter()
.map(|(id, count)| ((*id).to_owned(), (*count).try_into().unwrap_or(UInt::MAX))),
);
let answers = self
.content
.poll
.answers
.iter()
.map(|a| {
let text = a.text.find_plain().unwrap_or(&a.id);
(a.id.as_str(), text)
})
.collect::<Vec<_>>();
let plain_text = generate_poll_end_fallback_text(&answers, results.into_iter());
let mut end = PollEndEventContent::with_plain_text(plain_text, self.event_id.clone());
end.poll_results = Some(poll_results);
end
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct PollContentBlock {
pub question: PollQuestion,
#[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
pub kind: PollKind,
#[serde(
default = "PollContentBlock::default_max_selections",
skip_serializing_if = "PollContentBlock::max_selections_is_default"
)]
pub max_selections: UInt,
pub answers: PollAnswers,
}
impl PollContentBlock {
pub fn new(question: TextContentBlock, answers: PollAnswers) -> Self {
Self {
question: question.into(),
kind: Default::default(),
max_selections: Self::default_max_selections(),
answers,
}
}
pub(super) fn default_max_selections() -> UInt {
uint!(1)
}
fn max_selections_is_default(max_selections: &UInt) -> bool {
max_selections == &Self::default_max_selections()
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct PollQuestion {
#[serde(rename = "m.text")]
pub text: TextContentBlock,
}
impl From<TextContentBlock> for PollQuestion {
fn from(text: TextContentBlock) -> Self {
Self { text }
}
}
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
#[derive(Clone, Default, PartialEq, Eq, StringEnum)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub enum PollKind {
#[default]
#[ruma_enum(rename = "m.undisclosed")]
Undisclosed,
#[ruma_enum(rename = "m.disclosed")]
Disclosed,
#[doc(hidden)]
_Custom(PrivOwnedStr),
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(try_from = "PollAnswersDeHelper")]
pub struct PollAnswers(Vec<PollAnswer>);
impl PollAnswers {
pub const MIN_LENGTH: usize = 1;
pub const MAX_LENGTH: usize = 20;
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, thiserror::Error)]
#[non_exhaustive]
pub enum PollAnswersError {
#[error("too many values")]
TooManyValues,
#[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())
}
}
impl Deref for PollAnswers {
type Target = [PollAnswer];
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct PollAnswer {
#[serde(rename = "m.id")]
pub id: String,
#[serde(rename = "m.text")]
pub text: TextContentBlock,
}
impl PollAnswer {
pub fn new(id: String, text: TextContentBlock) -> Self {
Self { id, text }
}
}