diff --git a/crates/teloxide-core/src/types/poll_answer.rs b/crates/teloxide-core/src/types/poll_answer.rs index e87e0878..f40b55fb 100644 --- a/crates/teloxide-core/src/types/poll_answer.rs +++ b/crates/teloxide-core/src/types/poll_answer.rs @@ -1,17 +1,117 @@ -use crate::types::User; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; + +use crate::types::{Chat, User}; #[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PollAnswer { /// Unique poll identifier. pub poll_id: String, - /// The user, who changed the answer to the poll. - pub user: User, + /// If the voter is anonymous, stores the chat that changed the answer to + /// the poll. + /// + /// If the voter isn't anonymous, stores the user that changed + /// the answer to the poll + #[serde(deserialize_with = "deserialize_voter", flatten)] + pub voter: Voter, /// 0-based identifiers of answer options, chosen by the user. /// /// May be empty if the user retracted their vote. pub option_ids: Vec, } + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Voter { + Chat(Chat), + User(User), +} + +impl Voter { + #[must_use] + pub fn chat(&self) -> Option<&Chat> { + match self { + Self::Chat(chat) => Some(chat), + _ => None, + } + } + + #[must_use] + pub fn user(&self) -> Option<&User> { + match self { + Self::User(user) => Some(user), + _ => None, + } + } +} + +/// These fields `chat` and `user` from the original [`PollAnswer`] should be +/// exclusive, but in cases when the `voter_chat` is presented the `user` isn't +/// `None`, but rather actual value for backward compatibility, the field `user` +/// in such objects will contain the user 136817688 (@Channel_Bot). +#[derive(Deserialize)] +struct VoterDe { + /// The chat that changed the answer to the poll, if the voter is anonymous + pub voter_chat: Option, + + /// The user that changed the answer to the poll, if the voter isn't + /// anonymous + pub user: Option, +} + +fn deserialize_voter<'d, D: Deserializer<'d>>(d: D) -> Result { + let VoterDe { voter_chat, user } = VoterDe::deserialize(d)?; + Ok(voter_chat.map(Voter::Chat).or(user.map(Voter::User)).unwrap()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_poll_answer_with_user_de() { + let json = r#"{ + "poll_id":"POLL_ID", + "user": {"id":42,"is_bot":false,"first_name":"blah"}, + "option_ids": [] + }"#; + + let poll_answer: PollAnswer = serde_json::from_str(json).unwrap(); + assert!(matches!(poll_answer.voter, Voter::User(_))); + } + + #[test] + fn test_poll_answer_with_voter_chat_de() { + let json = r#"{ + "poll_id":"POLL_ID", + "voter_chat": { + "id": -1001160242915, + "title": "a", + "type": "group" + }, + "option_ids": [] + }"#; + + let poll_answer: PollAnswer = serde_json::from_str(json).unwrap(); + assert!(matches!(poll_answer.voter, Voter::Chat(_))); + } + + #[test] + fn test_poll_answer_with_both_user_and_voter_chat_de() { + let json = r#"{ + "poll_id":"POLL_ID", + "voter_chat": { + "id": -1001160242915, + "title": "a", + "type": "group" + }, + "user": {"id":136817688,"is_bot":true,"first_name":"Channel_Bot"}, + "option_ids": [] + }"#; + + let poll_answer: PollAnswer = serde_json::from_str(json).unwrap(); + assert!(matches!(poll_answer.voter, Voter::Chat(_))); + } +} diff --git a/crates/teloxide-core/src/types/update.rs b/crates/teloxide-core/src/types/update.rs index caff4c50..5f03f689 100644 --- a/crates/teloxide-core/src/types/update.rs +++ b/crates/teloxide-core/src/types/update.rs @@ -136,7 +136,7 @@ impl Update { InlineQuery(query) => &query.from, ShippingQuery(query) => &query.from, PreCheckoutQuery(query) => &query.from, - PollAnswer(answer) => &answer.user, + PollAnswer(answer) => return answer.voter.user(), MyChatMember(m) | ChatMember(m) => &m.from, ChatJoinRequest(r) => &r.from, @@ -198,7 +198,12 @@ impl Update { UpdateKind::PreCheckoutQuery(query) => i1(once(&query.from)), UpdateKind::Poll(poll) => i3(poll.mentioned_users()), - UpdateKind::PollAnswer(answer) => i1(once(&answer.user)), + UpdateKind::PollAnswer(answer) => { + if let Some(user) = answer.voter.user() { + return i1(once(user)); + } + i6(empty()) + } UpdateKind::MyChatMember(member) | UpdateKind::ChatMember(member) => { i4(member.mentioned_users())