mirror of
https://github.com/teloxide/teloxide.git
synced 2024-12-22 14:35:36 +01:00
Merge pull request #1134 from syrtcevvi/fix/more-rusty-api
Add `MaybeAnonymousUser` type
This commit is contained in:
commit
ae83ff15a2
8 changed files with 253 additions and 58 deletions
23
Cargo.lock
generated
23
Cargo.lock
generated
|
@ -441,6 +441,12 @@ dependencies = [
|
|||
"syn 2.0.52",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
|
@ -1395,6 +1401,16 @@ dependencies = [
|
|||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pretty_assertions"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
|
||||
dependencies = [
|
||||
"diff",
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pretty_env_logger"
|
||||
version = "0.5.0"
|
||||
|
@ -2216,6 +2232,7 @@ dependencies = [
|
|||
"mime",
|
||||
"once_cell",
|
||||
"pin-project",
|
||||
"pretty_assertions",
|
||||
"pretty_env_logger",
|
||||
"rc-box",
|
||||
"reqwest",
|
||||
|
@ -2875,6 +2892,12 @@ version = "0.2.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d422e8e38ec76e2f06ee439ccc765e9c6a9638b9e7c9f2e8255e4d41e8bd852"
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
|
|
|
@ -21,6 +21,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
[pr1131]: https://github.com/teloxide/teloxide/pull/1131
|
||||
|
||||
### Changed
|
||||
|
||||
- `MaybeAnonymousUser` type introduced, which replaced `PollAnswer::voter: Voter` and `MessageReactionUpdated::{user, actor_chat}` in `MessageReactionUpdated`([#1134][pr1134])
|
||||
|
||||
[pr1134]: https://github.com/teloxide/teloxide/pull/1134
|
||||
|
||||
## 0.10.1 - 2024-08-17
|
||||
|
||||
### Fixed
|
||||
|
|
|
@ -95,6 +95,7 @@ ron = "0.7"
|
|||
indexmap = { version = "1.9", features = ["serde-1"] }
|
||||
aho-corasick = "0.7"
|
||||
itertools = "0.10"
|
||||
pretty_assertions = "1.4.0"
|
||||
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
|
|
@ -91,6 +91,7 @@ pub use link_preview_options::*;
|
|||
pub use location::*;
|
||||
pub use login_url::*;
|
||||
pub use mask_position::*;
|
||||
pub use maybe_anonymous_user::*;
|
||||
pub use maybe_inaccessible_message::*;
|
||||
pub use me::*;
|
||||
pub use menu_button::*;
|
||||
|
@ -218,6 +219,7 @@ mod link_preview_options;
|
|||
mod location;
|
||||
mod login_url;
|
||||
mod mask_position;
|
||||
mod maybe_anonymous_user;
|
||||
mod maybe_inaccessible_message;
|
||||
mod me;
|
||||
mod menu_button;
|
||||
|
|
69
crates/teloxide-core/src/types/maybe_anonymous_user.rs
Normal file
69
crates/teloxide-core/src/types/maybe_anonymous_user.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::types::{Chat, User};
|
||||
|
||||
/// Represents either [`User`] or anonymous user ([`Chat`]) that acts on behalf
|
||||
/// of the chat
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum MaybeAnonymousUser {
|
||||
User(User),
|
||||
Chat(Chat),
|
||||
}
|
||||
|
||||
impl MaybeAnonymousUser {
|
||||
pub fn is_user(&self) -> bool {
|
||||
self.user().is_some()
|
||||
}
|
||||
|
||||
pub fn is_chat(&self) -> bool {
|
||||
self.chat().is_some()
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn user_de() {
|
||||
let json = r#"{
|
||||
"id": 42,
|
||||
"is_bot": false,
|
||||
"first_name": "blah"
|
||||
}"#;
|
||||
|
||||
let user: MaybeAnonymousUser = serde_json::from_str(json).unwrap();
|
||||
|
||||
assert!(user.user().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chat_de() {
|
||||
let json = r#"{
|
||||
"id": -1001160242915,
|
||||
"title": "a",
|
||||
"type": "group"
|
||||
}"#;
|
||||
|
||||
let chat: MaybeAnonymousUser = serde_json::from_str(json).unwrap();
|
||||
|
||||
assert!(chat.chat().is_some());
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
use crate::types::{Chat, MessageId, ReactionType, User};
|
||||
use crate::types::{Chat, MaybeAnonymousUser, MessageId, ReactionType, User};
|
||||
|
||||
/// This object represents a change of a reaction on a message performed by a
|
||||
/// user.
|
||||
|
@ -15,12 +15,11 @@ pub struct MessageReactionUpdated {
|
|||
#[serde(flatten)]
|
||||
pub message_id: MessageId,
|
||||
|
||||
/// The user that changed the reaction, if the user isn't anonymous
|
||||
pub user: Option<User>,
|
||||
|
||||
/// The chat on behalf of which the reaction was changed, if the user is
|
||||
/// anonymous
|
||||
pub actor_chat: Option<Chat>,
|
||||
/// The [`MaybeAnonymousUser::User`] that changed the reaction, if the user
|
||||
/// isn't anonymous or the [`MaybeAnonymousUser::Chat`] on behalf of
|
||||
/// which the reaction was changed, if the user is anonymous
|
||||
#[serde(deserialize_with = "deserialize_actor", flatten)]
|
||||
pub actor: MaybeAnonymousUser,
|
||||
|
||||
/// Date of the change in Unix time
|
||||
#[serde(with = "crate::types::serde_date_from_unix_timestamp")]
|
||||
|
@ -35,22 +34,37 @@ pub struct MessageReactionUpdated {
|
|||
|
||||
impl MessageReactionUpdated {
|
||||
#[must_use]
|
||||
pub fn actor_chat(&self) -> Option<&Chat> {
|
||||
self.actor_chat.as_ref()
|
||||
pub fn chat(&self) -> Option<&Chat> {
|
||||
self.actor.chat()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn user(&self) -> Option<&User> {
|
||||
self.user.as_ref()
|
||||
self.actor.user()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ActorDe {
|
||||
/// The user that changed the reaction, if the user isn't anonymous
|
||||
user: Option<User>,
|
||||
/// The chat on behalf of which the reaction was changed, if the user is
|
||||
/// anonymous
|
||||
actor_chat: Option<Chat>,
|
||||
}
|
||||
|
||||
fn deserialize_actor<'d, D: Deserializer<'d>>(d: D) -> Result<MaybeAnonymousUser, D::Error> {
|
||||
let ActorDe { user, actor_chat } = ActorDe::deserialize(d)?;
|
||||
|
||||
Ok(actor_chat.map(MaybeAnonymousUser::Chat).or(user.map(MaybeAnonymousUser::User)).unwrap())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn deserialize() {
|
||||
fn deserialize_user() {
|
||||
let data = r#"
|
||||
{
|
||||
"chat": {
|
||||
|
@ -77,6 +91,37 @@ mod tests {
|
|||
]
|
||||
}
|
||||
"#;
|
||||
serde_json::from_str::<MessageReactionUpdated>(data).unwrap();
|
||||
let message_reaction_update = serde_json::from_str::<MessageReactionUpdated>(data).unwrap();
|
||||
|
||||
assert!(message_reaction_update.actor.is_user());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_chat() {
|
||||
let data = r#"{
|
||||
"chat": {
|
||||
"id": -1002199793788,
|
||||
"title": "тест",
|
||||
"type": "supergroup"
|
||||
},
|
||||
"message_id": 2,
|
||||
"actor_chat": {
|
||||
"id": -1002199793788,
|
||||
"title": "тест",
|
||||
"type": "supergroup"
|
||||
},
|
||||
"date": 1723798597,
|
||||
"old_reaction": [
|
||||
{
|
||||
"type": "emoji",
|
||||
"emoji": "❤"
|
||||
}
|
||||
],
|
||||
"new_reaction": []
|
||||
}"#;
|
||||
|
||||
let message_reaction_update = serde_json::from_str::<MessageReactionUpdated>(data).unwrap();
|
||||
|
||||
assert!(message_reaction_update.actor.is_chat())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
use crate::types::{Chat, User};
|
||||
use crate::types::{Chat, MaybeAnonymousUser, User};
|
||||
|
||||
#[serde_with::skip_serializing_none]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct PollAnswer {
|
||||
// FIXME: PollId
|
||||
/// Unique poll identifier.
|
||||
pub poll_id: String,
|
||||
|
||||
|
@ -14,7 +15,7 @@ pub struct PollAnswer {
|
|||
/// 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,
|
||||
pub voter: MaybeAnonymousUser,
|
||||
|
||||
/// 0-based identifiers of answer options, chosen by the user.
|
||||
///
|
||||
|
@ -22,31 +23,6 @@ pub struct PollAnswer {
|
|||
pub option_ids: Vec<u8>,
|
||||
}
|
||||
|
||||
#[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`
|
||||
|
@ -61,9 +37,9 @@ struct VoterDe {
|
|||
pub user: Option<User>,
|
||||
}
|
||||
|
||||
fn deserialize_voter<'d, D: Deserializer<'d>>(d: D) -> Result<Voter, D::Error> {
|
||||
fn deserialize_voter<'d, D: Deserializer<'d>>(d: D) -> Result<MaybeAnonymousUser, D::Error> {
|
||||
let VoterDe { voter_chat, user } = VoterDe::deserialize(d)?;
|
||||
Ok(voter_chat.map(Voter::Chat).or(user.map(Voter::User)).unwrap())
|
||||
Ok(voter_chat.map(MaybeAnonymousUser::Chat).or(user.map(MaybeAnonymousUser::User)).unwrap())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -71,21 +47,22 @@ mod tests {
|
|||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_poll_answer_with_user_de() {
|
||||
fn poll_answer_with_user_de() {
|
||||
let json = r#"{
|
||||
"poll_id":"POLL_ID",
|
||||
"user": {"id":42,"is_bot":false,"first_name":"blah"},
|
||||
"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(_)));
|
||||
|
||||
assert!(poll_answer.voter.is_user());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_poll_answer_with_voter_chat_de() {
|
||||
fn poll_answer_with_voter_chat_de() {
|
||||
let json = r#"{
|
||||
"poll_id":"POLL_ID",
|
||||
"poll_id": "POLL_ID",
|
||||
"voter_chat": {
|
||||
"id": -1001160242915,
|
||||
"title": "a",
|
||||
|
@ -95,11 +72,11 @@ mod tests {
|
|||
}"#;
|
||||
|
||||
let poll_answer: PollAnswer = serde_json::from_str(json).unwrap();
|
||||
assert!(matches!(poll_answer.voter, Voter::Chat(_)));
|
||||
assert!(poll_answer.voter.is_chat());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_poll_answer_with_both_user_and_voter_chat_de() {
|
||||
fn poll_answer_with_both_user_and_voter_chat_de() {
|
||||
let json = r#"{
|
||||
"poll_id":"POLL_ID",
|
||||
"voter_chat": {
|
||||
|
@ -107,11 +84,11 @@ mod tests {
|
|||
"title": "a",
|
||||
"type": "group"
|
||||
},
|
||||
"user": {"id":136817688,"is_bot":true,"first_name":"Channel_Bot"},
|
||||
"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(_)));
|
||||
assert!(poll_answer.voter.is_chat());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -469,13 +469,14 @@ mod test {
|
|||
use crate::types::{
|
||||
Chat, ChatBoost, ChatBoostRemoved, ChatBoostSource, ChatBoostSourcePremium,
|
||||
ChatBoostUpdated, ChatFullInfo, ChatId, ChatKind, ChatPrivate, ChatPublic,
|
||||
LinkPreviewOptions, MediaKind, MediaText, Message, MessageCommon, MessageId, MessageKind,
|
||||
MessageReactionCountUpdated, MessageReactionUpdated, PublicChatChannel, PublicChatKind,
|
||||
PublicChatSupergroup, ReactionCount, ReactionType, Update, UpdateId, UpdateKind, User,
|
||||
UserId,
|
||||
LinkPreviewOptions, MaybeAnonymousUser, MediaKind, MediaText, Message, MessageCommon,
|
||||
MessageId, MessageKind, MessageReactionCountUpdated, MessageReactionUpdated,
|
||||
PublicChatChannel, PublicChatKind, PublicChatSupergroup, ReactionCount, ReactionType,
|
||||
Update, UpdateId, UpdateKind, User, UserId,
|
||||
};
|
||||
|
||||
use chrono::DateTime;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
// TODO: more tests for deserialization
|
||||
#[test]
|
||||
|
@ -892,7 +893,7 @@ mod test {
|
|||
chat_full_info: ChatFullInfo::default(),
|
||||
},
|
||||
message_id: MessageId(35),
|
||||
user: Some(User {
|
||||
actor: MaybeAnonymousUser::User(User {
|
||||
id: UserId(1459074222),
|
||||
is_bot: false,
|
||||
first_name: "shadowchain".to_owned(),
|
||||
|
@ -902,7 +903,6 @@ mod test {
|
|||
is_premium: true,
|
||||
added_to_attachment_menu: false,
|
||||
}),
|
||||
actor_chat: None,
|
||||
date: DateTime::from_timestamp(1721306082, 0).unwrap(),
|
||||
old_reaction: vec![],
|
||||
new_reaction: vec![ReactionType::Emoji { emoji: "🌭".to_owned() }],
|
||||
|
@ -911,6 +911,78 @@ mod test {
|
|||
|
||||
let actual = serde_json::from_str::<Update>(json).unwrap();
|
||||
assert_eq!(expected, actual);
|
||||
|
||||
let json = r#"
|
||||
{
|
||||
"update_id": 767844136,
|
||||
"message_reaction": {
|
||||
"chat": {
|
||||
"id": -1002199793788,
|
||||
"title": "тест",
|
||||
"type": "supergroup"
|
||||
},
|
||||
"message_id": 2,
|
||||
"actor_chat": {
|
||||
"id": -1002199793788,
|
||||
"title": "тест",
|
||||
"type": "supergroup"
|
||||
},
|
||||
"date": 1723798597,
|
||||
"old_reaction": [
|
||||
{
|
||||
"type": "emoji",
|
||||
"emoji": "❤"
|
||||
}
|
||||
],
|
||||
"new_reaction": []
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let chat = Chat {
|
||||
id: ChatId(-1002199793788),
|
||||
kind: ChatKind::Public(ChatPublic {
|
||||
title: Some("тест".to_owned()),
|
||||
kind: PublicChatKind::Supergroup(PublicChatSupergroup {
|
||||
username: None,
|
||||
active_usernames: None,
|
||||
is_forum: false,
|
||||
sticker_set_name: None,
|
||||
can_set_sticker_set: None,
|
||||
permissions: None,
|
||||
slow_mode_delay: None,
|
||||
linked_chat_id: None,
|
||||
location: None,
|
||||
join_to_send_messages: None,
|
||||
join_by_request: None,
|
||||
custom_emoji_sticker_set_name: None,
|
||||
unrestrict_boost_count: None,
|
||||
}),
|
||||
description: None,
|
||||
invite_link: None,
|
||||
has_protected_content: None,
|
||||
}),
|
||||
photo: None,
|
||||
available_reactions: None,
|
||||
pinned_message: None,
|
||||
message_auto_delete_time: None,
|
||||
has_hidden_members: false,
|
||||
has_aggressive_anti_spam_enabled: false,
|
||||
chat_full_info: ChatFullInfo::default(),
|
||||
};
|
||||
let expected = Update {
|
||||
id: UpdateId(767844136),
|
||||
kind: UpdateKind::MessageReaction(MessageReactionUpdated {
|
||||
chat: chat.clone(),
|
||||
message_id: MessageId(2),
|
||||
actor: MaybeAnonymousUser::Chat(chat),
|
||||
date: DateTime::from_timestamp(1723798597, 0).unwrap(),
|
||||
old_reaction: vec![ReactionType::Emoji { emoji: "❤".to_owned() }],
|
||||
new_reaction: vec![],
|
||||
}),
|
||||
};
|
||||
|
||||
let actual = serde_json::from_str::<Update>(json).unwrap();
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Reference in a new issue