From 27ef3409c28b1042c1bc358e169dbc0d61c404b4 Mon Sep 17 00:00:00 2001 From: puh <puhovik@protonmail.com> Date: Sun, 5 Feb 2023 17:15:39 +0300 Subject: [PATCH] add macro to implement Deserialize on ApiError enum --- crates/teloxide-core/src/errors.rs | 364 ++++++++++++----------------- crates/teloxide/tests/errors.rs | 212 +++++++++++++++++ 2 files changed, 361 insertions(+), 215 deletions(-) create mode 100644 crates/teloxide/tests/errors.rs diff --git a/crates/teloxide-core/src/errors.rs b/crates/teloxide-core/src/errors.rs index 41acffdf..882433ef 100644 --- a/crates/teloxide-core/src/errors.rs +++ b/crates/teloxide-core/src/errors.rs @@ -2,7 +2,6 @@ use std::{io, time::Duration}; -use serde::Deserialize; use thiserror::Error; use crate::types::ResponseParameters; @@ -89,21 +88,93 @@ impl AsResponseParameters for crate::RequestError { } } +macro_rules! match_prefix { + ("") => {{ + |data: &str| Some(data.to_owned()) + }}; + ($prefix: literal) => {{ + |data: &str| { + if data.starts_with($prefix) { + Some(data.to_owned()) + } else { + None + } + } + }}; +} + +macro_rules! impl_api_error { + ( + $(#[$meta: meta])* + $vis: vis enum $ident: ident { + $($(#[$var_meta: meta])* + $var_name: ident$(($var_inner: ty))? = $var_string: literal $(with $var_parser: block)?),* } ) => { + + $(#[$meta])* + #[derive(Error)] + $vis enum $ident { + $( + + $(#[$var_meta])* + #[error($var_string)] + $var_name $(($var_inner))*, + + )* + } + + const _: () = { + struct Visitor; + + impl<'de> ::serde::de::Visitor<'de> for Visitor { + type Value = $ident; + + fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { + formatter.write_str("telegram api error string") + } + + fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> + where + E: ::serde::de::Error, + { + $(impl_api_error!(@de v, $var_name, $var_string $(, $var_parser)*);)* + Err(E::unknown_variant(v, &[])) + } + } + + impl<'de> ::serde::de::Deserialize<'de> for $ident { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: ::serde::de::Deserializer<'de>, + { + deserializer.deserialize_str(Visitor) + } + } + }; + }; + (@de $value: ident, $variant: ident, $val: literal) => { + if $value == $val { + return Ok(Self::Value::$variant) + } + }; + (@de $value: ident, $variant: ident, $val: literal, $block: expr) => { + match $block($value) { + Some(data) => return Ok(Self::Value::$variant(data)), + _ => {} + } + }; +} + +impl_api_error! { /// A kind of an API error. -#[derive(Debug, Error, Deserialize, PartialEq, Hash, Eq, Clone)] -#[serde(field_identifier)] +#[derive(Debug, PartialEq, Hash, Eq, Clone)] #[non_exhaustive] pub enum ApiError { /// Occurs when the bot tries to send message to user who blocked the bot. - #[serde(rename = "Forbidden: bot was blocked by the user")] - #[error("Forbidden: bot was blocked by the user")] - BotBlocked, + BotBlocked = "Forbidden: bot was blocked by the user", /// Occurs when the bot token is incorrect. // FIXME: rename this to something akin "InvalidToken" - #[serde(rename = "Unauthorized")] - #[error("Unauthorized")] - NotFound, + NotFound = "Unauthorized", /// Occurs when bot tries to modify a message without modification content. /// @@ -111,14 +182,8 @@ pub enum ApiError { /// 1. [`EditMessageText`] /// /// [`EditMessageText`]: crate::payloads::EditMessageText - #[serde(rename = "Bad Request: message is not modified: specified new message content and \ - reply markup are exactly the same as a current content and reply markup \ - of the message")] - #[error( - "Bad Request: message is not modified: specified new message content and reply markup are \ - exactly the same as a current content and reply markup of the message" - )] - MessageNotModified, + MessageNotModified = "Bad Request: message is not modified: specified new message content and reply markup are \ + exactly the same as a current content and reply markup of the message", /// Occurs when bot tries to forward or delete a message which was deleted. /// @@ -128,9 +193,7 @@ pub enum ApiError { /// /// [`ForwardMessage`]: crate::payloads::ForwardMessage /// [`DeleteMessage`]: crate::payloads::DeleteMessage - #[serde(rename = "Bad Request: MESSAGE_ID_INVALID")] - #[error("Bad Request: MESSAGE_ID_INVALID")] - MessageIdInvalid, + MessageIdInvalid = "Bad Request: MESSAGE_ID_INVALID", /// Occurs when bot tries to forward a message which does not exists. /// @@ -138,9 +201,7 @@ pub enum ApiError { /// 1. [`ForwardMessage`] /// /// [`ForwardMessage`]: crate::payloads::ForwardMessage - #[serde(rename = "Bad Request: message to forward not found")] - #[error("Bad Request: message to forward not found")] - MessageToForwardNotFound, + MessageToForwardNotFound = "Bad Request: message to forward not found", /// Occurs when bot tries to delete a message which does not exists. /// @@ -148,9 +209,7 @@ pub enum ApiError { /// 1. [`DeleteMessage`] /// /// [`DeleteMessage`]: crate::payloads::DeleteMessage - #[serde(rename = "Bad Request: message to delete not found")] - #[error("Bad Request: message to delete not found")] - MessageToDeleteNotFound, + MessageToDeleteNotFound = "Bad Request: message to delete not found", /// Occurs when bot tries to send a text message without text. /// @@ -158,9 +217,7 @@ pub enum ApiError { /// 1. [`SendMessage`] /// /// [`SendMessage`]: crate::payloads::SendMessage - #[serde(rename = "Bad Request: message text is empty")] - #[error("Bad Request: message text is empty")] - MessageTextIsEmpty, + MessageTextIsEmpty = "Bad Request: message text is empty", /// Occurs when bot tries to edit a message after long time. /// @@ -168,9 +225,7 @@ pub enum ApiError { /// 1. [`EditMessageText`] /// /// [`EditMessageText`]: crate::payloads::EditMessageText - #[serde(rename = "Bad Request: message can't be edited")] - #[error("Bad Request: message can't be edited")] - MessageCantBeEdited, + MessageCantBeEdited = "Bad Request: message can't be edited", /// Occurs when bot tries to delete a someone else's message in group where /// it does not have enough rights. @@ -179,9 +234,7 @@ pub enum ApiError { /// 1. [`DeleteMessage`] /// /// [`DeleteMessage`]: crate::payloads::DeleteMessage - #[serde(rename = "Bad Request: message can't be deleted")] - #[error("Bad Request: message can't be deleted")] - MessageCantBeDeleted, + MessageCantBeDeleted = "Bad Request: message can't be deleted", /// Occurs when bot tries to edit a message which does not exists. /// @@ -189,9 +242,7 @@ pub enum ApiError { /// 1. [`EditMessageText`] /// /// [`EditMessageText`]: crate::payloads::EditMessageText - #[serde(rename = "Bad Request: message to edit not found")] - #[error("Bad Request: message to edit not found")] - MessageToEditNotFound, + MessageToEditNotFound = "Bad Request: message to edit not found", /// Occurs when bot tries to reply to a message which does not exists. /// @@ -199,14 +250,10 @@ pub enum ApiError { /// 1. [`SendMessage`] /// /// [`SendMessage`]: crate::payloads::SendMessage - #[serde(rename = "Bad Request: reply message not found")] - #[error("Bad Request: reply message not found")] - MessageToReplyNotFound, + MessageToReplyNotFound = "Bad Request: reply message not found", /// Occurs when bot tries to - #[serde(rename = "Bad Request: message identifier is not specified")] - #[error("Bad Request: message identifier is not specified")] - MessageIdentifierNotSpecified, + MessageIdentifierNotSpecified = "Bad Request: message identifier is not specified", /// Occurs when bot tries to send a message with text size greater then /// 4096 symbols. @@ -215,9 +262,7 @@ pub enum ApiError { /// 1. [`SendMessage`] /// /// [`SendMessage`]: crate::payloads::SendMessage - #[serde(rename = "Bad Request: message is too long")] - #[error("Bad Request: message is too long")] - MessageIsTooLong, + MessageIsTooLong = "Bad Request: message is too long", /// Occurs when bot tries to edit a message with text size greater then /// 4096 symbols. @@ -232,9 +277,7 @@ pub enum ApiError { /// [`EditMessageTextInline`]: crate::payloads::EditMessageTextInline /// [`EditMessageCaption`]: crate::payloads::EditMessageCaption /// [`EditMessageCaptionInline`]: crate::payloads::EditMessageCaptionInline - #[serde(rename = "Bad Request: MESSAGE_TOO_LONG")] - #[error("Bad Request: MESSAGE_TOO_LONG")] - EditedMessageIsTooLong, + EditedMessageIsTooLong = "Bad Request: MESSAGE_TOO_LONG", /// Occurs when bot tries to send media group with more than 10 items. /// @@ -242,9 +285,7 @@ pub enum ApiError { /// 1. [`SendMediaGroup`] /// /// [`SendMediaGroup`]: crate::payloads::SendMediaGroup - #[serde(rename = "Bad Request: Too much messages to send as an album")] - #[error("Bad Request: Too much messages to send as an album")] - ToMuchMessages, + ToMuchMessages = "Bad Request: Too much messages to send as an album", /// Occurs when bot tries to answer an inline query with more than 50 /// results. @@ -255,9 +296,7 @@ pub enum ApiError { /// 1. [`AnswerInlineQuery`] /// /// [`AnswerInlineQuery`]: crate::payloads::AnswerInlineQuery - #[serde(rename = "Bad Request: RESULTS_TOO_MUCH")] - #[error("Bad Request: RESULTS_TOO_MUCH")] - TooMuchInlineQueryResults, + TooMuchInlineQueryResults = "Bad Request: RESULTS_TOO_MUCH", /// Occurs when bot tries to stop poll that has already been stopped. /// @@ -265,9 +304,7 @@ pub enum ApiError { /// 1. [`SendPoll`] /// /// [`SendPoll`]: crate::payloads::SendPoll - #[serde(rename = "Bad Request: poll has already been closed")] - #[error("Bad Request: poll has already been closed")] - PollHasAlreadyClosed, + PollHasAlreadyClosed = "Bad Request: poll has already been closed", /// Occurs when bot tries to send poll with less than 2 options. /// @@ -275,9 +312,7 @@ pub enum ApiError { /// 1. [`SendPoll`] /// /// [`SendPoll`]: crate::payloads::SendPoll - #[serde(rename = "Bad Request: poll must have at least 2 option")] - #[error("Bad Request: poll must have at least 2 option")] - PollMustHaveMoreOptions, + PollMustHaveMoreOptions = "Bad Request: poll must have at least 2 option", /// Occurs when bot tries to send poll with more than 10 options. /// @@ -285,9 +320,7 @@ pub enum ApiError { /// 1. [`SendPoll`] /// /// [`SendPoll`]: crate::payloads::SendPoll - #[serde(rename = "Bad Request: poll can't have more than 10 options")] - #[error("Bad Request: poll can't have more than 10 options")] - PollCantHaveMoreOptions, + PollCantHaveMoreOptions = "Bad Request: poll can't have more than 10 options", /// Occurs when bot tries to send poll with empty option (without text). /// @@ -295,9 +328,7 @@ pub enum ApiError { /// 1. [`SendPoll`] /// /// [`SendPoll`]: crate::payloads::SendPoll - #[serde(rename = "Bad Request: poll options must be non-empty")] - #[error("Bad Request: poll options must be non-empty")] - PollOptionsMustBeNonEmpty, + PollOptionsMustBeNonEmpty = "Bad Request: poll options must be non-empty", /// Occurs when bot tries to send poll with empty question (without text). /// @@ -305,9 +336,7 @@ pub enum ApiError { /// 1. [`SendPoll`] /// /// [`SendPoll`]: crate::payloads::SendPoll - #[serde(rename = "Bad Request: poll question must be non-empty")] - #[error("Bad Request: poll question must be non-empty")] - PollQuestionMustBeNonEmpty, + PollQuestionMustBeNonEmpty = "Bad Request: poll question must be non-empty", /// Occurs when bot tries to send poll with total size of options more than /// 100 symbols. @@ -316,9 +345,7 @@ pub enum ApiError { /// 1. [`SendPoll`] /// /// [`SendPoll`]: crate::payloads::SendPoll - #[serde(rename = "Bad Request: poll options length must not exceed 100")] - #[error("Bad Request: poll options length must not exceed 100")] - PollOptionsLengthTooLong, + PollOptionsLengthTooLong = "Bad Request: poll options length must not exceed 100", /// Occurs when bot tries to send poll with question size more than 255 /// symbols. @@ -327,9 +354,7 @@ pub enum ApiError { /// 1. [`SendPoll`] /// /// [`SendPoll`]: crate::payloads::SendPoll - #[serde(rename = "Bad Request: poll question length must not exceed 255")] - #[error("Bad Request: poll question length must not exceed 255")] - PollQuestionLengthTooLong, + PollQuestionLengthTooLong = "Bad Request: poll question length must not exceed 255", /// Occurs when bot tries to stop poll with message without poll. /// @@ -337,9 +362,7 @@ pub enum ApiError { /// 1. [`StopPoll`] /// /// [`StopPoll`]: crate::payloads::StopPoll - #[serde(rename = "Bad Request: message with poll to stop not found")] - #[error("Bad Request: message with poll to stop not found")] - MessageWithPollNotFound, + MessageWithPollNotFound = "Bad Request: message with poll to stop not found", /// Occurs when bot tries to stop poll with message without poll. /// @@ -347,9 +370,7 @@ pub enum ApiError { /// 1. [`StopPoll`] /// /// [`StopPoll`]: crate::payloads::StopPoll - #[serde(rename = "Bad Request: message is not a poll")] - #[error("Bad Request: message is not a poll")] - MessageIsNotAPoll, + MessageIsNotAPoll = "Bad Request: message is not a poll", /// Occurs when bot tries to send a message to chat in which it is not a /// member. @@ -358,9 +379,7 @@ pub enum ApiError { /// 1. [`SendMessage`] /// /// [`SendMessage`]: crate::payloads::SendMessage - #[serde(rename = "Bad Request: chat not found")] - #[error("Bad Request: chat not found")] - ChatNotFound, + ChatNotFound = "Bad Request: chat not found", /// Occurs when bot tries to send method with unknown user_id. /// @@ -369,9 +388,7 @@ pub enum ApiError { /// /// [`getUserProfilePhotos`]: /// crate::payloads::GetUserProfilePhotos - #[serde(rename = "Bad Request: user not found")] - #[error("Bad Request: user not found")] - UserNotFound, + UserNotFound = "Bad Request: user not found", /// Occurs when bot tries to send [`SetChatDescription`] with same text as /// in the current description. @@ -380,9 +397,7 @@ pub enum ApiError { /// 1. [`SetChatDescription`] /// /// [`SetChatDescription`]: crate::payloads::SetChatDescription - #[serde(rename = "Bad Request: chat description is not modified")] - #[error("Bad Request: chat description is not modified")] - ChatDescriptionIsNotModified, + ChatDescriptionIsNotModified = "Bad Request: chat description is not modified", /// Occurs when bot tries to answer to query after timeout expire. /// @@ -390,10 +405,7 @@ pub enum ApiError { /// 1. [`AnswerCallbackQuery`] /// /// [`AnswerCallbackQuery`]: crate::payloads::AnswerCallbackQuery - #[serde(rename = "Bad Request: query is too old and response timeout expired or query id is \ - invalid")] - #[error("Bad Request: query is too old and response timeout expired or query id is invalid")] - InvalidQueryId, + InvalidQueryId = "Bad Request: query is too old and response timeout expired or query id is invalid", /// Occurs when bot tries to send InlineKeyboardMarkup with invalid button /// url. @@ -402,9 +414,7 @@ pub enum ApiError { /// 1. [`SendMessage`] /// /// [`SendMessage`]: crate::payloads::SendMessage - #[serde(rename = "Bad Request: BUTTON_URL_INVALID")] - #[error("Bad Request: BUTTON_URL_INVALID")] - ButtonUrlInvalid, + ButtonUrlInvalid = "Bad Request: BUTTON_URL_INVALID", /// Occurs when bot tries to send button with data size more than 64 bytes. /// @@ -412,9 +422,7 @@ pub enum ApiError { /// 1. [`SendMessage`] /// /// [`SendMessage`]: crate::payloads::SendMessage - #[serde(rename = "Bad Request: BUTTON_DATA_INVALID")] - #[error("Bad Request: BUTTON_DATA_INVALID")] - ButtonDataInvalid, + ButtonDataInvalid = "Bad Request: BUTTON_DATA_INVALID", /// Occurs when bot tries to send button with data size == 0. /// @@ -422,13 +430,8 @@ pub enum ApiError { /// 1. [`SendMessage`] /// /// [`SendMessage`]: crate::payloads::SendMessage - #[serde(rename = "Bad Request: can't parse inline keyboard button: Text buttons are \ - unallowed in the inline keyboard")] - #[error( - "Bad Request: can't parse inline keyboard button: Text buttons are unallowed in the \ - inline keyboard" - )] - TextButtonsAreUnallowed, + TextButtonsAreUnallowed = "Bad Request: can't parse inline keyboard button: Text buttons are unallowed in the \ + inline keyboard", /// Occurs when bot tries to get file by wrong file id. /// @@ -436,34 +439,24 @@ pub enum ApiError { /// 1. [`GetFile`] /// /// [`GetFile`]: crate::payloads::GetFile - #[serde(rename = "Bad Request: wrong file id")] - #[error("Bad Request: wrong file id")] - WrongFileId, + WrongFileId = "Bad Request: wrong file id", /// Occurs when bot tries to send files with wrong file identifier or HTTP /// url - #[serde(rename = "Bad Request: wrong file identifier/HTTP URL specified")] - #[error("Bad Request: wrong file identifier/HTTP URL specified")] - WrongFileIdOrUrl, + WrongFileIdOrUrl = "Bad Request: wrong file identifier/HTTP URL specified", /// Occurs when When sending files with an url to a site that doesn't /// respond. - #[serde(rename = "Bad Request: failed to get HTTP URL content")] - #[error("Bad Request: failed to get HTTP URL content")] - FailedToGetUrlContent, + FailedToGetUrlContent = "Bad Request: failed to get HTTP URL content", /// Occurs when bot tries to do some with group which was deactivated. - #[serde(rename = "Bad Request: group is deactivated")] - #[error("Bad Request: group is deactivated")] - GroupDeactivated, + GroupDeactivated = "Bad Request: group is deactivated", /// Occurs when image processing fails on telegram's side. /// /// This is likely caused by an incorrectly encoded image, make sure that /// the image is correctly encoded in a format telegram accepts. - #[serde(rename = "Bad Request: IMAGE_PROCESS_FAILED")] - #[error("Bad Request: IMAGE_PROCESS_FAILED")] - ImageProcessFailed, + ImageProcessFailed = "Bad Request: IMAGE_PROCESS_FAILED", /// Occurs when bot tries to set chat photo from file ID /// @@ -471,9 +464,7 @@ pub enum ApiError { /// 1. [`SetChatPhoto`] /// /// [`SetChatPhoto`]: crate::payloads::SetChatPhoto - #[serde(rename = "Bad Request: Photo should be uploaded as an InputFile")] - #[error("Bad Request: Photo should be uploaded as an InputFile")] - PhotoAsInputFileRequired, + PhotoAsInputFileRequired = "Bad Request: Photo should be uploaded as an InputFile", /// Occurs when bot tries to add sticker to stickerset by invalid name. /// @@ -481,9 +472,7 @@ pub enum ApiError { /// 1. [`AddStickerToSet`] /// /// [`AddStickerToSet`]: crate::payloads::AddStickerToSet - #[serde(rename = "Bad Request: STICKERSET_INVALID")] - #[error("Bad Request: STICKERSET_INVALID")] - InvalidStickersSet, + InvalidStickersSet = "Bad Request: STICKERSET_INVALID", /// Occurs when bot tries to create a sticker set with a name that is /// already used by another sticker set. @@ -492,9 +481,7 @@ pub enum ApiError { /// 1. [`CreateNewStickerSet`] /// /// [`CreateNewStickerSet`]: crate::payloads::CreateNewStickerSet - #[serde(rename = "Bad Request: sticker set name is already occupied")] - #[error("Bad Request: sticker set name is already occupied")] - StickerSetNameOccupied, + StickerSetNameOccupied = "Bad Request: sticker set name is already occupied", /// Occurs when bot tries to create a sticker set with user id of a bot. /// @@ -502,9 +489,7 @@ pub enum ApiError { /// 1. [`CreateNewStickerSet`] /// /// [`CreateNewStickerSet`]: crate::payloads::CreateNewStickerSet - #[serde(rename = "Bad Request: USER_IS_BOT")] - #[error("Bad Request: USER_IS_BOT")] - StickerSetOwnerIsBot, + StickerSetOwnerIsBot = "Bad Request: USER_IS_BOT", /// Occurs when bot tries to create a sticker set with invalid name. /// @@ -519,9 +504,7 @@ pub enum ApiError { /// 1. [`CreateNewStickerSet`] /// /// [`CreateNewStickerSet`]: crate::payloads::CreateNewStickerSet - #[serde(rename = "Bad Request: invalid sticker set name is specified")] - #[error("Bad Request: invalid sticker set name is specified")] - InvalidStickerName, + InvalidStickerName = "Bad Request: invalid sticker set name is specified", /// Occurs when bot tries to pin a message without rights to pin in this /// chat. @@ -530,9 +513,7 @@ pub enum ApiError { /// 1. [`PinChatMessage`] /// /// [`PinChatMessage`]: crate::payloads::PinChatMessage - #[serde(rename = "Bad Request: not enough rights to pin a message")] - #[error("Bad Request: not enough rights to pin a message")] - NotEnoughRightsToPinMessage, + NotEnoughRightsToPinMessage = "Bad Request: not enough rights to pin a message", /// Occurs when bot tries to pin or unpin a message without rights to pin /// in this chat. @@ -543,9 +524,7 @@ pub enum ApiError { /// /// [`PinChatMessage`]: crate::payloads::PinChatMessage /// [`UnpinChatMessage`]: crate::payloads::UnpinChatMessage - #[serde(rename = "Bad Request: not enough rights to manage pinned messages in the chat")] - #[error("Bad Request: not enough rights to manage pinned messages in the chat")] - NotEnoughRightsToManagePins, + NotEnoughRightsToManagePins = "Bad Request: not enough rights to manage pinned messages in the chat", /// Occurs when bot tries change default chat permissions without "Ban /// Users" permission in this chat. @@ -554,15 +533,11 @@ pub enum ApiError { /// 1. [`SetChatPermissions`] /// /// [`SetChatPermissions`]: crate::payloads::SetChatPermissions - #[serde(rename = "Bad Request: not enough rights to change chat permissions")] - #[error("Bad Request: not enough rights to change chat permissions")] - NotEnoughRightsToChangeChatPermissions, + NotEnoughRightsToChangeChatPermissions = "Bad Request: not enough rights to change chat permissions", /// Occurs when bot tries to use method in group which is allowed only in a /// supergroup or channel. - #[serde(rename = "Bad Request: method is available only for supergroups and channel")] - #[error("Bad Request: method is available only for supergroups and channel")] - MethodNotAvailableInPrivateChats, + MethodNotAvailableInPrivateChats = "Bad Request: method is available only for supergroups and channel", /// Occurs when bot tries to demote chat creator. /// @@ -570,9 +545,7 @@ pub enum ApiError { /// 1. [`PromoteChatMember`] /// /// [`PromoteChatMember`]: crate::payloads::PromoteChatMember - #[serde(rename = "Bad Request: can't demote chat creator")] - #[error("Bad Request: can't demote chat creator")] - CantDemoteChatCreator, + CantDemoteChatCreator = "Bad Request: can't demote chat creator", /// Occurs when bot tries to restrict self in group chats. /// @@ -580,9 +553,7 @@ pub enum ApiError { /// 1. [`RestrictChatMember`] /// /// [`RestrictChatMember`]: crate::payloads::RestrictChatMember - #[serde(rename = "Bad Request: can't restrict self")] - #[error("Bad Request: can't restrict self")] - CantRestrictSelf, + CantRestrictSelf = "Bad Request: can't restrict self", /// Occurs when bot tries to restrict chat member without rights to /// restrict in this chat. @@ -591,15 +562,11 @@ pub enum ApiError { /// 1. [`RestrictChatMember`] /// /// [`RestrictChatMember`]: crate::payloads::RestrictChatMember - #[serde(rename = "Bad Request: not enough rights to restrict/unrestrict chat member")] - #[error("Bad Request: not enough rights to restrict/unrestrict chat member")] - NotEnoughRightsToRestrict, + NotEnoughRightsToRestrict = "Bad Request: not enough rights to restrict/unrestrict chat member", /// Occurs when bot tries to post a message in a channel without "Post /// Messages" admin right. - #[serde(rename = "Bad Request: need administrator rights in the channel chat")] - #[error("Bad Request: need administrator rights in the channel chat")] - NotEnoughRightsToPostMessages, + NotEnoughRightsToPostMessages = "Bad Request: need administrator rights in the channel chat", /// Occurs when bot tries set webhook to protocol other than HTTPS. /// @@ -607,9 +574,7 @@ pub enum ApiError { /// 1. [`SetWebhook`] /// /// [`SetWebhook`]: crate::payloads::SetWebhook - #[serde(rename = "Bad Request: bad webhook: HTTPS url must be provided for webhook")] - #[error("Bad Request: bad webhook: HTTPS url must be provided for webhook")] - WebhookRequireHttps, + WebhookRequireHttps = "Bad Request: bad webhook: HTTPS url must be provided for webhook", /// Occurs when bot tries to set webhook to port other than 80, 88, 443 or /// 8443. @@ -618,10 +583,7 @@ pub enum ApiError { /// 1. [`SetWebhook`] /// /// [`SetWebhook`]: crate::payloads::SetWebhook - #[serde(rename = "Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 \ - or 8443")] - #[error("Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 or 8443")] - BadWebhookPort, + BadWebhookPort = "Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 or 8443", /// Occurs when bot tries to set webhook to unknown host. /// @@ -629,9 +591,7 @@ pub enum ApiError { /// 1. [`SetWebhook`] /// /// [`SetWebhook`]: crate::payloads::SetWebhook - #[serde(rename = "Bad Request: bad webhook: Failed to resolve host: Name or service not known")] - #[error("Bad Request: bad webhook: Failed to resolve host: Name or service not known")] - UnknownHost, + UnknownHost = "Bad Request: bad webhook: Failed to resolve host: Name or service not known", /// Occurs when bot tries to set webhook to invalid URL. /// @@ -639,9 +599,7 @@ pub enum ApiError { /// 1. [`SetWebhook`] /// /// [`SetWebhook`]: crate::payloads::SetWebhook - #[serde(rename = "Bad Request: can't parse URL")] - #[error("Bad Request: can't parse URL")] - CantParseUrl, + CantParseUrl = "Bad Request: can't parse URL", /// Occurs when bot tries to send message with unfinished entities. /// @@ -649,9 +607,7 @@ pub enum ApiError { /// 1. [`SendMessage`] /// /// [`SendMessage`]: crate::payloads::SendMessage - #[serde(rename = "Bad Request: can't parse entities")] - #[error("Bad Request: can't parse entities")] - CantParseEntities, + CantParseEntities(String) = "{0}" with {match_prefix!("Bad Request: can't parse entities")}, /// Occurs when bot tries to use getUpdates while webhook is active. /// @@ -659,9 +615,7 @@ pub enum ApiError { /// 1. [`GetUpdates`] /// /// [`GetUpdates`]: crate::payloads::GetUpdates - #[serde(rename = "can't use getUpdates method while webhook is active")] - #[error("can't use getUpdates method while webhook is active")] - CantGetUpdates, + CantGetUpdates = "can't use getUpdates method while webhook is active", /// Occurs when bot tries to do some in group where bot was kicked. /// @@ -669,9 +623,7 @@ pub enum ApiError { /// 1. [`SendMessage`] /// /// [`SendMessage`]: crate::payloads::SendMessage - #[serde(rename = "Unauthorized: bot was kicked from a chat")] - #[error("Unauthorized: bot was kicked from a chat")] - BotKicked, + BotKicked = "Unauthorized: bot was kicked from a chat", /// Occurs when bot tries to do something in a supergroup the bot was /// kicked from. @@ -680,9 +632,7 @@ pub enum ApiError { /// 1. [`SendMessage`] /// /// [`SendMessage`]: crate::payloads::SendMessage - #[serde(rename = "Forbidden: bot was kicked from the supergroup chat")] - #[error("Forbidden: bot was kicked from the supergroup chat")] - BotKickedFromSupergroup, + BotKickedFromSupergroup = "Forbidden: bot was kicked from the supergroup chat", /// Occurs when bot tries to send a message to a deactivated user (i.e. a /// user that was banned by telegram). @@ -691,9 +641,7 @@ pub enum ApiError { /// 1. [`SendMessage`] /// /// [`SendMessage`]: crate::payloads::SendMessage - #[serde(rename = "Forbidden: user is deactivated")] - #[error("Forbidden: user is deactivated")] - UserDeactivated, + UserDeactivated = "Forbidden: user is deactivated", /// Occurs when you tries to initiate conversation with a user. /// @@ -701,9 +649,7 @@ pub enum ApiError { /// 1. [`SendMessage`] /// /// [`SendMessage`]: crate::payloads::SendMessage - #[serde(rename = "Unauthorized: bot can't initiate conversation with a user")] - #[error("Unauthorized: bot can't initiate conversation with a user")] - CantInitiateConversation, + CantInitiateConversation = "Unauthorized: bot can't initiate conversation with a user", /// Occurs when you tries to send message to bot. /// @@ -711,9 +657,7 @@ pub enum ApiError { /// 1. [`SendMessage`] /// /// [`SendMessage`]: crate::payloads::SendMessage - #[serde(rename = "Unauthorized: bot can't send messages to bots")] - #[error("Unauthorized: bot can't send messages to bots")] - CantTalkWithBots, + CantTalkWithBots = "Unauthorized: bot can't send messages to bots", /// Occurs when bot tries to send button with invalid http url. /// @@ -721,9 +665,7 @@ pub enum ApiError { /// 1. [`SendMessage`] /// /// [`SendMessage`]: crate::payloads::SendMessage - #[serde(rename = "Bad Request: wrong HTTP URL")] - #[error("Bad Request: wrong HTTP URL")] - WrongHttpUrl, + WrongHttpUrl = "Bad Request: wrong HTTP URL", /// Occurs when bot tries GetUpdate before the timeout. Make sure that only /// one Updater is running. @@ -732,13 +674,8 @@ pub enum ApiError { /// 1. [`GetUpdates`] /// /// [`GetUpdates`]: crate::payloads::GetUpdates - #[serde(rename = "Conflict: terminated by other getUpdates request; make sure that only one \ - bot instance is running")] - #[error( - "Conflict: terminated by other getUpdates request; make sure that only one bot instance \ - is running" - )] - TerminatedByOtherGetUpdates, + TerminatedByOtherGetUpdates = "Conflict: terminated by other getUpdates request; make sure that only one bot instance \ + is running", /// Occurs when bot tries to get file by invalid file id. /// @@ -746,9 +683,7 @@ pub enum ApiError { /// 1. [`GetFile`] /// /// [`GetFile`]: crate::payloads::GetFile - #[serde(rename = "Bad Request: invalid file id")] - #[error("Bad Request: invalid file id")] - FileIdInvalid, + FileIdInvalid = "Bad Request: invalid file id", /// Occurs when bot tries to upload a file which is larger than 50 MB using /// multipart/form-data. @@ -759,9 +694,8 @@ pub enum ApiError { /// /// [`SendVideo`]: crate::payloads::SendVideo /// [`SendDocument`]: crate::payloads::SendDocument - #[serde(rename = "Request Entity Too Large")] - #[error("Request Entity Too Large")] - RequestEntityTooLarge, + RequestEntityTooLarge = "Request Entity Too Large", + /// Error which is not known to `teloxide`. /// @@ -769,8 +703,8 @@ pub enum ApiError { /// description of the error. /// /// [open an issue]: https://github.com/teloxide/teloxide/issues/new - #[error("Unknown error: {0:?}")] - Unknown(String), + Unknown(String) = "Unknown error: {0:?}" with {match_prefix!("")} +} } /// This impl allows to use `?` to propagate [`DownloadError`]s in function diff --git a/crates/teloxide/tests/errors.rs b/crates/teloxide/tests/errors.rs new file mode 100644 index 00000000..f0edcf78 --- /dev/null +++ b/crates/teloxide/tests/errors.rs @@ -0,0 +1,212 @@ +use serde::Deserialize; + +#[test] +fn custom_result() { + use teloxide_core::ApiError; + + let cases = &[ + ("{\"data\": \"Forbidden: bot was blocked by the user\"}", ApiError::BotBlocked), + ("{\"data\": \"Unauthorized\"}", ApiError::NotFound), + ( + "{\"data\": \"Bad Request: message is not modified: specified new message content and \ + reply markup are exactly the same as a current content and reply markup of the \ + message\"}", + ApiError::MessageNotModified, + ), + ("{\"data\": \"Bad Request: MESSAGE_ID_INVALID\"}", ApiError::MessageIdInvalid), + ( + "{\"data\": \"Bad Request: message to forward not found\"}", + ApiError::MessageToForwardNotFound, + ), + ( + "{\"data\": \"Bad Request: message to delete not found\"}", + ApiError::MessageToDeleteNotFound, + ), + ("{\"data\": \"Bad Request: message text is empty\"}", ApiError::MessageTextIsEmpty), + ("{\"data\": \"Bad Request: message can't be edited\"}", ApiError::MessageCantBeEdited), + ("{\"data\": \"Bad Request: message can't be deleted\"}", ApiError::MessageCantBeDeleted), + ("{\"data\": \"Bad Request: message to edit not found\"}", ApiError::MessageToEditNotFound), + ("{\"data\": \"Bad Request: reply message not found\"}", ApiError::MessageToReplyNotFound), + ( + "{\"data\": \"Bad Request: message identifier is not specified\"}", + ApiError::MessageIdentifierNotSpecified, + ), + ("{\"data\": \"Bad Request: message is too long\"}", ApiError::MessageIsTooLong), + ("{\"data\": \"Bad Request: MESSAGE_TOO_LONG\"}", ApiError::EditedMessageIsTooLong), + ( + "{\"data\": \"Bad Request: Too much messages to send as an album\"}", + ApiError::ToMuchMessages, + ), + ("{\"data\": \"Bad Request: RESULTS_TOO_MUCH\"}", ApiError::TooMuchInlineQueryResults), + ( + "{\"data\": \"Bad Request: poll has already been closed\"}", + ApiError::PollHasAlreadyClosed, + ), + ( + "{\"data\": \"Bad Request: poll must have at least 2 option\"}", + ApiError::PollMustHaveMoreOptions, + ), + ( + "{\"data\": \"Bad Request: poll can't have more than 10 options\"}", + ApiError::PollCantHaveMoreOptions, + ), + ( + "{\"data\": \"Bad Request: poll options must be non-empty\"}", + ApiError::PollOptionsMustBeNonEmpty, + ), + ( + "{\"data\": \"Bad Request: poll question must be non-empty\"}", + ApiError::PollQuestionMustBeNonEmpty, + ), + ( + "{\"data\": \"Bad Request: poll options length must not exceed 100\"}", + ApiError::PollOptionsLengthTooLong, + ), + ( + "{\"data\": \"Bad Request: poll question length must not exceed 255\"}", + ApiError::PollQuestionLengthTooLong, + ), + ( + "{\"data\": \"Bad Request: message with poll to stop not found\"}", + ApiError::MessageWithPollNotFound, + ), + ("{\"data\": \"Bad Request: message is not a poll\"}", ApiError::MessageIsNotAPoll), + ("{\"data\": \"Bad Request: chat not found\"}", ApiError::ChatNotFound), + ("{\"data\": \"Bad Request: user not found\"}", ApiError::UserNotFound), + ( + "{\"data\": \"Bad Request: chat description is not modified\"}", + ApiError::ChatDescriptionIsNotModified, + ), + ( + "{\"data\": \"Bad Request: query is too old and response timeout expired or query id \ + is invalid\"}", + ApiError::InvalidQueryId, + ), + ("{\"data\": \"Bad Request: BUTTON_URL_INVALID\"}", ApiError::ButtonUrlInvalid), + ("{\"data\": \"Bad Request: BUTTON_DATA_INVALID\"}", ApiError::ButtonDataInvalid), + ( + "{\"data\": \"Bad Request: can't parse inline keyboard button: Text buttons are \ + unallowed in the inline keyboard\"}", + ApiError::TextButtonsAreUnallowed, + ), + ("{\"data\": \"Bad Request: wrong file id\"}", ApiError::WrongFileId), + ( + "{\"data\": \"Bad Request: wrong file identifier/HTTP URL specified\"}", + ApiError::WrongFileIdOrUrl, + ), + ( + "{\"data\": \"Bad Request: failed to get HTTP URL content\"}", + ApiError::FailedToGetUrlContent, + ), + ("{\"data\": \"Bad Request: group is deactivated\"}", ApiError::GroupDeactivated), + ("{\"data\": \"Bad Request: IMAGE_PROCESS_FAILED\"}", ApiError::ImageProcessFailed), + ( + "{\"data\": \"Bad Request: Photo should be uploaded as an InputFile\"}", + ApiError::PhotoAsInputFileRequired, + ), + ("{\"data\": \"Bad Request: STICKERSET_INVALID\"}", ApiError::InvalidStickersSet), + ( + "{\"data\": \"Bad Request: sticker set name is already occupied\"}", + ApiError::StickerSetNameOccupied, + ), + ("{\"data\": \"Bad Request: USER_IS_BOT\"}", ApiError::StickerSetOwnerIsBot), + ( + "{\"data\": \"Bad Request: invalid sticker set name is specified\"}", + ApiError::InvalidStickerName, + ), + ( + "{\"data\": \"Bad Request: not enough rights to pin a message\"}", + ApiError::NotEnoughRightsToPinMessage, + ), + ( + "{\"data\": \"Bad Request: not enough rights to manage pinned messages in the chat\"}", + ApiError::NotEnoughRightsToManagePins, + ), + ( + "{\"data\": \"Bad Request: not enough rights to change chat permissions\"}", + ApiError::NotEnoughRightsToChangeChatPermissions, + ), + ( + "{\"data\": \"Bad Request: method is available only for supergroups and channel\"}", + ApiError::MethodNotAvailableInPrivateChats, + ), + ("{\"data\": \"Bad Request: can't demote chat creator\"}", ApiError::CantDemoteChatCreator), + ("{\"data\": \"Bad Request: can't restrict self\"}", ApiError::CantRestrictSelf), + ( + "{\"data\": \"Bad Request: not enough rights to restrict/unrestrict chat member\"}", + ApiError::NotEnoughRightsToRestrict, + ), + ( + "{\"data\": \"Bad Request: need administrator rights in the channel chat\"}", + ApiError::NotEnoughRightsToPostMessages, + ), + ( + "{\"data\": \"Bad Request: bad webhook: HTTPS url must be provided for webhook\"}", + ApiError::WebhookRequireHttps, + ), + ( + "{\"data\": \"Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, \ + 443 or 8443\"}", + ApiError::BadWebhookPort, + ), + ( + "{\"data\": \"Bad Request: bad webhook: Failed to resolve host: Name or service not \ + known\"}", + ApiError::UnknownHost, + ), + ("{\"data\": \"Bad Request: can't parse URL\"}", ApiError::CantParseUrl), + ( + "{\"data\": \"Bad Request: can't parse entities\"}", + ApiError::CantParseEntities( + "Bad Request: can't parse entities: SomeRandomString".to_owned(), + ), + ), + ( + "{\"data\": \"can't use getUpdates method while webhook is active\"}", + ApiError::CantGetUpdates, + ), + ("{\"data\": \"Unauthorized: bot was kicked from a chat\"}", ApiError::BotKicked), + ( + "{\"data\": \"Forbidden: bot was kicked from the supergroup chat\"}", + ApiError::BotKickedFromSupergroup, + ), + ("{\"data\": \"Forbidden: user is deactivated\"}", ApiError::UserDeactivated), + ( + "{\"data\": \"Unauthorized: bot can't initiate conversation with a user\"}", + ApiError::CantInitiateConversation, + ), + ( + "{\"data\": \"Unauthorized: bot can't send messages to bots\"}", + ApiError::CantTalkWithBots, + ), + ("{\"data\": \"Bad Request: wrong HTTP URL\"}", ApiError::WrongHttpUrl), + ( + "{\"data\": \"Conflict: terminated by other getUpdates request; make sure that only \ + one bot instance is running\"}", + ApiError::TerminatedByOtherGetUpdates, + ), + ("{\"data\": \"Bad Request: invalid file id\"}", ApiError::FileIdInvalid), + ("{\"data\": \"Request Entity Too Large\"}", ApiError::RequestEntityTooLarge), + ("{\"data\": \"RandomError\"}", ApiError::Unknown("RandomError".to_string())), + ]; + + #[derive(Deserialize, Debug)] + struct Res<T> { + data: T, + } + + for (data, expected) in cases { + let raw = serde_json::from_str::<Res<String>>(data).unwrap().data; + let parsed = serde_json::from_str::<Res<ApiError>>(data).unwrap().data; + assert_eq!(&parsed, expected); + + let expected_error_message = match parsed { + ApiError::Unknown(_) => { + format!("Unknown error: \"{raw}\"") + } + _ => raw, + }; + assert_eq!(parsed.to_string(), expected_error_message); + } +} +