Skip to main content

ronami_core/
errors.rs

1//! Possible error types.
2
3use std::{io, sync::Arc};
4
5use thiserror::Error;
6
7use crate::types::{ChatId, ResponseParameters, Seconds};
8
9/// An error caused by sending a request to Telegram.
10#[derive(Debug, Error, Clone)]
11pub enum RequestError {
12    /// A Telegram API error.
13    #[error("A Telegram's error: {0}")]
14    Api(#[from] ApiError),
15
16    /// The group has been migrated to a supergroup with the specified
17    /// identifier.
18    #[error("The group has been migrated to a supergroup with ID #{0}")]
19    MigrateToChatId(ChatId),
20
21    /// In case of exceeding flood control, the number of seconds left to wait
22    /// before the request can be repeated.
23    #[error("Retry after {0}")]
24    RetryAfter(Seconds),
25
26    /// Network error while sending a request to Telegram.
27    #[error("A network error: {0}")]
28    // NOTE: this variant must not be created by anything except the explicit From impl
29    Network(#[source] Arc<reqwest::Error>),
30
31    /// Error while parsing a response from Telegram.
32    ///
33    /// If you've received this error, please open an issue in the Ronami
34    /// repository with the description of the error.
35    #[error("An error while parsing JSON: {source} (raw: {raw:?})")]
36    InvalidJson {
37        #[source]
38        source: Arc<serde_json::Error>,
39        /// The raw string JSON that couldn't been parsed
40        raw: Box<str>,
41    },
42
43    /// Occurs when trying to send a file to Telegram.
44    #[error("An I/O error: {0}")]
45    Io(#[from] Arc<io::Error>),
46}
47
48/// An error caused by downloading a file.
49#[derive(Debug, Error, Clone)]
50pub enum DownloadError {
51    /// A network error while downloading a file from Telegram.
52    #[error("A network error: {0}")]
53    // NOTE: this variant must not be created by anything except the explicit From impl
54    Network(#[source] Arc<reqwest::Error>),
55
56    /// An I/O error while writing a file to destination.
57    #[error("An I/O error: {0}")]
58    Io(#[from] Arc<std::io::Error>),
59}
60
61pub trait AsResponseParameters {
62    fn response_parameters(&self) -> Option<ResponseParameters>;
63
64    fn retry_after(&self) -> Option<Seconds> {
65        self.response_parameters().and_then(|rp| match rp {
66            ResponseParameters::RetryAfter(n) => Some(n),
67            _ => None,
68        })
69    }
70
71    fn migrate_to_chat_id(&self) -> Option<ChatId> {
72        self.response_parameters().and_then(|rp| match rp {
73            ResponseParameters::MigrateToChatId(id) => Some(id),
74            _ => None,
75        })
76    }
77}
78
79impl AsResponseParameters for crate::RequestError {
80    fn response_parameters(&self) -> Option<ResponseParameters> {
81        match *self {
82            Self::RetryAfter(n) => Some(ResponseParameters::RetryAfter(n)),
83            Self::MigrateToChatId(id) => Some(ResponseParameters::MigrateToChatId(id)),
84            _ => None,
85        }
86    }
87}
88
89macro_rules! impl_api_error {
90    (
91        $( #[$meta:meta] )*
92        $vis:vis enum $ident:ident {
93            $(
94                $( #[$var_meta:meta] )*
95                $var_name:ident $( ($var_inner:ty) )? = $var_string:literal $(with $var_parser:expr)?
96             ),*
97         }
98    ) => {
99
100        $(#[$meta])*
101        #[derive(Error)]
102        $vis enum $ident {
103            $(
104            $(#[$var_meta])*
105            #[error($var_string)]
106            $var_name $(($var_inner))*,
107            )*
108        }
109
110        const _: () = {
111            struct Visitor;
112
113            impl<'de> ::serde::de::Visitor<'de> for Visitor {
114                type Value = $ident;
115
116                fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
117                    formatter.write_str("telegram api error string")
118                }
119
120                fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
121                where
122                    E: ::serde::de::Error,
123                {
124                    $(impl_api_error!(@de v, $var_name $( ($var_inner) )?, $var_string $(, $var_parser)*);)*
125                    Err(E::unknown_variant(v, &[]))
126                }
127            }
128
129            impl<'de> ::serde::de::Deserialize<'de> for $ident {
130                fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
131                where
132                    D: ::serde::de::Deserializer<'de>,
133                {
134                    deserializer.deserialize_str(Visitor)
135                }
136            }
137        };
138    };
139    (@de $value:ident, $variant:ident, $val:literal) => {
140        if $value == $val {
141            return Ok(Self::Value::$variant)
142        }
143    };
144    (@de $value:ident, $variant:ident ($var_inner:ty), $val:literal, $block:expr) => {
145        #[allow(clippy::redundant_closure_call)]
146        match $block($value) {
147            Some(data) => return Ok(Self::Value::$variant(data)),
148            _ => {}
149        }
150    };
151    (@de $value:ident, $variant:ident, $val:literal, $block:expr) => {
152        #[allow(clippy::redundant_closure_call)]
153        if $block($value) {
154            return Ok(Self::Value::$variant);
155        }
156    };
157}
158
159impl_api_error! {
160    /// A kind of an API error.
161    #[derive(Debug, PartialEq, Hash, Eq, Clone)]
162    #[non_exhaustive]
163    pub enum ApiError {
164        /// Occurs when the bot tries to send message to user who blocked the bot.
165        BotBlocked = "Forbidden: bot was blocked by the user",
166
167        /// Occurs when the bot token is invalid.
168        // N.B. These errors are actually slightly different, "Unauthorized" is when the bot token
169        //      is formatted mostly right, but is incorrect, whereas "Not Found" is when the url is
170        //      not handled by TBA at all. From user POV both of those are "token is invalid", but
171        //      there might be some cases where this is not right...
172        InvalidToken = "Invalid bot token" with |text: &str| text == "Unauthorized" || text == "Not Found",
173
174        /// Occurs when bot tries to modify a message without modification content.
175        ///
176        /// May happen in methods:
177        /// 1. [`EditMessageText`]
178        ///
179        /// [`EditMessageText`]: crate::payloads::EditMessageText
180        MessageNotModified = "Bad Request: message is not modified: specified new message content and reply markup are \
181            exactly the same as a current content and reply markup of the message",
182
183        /// Occurs when bot tries to forward or delete a message which was deleted.
184        ///
185        /// May happen in methods:
186        /// 1. [`ForwardMessage`]
187        /// 2. [`DeleteMessage`]
188        ///
189        /// [`ForwardMessage`]: crate::payloads::ForwardMessage
190        /// [`DeleteMessage`]: crate::payloads::DeleteMessage
191        MessageIdInvalid = "Bad Request: MESSAGE_ID_INVALID",
192
193        /// Occurs when bot tries to forward a message which does not exists.
194        ///
195        /// May happen in methods:
196        /// 1. [`ForwardMessage`]
197        ///
198        /// [`ForwardMessage`]: crate::payloads::ForwardMessage
199        MessageToForwardNotFound = "Bad Request: message to forward not found",
200
201        /// Occurs when bot tries to delete a message which does not exists.
202        ///
203        /// May happen in methods:
204        /// 1. [`DeleteMessage`]
205        ///
206        /// [`DeleteMessage`]: crate::payloads::DeleteMessage
207        MessageToDeleteNotFound = "Bad Request: message to delete not found",
208
209        /// Occurs when bot tries to copy a message which does not exists.
210        /// May happen in methods:
211        /// 1. [`CopyMessage`]
212        ///
213        /// [`CopyMessage`]: crate::payloads::CopyMessage
214        MessageToCopyNotFound = "Bad Request: message to copy not found",
215
216        /// Occurs when bot tries to send a text message without text.
217        ///
218        /// May happen in methods:
219        /// 1. [`SendMessage`]
220        ///
221        /// [`SendMessage`]: crate::payloads::SendMessage
222        MessageTextIsEmpty = "Bad Request: message text is empty",
223
224        /// Occurs when bot tries to edit a message after long time.
225        ///
226        /// May happen in methods:
227        /// 1. [`EditMessageText`]
228        ///
229        /// [`EditMessageText`]: crate::payloads::EditMessageText
230        MessageCantBeEdited = "Bad Request: message can't be edited",
231
232        /// Occurs when bot tries to delete a someone else's message in group where
233        /// it does not have enough rights.
234        ///
235        /// May happen in methods:
236        /// 1. [`DeleteMessage`]
237        ///
238        /// [`DeleteMessage`]: crate::payloads::DeleteMessage
239        MessageCantBeDeleted = "Bad Request: message can't be deleted",
240
241        /// Occurs when bot tries to edit a message which does not exists.
242        ///
243        /// May happen in methods:
244        /// 1. [`EditMessageText`]
245        ///
246        /// [`EditMessageText`]: crate::payloads::EditMessageText
247        MessageToEditNotFound = "Bad Request: message to edit not found",
248
249        /// Occurs when bot tries to edit a message which has no text.
250        ///
251        /// May happen in methods:
252        /// 1. [`EditMessageText`]
253        /// 2. [`EditMessageTextInline`]
254        ///
255        /// [`EditMessageText`]: crate::payloads::EditMessageText
256        /// [`EditMessageTextInline`]: crate::payloads::EditMessageTextInline
257        MessageToEditHasNoText = "Bad Request: there is no text in the message to edit",
258
259        /// Occurs when bot tries to reply to a message which does not exists.
260        ///
261        /// May happen in methods:
262        /// 1. [`SendMessage`]
263        ///
264        /// [`SendMessage`]: crate::payloads::SendMessage
265        MessageToReplyNotFound = "Bad Request: message to be replied not found",
266
267        /// Occurs when bot tries to
268        MessageIdentifierNotSpecified = "Bad Request: message identifier is not specified",
269
270        /// Occurs when bot tries to send a message with text size greater then
271        /// 4096 symbols.
272        ///
273        /// May happen in methods:
274        /// 1. [`SendMessage`]
275        ///
276        /// [`SendMessage`]: crate::payloads::SendMessage
277        MessageIsTooLong = "Bad Request: message is too long",
278
279        /// Occurs when bot tries to edit a message with text size greater then
280        /// 4096 symbols.
281        ///
282        /// May happen in methods:
283        /// 1. [`EditMessageText`]
284        /// 2. [`EditMessageTextInline`]
285        /// 3. [`EditMessageCaption`]
286        /// 4. [`EditMessageCaptionInline`]
287        ///
288        /// [`EditMessageText`]: crate::payloads::EditMessageText
289        /// [`EditMessageTextInline`]: crate::payloads::EditMessageTextInline
290        /// [`EditMessageCaption`]: crate::payloads::EditMessageCaption
291        /// [`EditMessageCaptionInline`]: crate::payloads::EditMessageCaptionInline
292        EditedMessageIsTooLong = "Bad Request: MESSAGE_TOO_LONG",
293
294        /// Occurs when bot tries to send media group with more than 10 items.
295        ///
296        /// May happen in methods:
297        /// 1. [`SendMediaGroup`]
298        ///
299        /// [`SendMediaGroup`]: crate::payloads::SendMediaGroup
300        TooMuchMessages = "Bad Request: Too much messages to send as an album",
301
302        /// Occurs when bot tries to answer an inline query with more than 50
303        /// results.
304        ///
305        /// Consider using offsets to paginate results.
306        ///
307        /// May happen in methods:
308        /// 1. [`AnswerInlineQuery`]
309        ///
310        /// [`AnswerInlineQuery`]: crate::payloads::AnswerInlineQuery
311        TooMuchInlineQueryResults = "Bad Request: RESULTS_TOO_MUCH",
312
313        /// Occurs when bot tries to stop poll that has already been stopped.
314        ///
315        /// May happen in methods:
316        /// 1. [`SendPoll`]
317        ///
318        /// [`SendPoll`]: crate::payloads::SendPoll
319        PollHasAlreadyClosed = "Bad Request: poll has already been closed",
320
321        /// Occurs when bot tries to send poll with less than 2 options.
322        ///
323        /// May happen in methods:
324        /// 1. [`SendPoll`]
325        ///
326        /// [`SendPoll`]: crate::payloads::SendPoll
327        PollMustHaveMoreOptions = "Bad Request: poll must have at least 2 option",
328
329        /// Occurs when bot tries to send poll with more than 12 options.
330        ///
331        /// May happen in methods:
332        /// 1. [`SendPoll`]
333        ///
334        /// [`SendPoll`]: crate::payloads::SendPoll
335        PollCantHaveMoreOptions = "Bad Request: poll can't have more than 12 options",
336
337        /// Occurs when bot tries to send poll with empty option (without text).
338        ///
339        /// May happen in methods:
340        /// 1. [`SendPoll`]
341        ///
342        /// [`SendPoll`]: crate::payloads::SendPoll
343        PollOptionsMustBeNonEmpty = "Bad Request: poll options must be non-empty",
344
345        /// Occurs when bot tries to send poll with empty question (without text).
346        ///
347        /// May happen in methods:
348        /// 1. [`SendPoll`]
349        ///
350        /// [`SendPoll`]: crate::payloads::SendPoll
351        PollQuestionMustBeNonEmpty = "Bad Request: poll question must be non-empty",
352
353        /// Occurs when bot tries to send poll with total size of options more than
354        /// 100 symbols.
355        ///
356        /// May happen in methods:
357        /// 1. [`SendPoll`]
358        ///
359        /// [`SendPoll`]: crate::payloads::SendPoll
360        PollOptionsLengthTooLong = "Bad Request: poll options length must not exceed 100",
361
362        /// Occurs when bot tries to send poll with question size more than 255
363        /// symbols.
364        ///
365        /// May happen in methods:
366        /// 1. [`SendPoll`]
367        ///
368        /// [`SendPoll`]: crate::payloads::SendPoll
369        PollQuestionLengthTooLong = "Bad Request: poll question length must not exceed 255",
370
371        /// Occurs when bot tries to stop poll with message without poll.
372        ///
373        /// May happen in methods:
374        /// 1. [`StopPoll`]
375        ///
376        /// [`StopPoll`]: crate::payloads::StopPoll
377        MessageWithPollNotFound = "Bad Request: message with poll to stop not found",
378
379        /// Occurs when bot tries to stop poll with message without poll.
380        ///
381        /// May happen in methods:
382        /// 1. [`StopPoll`]
383        ///
384        /// [`StopPoll`]: crate::payloads::StopPoll
385        MessageIsNotAPoll = "Bad Request: message is not a poll",
386
387        /// Occurs when bot tries to send a message to chat in which it is not a
388        /// member.
389        ///
390        /// May happen in methods:
391        /// 1. [`SendMessage`]
392        ///
393        /// [`SendMessage`]: crate::payloads::SendMessage
394        ChatNotFound = "Bad Request: chat not found",
395
396        /// Occurs when bot tries to send method with unknown user_id.
397        ///
398        /// May happen in methods:
399        /// 1. [`getUserProfilePhotos`]
400        ///
401        /// [`getUserProfilePhotos`]:
402        /// crate::payloads::GetUserProfilePhotos
403        UserNotFound = "Bad Request: user not found",
404
405        /// Occurs when bot tries to send [`SetChatDescription`] with same text as
406        /// in the current description.
407        ///
408        /// May happen in methods:
409        /// 1. [`SetChatDescription`]
410        ///
411        /// [`SetChatDescription`]: crate::payloads::SetChatDescription
412        ChatDescriptionIsNotModified = "Bad Request: chat description is not modified",
413
414        /// Occurs when bot tries to answer to query after timeout expire.
415        ///
416        /// May happen in methods:
417        /// 1. [`AnswerCallbackQuery`]
418        ///
419        /// [`AnswerCallbackQuery`]: crate::payloads::AnswerCallbackQuery
420        InvalidQueryId = "Bad Request: query is too old and response timeout expired or query id is invalid",
421
422        /// Occurs when bot tries to send InlineKeyboardMarkup with invalid button
423        /// url.
424        ///
425        /// May happen in methods:
426        /// 1. [`SendMessage`]
427        ///
428        /// [`SendMessage`]: crate::payloads::SendMessage
429        ButtonUrlInvalid = "Bad Request: BUTTON_URL_INVALID",
430
431        /// Occurs when bot tries to send button with data size more than 64 bytes.
432        ///
433        /// May happen in methods:
434        /// 1. [`SendMessage`]
435        ///
436        /// [`SendMessage`]: crate::payloads::SendMessage
437        ButtonDataInvalid = "Bad Request: BUTTON_DATA_INVALID",
438
439        /// Occurs when bot tries to send button with data size == 0.
440        ///
441        /// May happen in methods:
442        /// 1. [`SendMessage`]
443        ///
444        /// [`SendMessage`]: crate::payloads::SendMessage
445        TextButtonsAreUnallowed = "Bad Request: can't parse inline keyboard button: Text buttons are unallowed in the \
446            inline keyboard",
447
448        /// Occurs when bot tries to get file by wrong file id.
449        ///
450        /// May happen in methods:
451        /// 1. [`GetFile`]
452        ///
453        /// [`GetFile`]: crate::payloads::GetFile
454        WrongFileId = "Bad Request: wrong file id",
455
456        /// Occurs when bot tries to send files with wrong file identifier or HTTP
457        /// url
458        WrongFileIdOrUrl = "Bad Request: wrong file identifier/HTTP URL specified",
459
460        /// Occurs when When sending files with an url to a site that doesn't
461        /// respond.
462        FailedToGetUrlContent = "Bad Request: failed to get HTTP URL content",
463
464        /// Occurs when bot tries to do some with group which was deactivated.
465        GroupDeactivated = "Bad Request: group is deactivated",
466
467        /// Occurs when image processing fails on telegram's side.
468        ///
469        /// This is likely caused by an incorrectly encoded image, make sure that
470        /// the image is correctly encoded in a format telegram accepts.
471        ImageProcessFailed = "Bad Request: IMAGE_PROCESS_FAILED",
472
473        /// Occurs when bot tries to set chat photo from file ID
474        ///
475        /// May happen in methods:
476        /// 1. [`SetChatPhoto`]
477        ///
478        /// [`SetChatPhoto`]: crate::payloads::SetChatPhoto
479        PhotoAsInputFileRequired = "Bad Request: Photo should be uploaded as an InputFile",
480
481        /// Occurs when bot tries to add sticker to stickerset by invalid name.
482        ///
483        /// May happen in methods:
484        /// 1. [`AddStickerToSet`]
485        ///
486        /// [`AddStickerToSet`]: crate::payloads::AddStickerToSet
487        InvalidStickersSet = "Bad Request: STICKERSET_INVALID",
488
489        /// Occurs when bot tries to create a sticker set with a name that is
490        /// already used by another sticker set.
491        ///
492        /// May happen in methods:
493        /// 1. [`CreateNewStickerSet`]
494        ///
495        /// [`CreateNewStickerSet`]: crate::payloads::CreateNewStickerSet
496        StickerSetNameOccupied = "Bad Request: sticker set name is already occupied",
497
498        /// Occurs when bot tries to create a sticker set with user id of a bot.
499        ///
500        /// May happen in methods:
501        /// 1. [`CreateNewStickerSet`]
502        ///
503        /// [`CreateNewStickerSet`]: crate::payloads::CreateNewStickerSet
504        StickerSetOwnerIsBot = "Bad Request: USER_IS_BOT",
505
506        /// Occurs when bot tries to create a sticker set with invalid name.
507        ///
508        /// From documentation of [`CreateNewStickerSet`]:
509        /// > Short name of sticker set, to be used in `t.me/addstickers/` URLs
510        /// (e.g., _animals_). Can contain only english letters, digits and
511        /// underscores. Must begin with a letter, can't contain consecutive
512        /// underscores and must end in “\_by\_<bot\_username>”. <bot\_username>
513        /// is case insensitive. 1-64 characters.
514        ///
515        /// May happen in methods:
516        /// 1. [`CreateNewStickerSet`]
517        ///
518        /// [`CreateNewStickerSet`]: crate::payloads::CreateNewStickerSet
519        InvalidStickerName = "Bad Request: invalid sticker set name is specified",
520
521        /// Occurs when bot tries to pin a message without rights to pin in this
522        /// chat.
523        ///
524        /// May happen in methods:
525        /// 1. [`PinChatMessage`]
526        ///
527        /// [`PinChatMessage`]: crate::payloads::PinChatMessage
528        NotEnoughRightsToPinMessage = "Bad Request: not enough rights to pin a message",
529
530        /// Occurs when bot tries to pin or unpin a message without rights to pin
531        /// in this chat.
532        ///
533        /// May happen in methods:
534        /// 1. [`PinChatMessage`]
535        /// 2. [`UnpinChatMessage`]
536        ///
537        /// [`PinChatMessage`]: crate::payloads::PinChatMessage
538        /// [`UnpinChatMessage`]: crate::payloads::UnpinChatMessage
539        NotEnoughRightsToManagePins = "Bad Request: not enough rights to manage pinned messages in the chat",
540
541        /// Occurs when bot tries change default chat permissions without "Ban
542        /// Users" permission in this chat.
543        ///
544        /// May happen in methods:
545        /// 1. [`SetChatPermissions`]
546        ///
547        /// [`SetChatPermissions`]: crate::payloads::SetChatPermissions
548        NotEnoughRightsToChangeChatPermissions = "Bad Request: not enough rights to change chat permissions",
549
550        /// Occurs when bot tries to use method in group which is allowed only in a
551        /// supergroup or channel.
552        MethodNotAvailableInPrivateChats = "Bad Request: method is available only for supergroups and channel",
553
554        /// Occurs when bot tries to demote chat creator.
555        ///
556        /// May happen in methods:
557        /// 1. [`PromoteChatMember`]
558        ///
559        /// [`PromoteChatMember`]: crate::payloads::PromoteChatMember
560        CantDemoteChatCreator = "Bad Request: can't demote chat creator",
561
562        /// Occurs when bot tries to restrict self in group chats.
563        ///
564        /// May happen in methods:
565        /// 1. [`RestrictChatMember`]
566        ///
567        /// [`RestrictChatMember`]: crate::payloads::RestrictChatMember
568        CantRestrictSelf = "Bad Request: can't restrict self",
569
570        /// Occurs when bot tries to restrict chat member without rights to
571        /// restrict in this chat.
572        ///
573        /// May happen in methods:
574        /// 1. [`RestrictChatMember`]
575        ///
576        /// [`RestrictChatMember`]: crate::payloads::RestrictChatMember
577        NotEnoughRightsToRestrict = "Bad Request: not enough rights to restrict/unrestrict chat member",
578
579        /// Occurs when bot tries to post a message in a channel without "Post
580        /// Messages" admin right.
581        NotEnoughRightsToPostMessages = "Bad Request: need administrator rights in the channel chat",
582
583        /// Occurs when bot tries to get a chat member with an invalid
584        /// participant ID.
585        ///
586        /// May happen in methods:
587        /// 1. [`GetChatMember`]
588        ///
589        /// [`GetChatMember`]: crate::payloads::GetChatMember
590        ParticipantIdInvalid = "Bad Request: PARTICIPANT_ID_INVALID",
591
592        /// Occurs when bot tries to perform an action that requires admin
593        /// privileges in the chat.
594        ///
595        /// May happen in methods:
596        /// 1. [`GetChatMember`]
597        ///
598        /// [`GetChatMember`]: crate::payloads::GetChatMember
599        ChatAdminRequired = "Bad Request: CHAT_ADMIN_REQUIRED",
600
601        /// Occurs when bot tries set webhook to protocol other than HTTPS.
602        ///
603        /// May happen in methods:
604        /// 1. [`SetWebhook`]
605        ///
606        /// [`SetWebhook`]: crate::payloads::SetWebhook
607        WebhookRequireHttps = "Bad Request: bad webhook: HTTPS url must be provided for webhook",
608
609        /// Occurs when bot tries to set webhook to port other than 80, 88, 443 or
610        /// 8443.
611        ///
612        /// May happen in methods:
613        /// 1. [`SetWebhook`]
614        ///
615        /// [`SetWebhook`]: crate::payloads::SetWebhook
616        BadWebhookPort = "Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 or 8443",
617
618        /// Occurs when bot tries to set webhook to unknown host.
619        ///
620        /// May happen in methods:
621        /// 1. [`SetWebhook`]
622        ///
623        /// [`SetWebhook`]: crate::payloads::SetWebhook
624        UnknownHost = "Bad Request: bad webhook: Failed to resolve host: Name or service not known",
625
626        /// Occurs when bot tries to set webhook to invalid URL.
627        ///
628        /// May happen in methods:
629        /// 1. [`SetWebhook`]
630        ///
631        /// [`SetWebhook`]: crate::payloads::SetWebhook
632        CantParseUrl = "Bad Request: can't parse URL",
633
634        /// Occurs when bot tries to send message with unfinished entities.
635        ///
636        /// May happen in methods:
637        /// 1. [`SendMessage`]
638        ///
639        /// [`SendMessage`]: crate::payloads::SendMessage
640        CantParseEntities(String) = "{0}" with |text: &str| {
641            if text.starts_with("Bad Request: can't parse entities") {
642                Some(text.to_owned())
643            } else {
644                None
645            }
646        },
647
648        /// Occurs when bot tries to use getUpdates while webhook is active.
649        ///
650        /// May happen in methods:
651        /// 1. [`GetUpdates`]
652        ///
653        /// [`GetUpdates`]: crate::payloads::GetUpdates
654        CantGetUpdates = "can't use getUpdates method while webhook is active",
655
656        /// Occurs when bot tries to do some in group where bot was kicked.
657        ///
658        /// May happen in methods:
659        /// 1. [`SendMessage`]
660        ///
661        /// [`SendMessage`]: crate::payloads::SendMessage
662        BotKicked = "Unauthorized: bot was kicked from a chat",
663
664        /// Occurs when bot tries to do something in a supergroup the bot was
665        /// kicked from.
666        ///
667        /// May happen in methods:
668        /// 1. [`SendMessage`]
669        ///
670        /// [`SendMessage`]: crate::payloads::SendMessage
671        BotKickedFromSupergroup = "Forbidden: bot was kicked from the supergroup chat",
672
673        /// Occurs when bot tries to do something in a channel the bot was
674        /// kicked from.
675        ///
676        /// May happen in methods:
677        /// 1. [`SendMessage`]
678        ///
679        /// [`SendMessage`]: crate::payloads::SendMessage
680        BotKickedFromChannel = "Forbidden: bot was kicked from the channel chat",
681
682        /// Occurs when bot tries to send a message to a deactivated user (i.e. a
683        /// user that was banned by telegram).
684        ///
685        /// May happen in methods:
686        /// 1. [`SendMessage`]
687        ///
688        /// [`SendMessage`]: crate::payloads::SendMessage
689        UserDeactivated = "Forbidden: user is deactivated",
690
691        /// Occurs when you tries to initiate conversation with a user.
692        ///
693        /// May happen in methods:
694        /// 1. [`SendMessage`]
695        ///
696        /// [`SendMessage`]: crate::payloads::SendMessage
697        CantInitiateConversation = "Unauthorized: bot can't initiate conversation with a user",
698
699        /// Occurs when you tries to send message to bot.
700        ///
701        /// May happen in methods:
702        /// 1. [`SendMessage`]
703        ///
704        /// [`SendMessage`]: crate::payloads::SendMessage
705        CantTalkWithBots = "Unauthorized: bot can't send messages to bots",
706
707        /// Occurs when bot tries to send button with invalid http url.
708        ///
709        /// May happen in methods:
710        /// 1. [`SendMessage`]
711        ///
712        /// [`SendMessage`]: crate::payloads::SendMessage
713        WrongHttpUrl = "Bad Request: wrong HTTP URL",
714
715        /// Occurs when multiple [`GetUpdates`] calls happen at the same time.
716        ///
717        /// This can happen if
718        /// 1. You are running multiple bot instances (including on different servers/hosting platforms)
719        /// 2. You are running multiple update consumers (like `Dispatcher` or `repl`)
720        /// 3. You are calling [`GetUpdates`] yourself and the second call is done before the first one finishes
721        ///
722        /// May happen in methods:
723        /// 1. [`GetUpdates`]
724        ///
725        /// [`GetUpdates`]: crate::payloads::GetUpdates
726        TerminatedByOtherGetUpdates = "Conflict: terminated by other getUpdates request; make sure that only one bot instance \
727            is running",
728
729        /// Occurs when bot tries to get file by invalid file id.
730        ///
731        /// May happen in methods:
732        /// 1. [`GetFile`]
733        ///
734        /// [`GetFile`]: crate::payloads::GetFile
735        FileIdInvalid = "Bad Request: invalid file id",
736
737        /// Occurs when bot tries to upload a file which is larger than 50 MB using
738        /// multipart/form-data.
739        ///
740        /// May happen in methods:
741        /// 1. [`SendVideo`]
742        /// 2. [`SendDocument`]
743        ///
744        /// [`SendVideo`]: crate::payloads::SendVideo
745        /// [`SendDocument`]: crate::payloads::SendDocument
746        RequestEntityTooLarge = "Request Entity Too Large",
747
748
749        /// Error which is not known to `ronami`.
750        ///
751        /// If you've received this error, please open an issue in the Ronami
752        /// repository with the description of the error.
753        Unknown(String) = "Unknown error: {0:?}" with |text: &str| Some(text.to_owned())
754    }
755}
756
757/// This impl allows to use `?` to propagate [`DownloadError`]s in function
758/// returning [`RequestError`]s. For example:
759///
760/// ```rust
761/// # use ronami_core::errors::{DownloadError, RequestError};
762///
763/// async fn handler() -> Result<(), RequestError> {
764///     download_file().await?; // `?` just works
765///
766///     Ok(())
767/// }
768///
769/// async fn download_file() -> Result<(), DownloadError> {
770///     /* download file here */
771///     Ok(())
772/// }
773/// ```
774impl From<DownloadError> for RequestError {
775    fn from(download_err: DownloadError) -> Self {
776        match download_err {
777            DownloadError::Network(err) => RequestError::Network(err),
778            DownloadError::Io(err) => RequestError::Io(err),
779        }
780    }
781}
782
783impl From<reqwest::Error> for DownloadError {
784    fn from(error: reqwest::Error) -> Self {
785        DownloadError::Network(Arc::new(hide_token(error)))
786    }
787}
788
789impl From<std::io::Error> for DownloadError {
790    fn from(error: std::io::Error) -> Self {
791        DownloadError::Io(Arc::new(error))
792    }
793}
794
795impl From<reqwest::Error> for RequestError {
796    fn from(error: reqwest::Error) -> Self {
797        RequestError::Network(Arc::new(hide_token(error)))
798    }
799}
800
801/// Replaces token in the url in the error with `token:redacted` string.
802pub(crate) fn hide_token(mut error: reqwest::Error) -> reqwest::Error {
803    let url = match error.url_mut() {
804        Some(url) => url,
805        None => return error,
806    };
807
808    if let Some(mut segments) = url.path_segments() {
809        // Usually the url looks like "bot<token>/..." or "file/bot<token>/...".
810        let (beginning, segment) = match segments.next() {
811            Some("file") => ("file/", segments.next()),
812            segment => ("", segment),
813        };
814
815        if let Some(token) = segment.and_then(|s| s.strip_prefix("bot")) {
816            // make sure that what we are about to delete looks like a bot token
817            if let Some((_id, _secret)) = token.split_once(':') {
818                // found token, hide the token unconditionally
819                let without_token = &url.path()[(beginning.len() + "/bot".len() + token.len())..];
820                let redacted = format!("{beginning}token:redacted{without_token}");
821
822                url.set_path(&redacted);
823                return error;
824            }
825        }
826    }
827
828    // couldn't find token in the url, hide the whole url
829    error.without_url()
830}
831
832#[cfg(test)]
833mod tests {
834    #[test]
835    fn custom_result() {
836        use super::ApiError;
837        use serde::Deserialize;
838
839        let cases = &[
840            ("{\"data\": \"Forbidden: bot was blocked by the user\"}", ApiError::BotBlocked),
841            ("{\"data\": \"Unauthorized\"}", ApiError::InvalidToken),
842            ("{\"data\": \"Not Found\"}", ApiError::InvalidToken),
843            (
844                "{\"data\": \"Bad Request: message is not modified: specified new message content \
845                 and reply markup are exactly the same as a current content and reply markup of \
846                 the message\"}",
847                ApiError::MessageNotModified,
848            ),
849            ("{\"data\": \"Bad Request: MESSAGE_ID_INVALID\"}", ApiError::MessageIdInvalid),
850            (
851                "{\"data\": \"Bad Request: message to forward not found\"}",
852                ApiError::MessageToForwardNotFound,
853            ),
854            (
855                "{\"data\": \"Bad Request: message to delete not found\"}",
856                ApiError::MessageToDeleteNotFound,
857            ),
858            (
859                "{\"data\": \"Bad Request: message to copy not found\"}",
860                ApiError::MessageToCopyNotFound,
861            ),
862            ("{\"data\": \"Bad Request: message text is empty\"}", ApiError::MessageTextIsEmpty),
863            ("{\"data\": \"Bad Request: message can't be edited\"}", ApiError::MessageCantBeEdited),
864            (
865                "{\"data\": \"Bad Request: message can't be deleted\"}",
866                ApiError::MessageCantBeDeleted,
867            ),
868            (
869                "{\"data\": \"Bad Request: message to edit not found\"}",
870                ApiError::MessageToEditNotFound,
871            ),
872            (
873                "{\"data\": \"Bad Request: there is no text in the message to edit\"}",
874                ApiError::MessageToEditHasNoText,
875            ),
876            (
877                "{\"data\": \"Bad Request: message to be replied not found\"}",
878                ApiError::MessageToReplyNotFound,
879            ),
880            (
881                "{\"data\": \"Bad Request: message identifier is not specified\"}",
882                ApiError::MessageIdentifierNotSpecified,
883            ),
884            ("{\"data\": \"Bad Request: message is too long\"}", ApiError::MessageIsTooLong),
885            ("{\"data\": \"Bad Request: MESSAGE_TOO_LONG\"}", ApiError::EditedMessageIsTooLong),
886            (
887                "{\"data\": \"Bad Request: Too much messages to send as an album\"}",
888                ApiError::TooMuchMessages,
889            ),
890            ("{\"data\": \"Bad Request: RESULTS_TOO_MUCH\"}", ApiError::TooMuchInlineQueryResults),
891            (
892                "{\"data\": \"Bad Request: poll has already been closed\"}",
893                ApiError::PollHasAlreadyClosed,
894            ),
895            (
896                "{\"data\": \"Bad Request: poll must have at least 2 option\"}",
897                ApiError::PollMustHaveMoreOptions,
898            ),
899            (
900                "{\"data\": \"Bad Request: poll can't have more than 12 options\"}",
901                ApiError::PollCantHaveMoreOptions,
902            ),
903            (
904                "{\"data\": \"Bad Request: poll options must be non-empty\"}",
905                ApiError::PollOptionsMustBeNonEmpty,
906            ),
907            (
908                "{\"data\": \"Bad Request: poll question must be non-empty\"}",
909                ApiError::PollQuestionMustBeNonEmpty,
910            ),
911            (
912                "{\"data\": \"Bad Request: poll options length must not exceed 100\"}",
913                ApiError::PollOptionsLengthTooLong,
914            ),
915            (
916                "{\"data\": \"Bad Request: poll question length must not exceed 255\"}",
917                ApiError::PollQuestionLengthTooLong,
918            ),
919            (
920                "{\"data\": \"Bad Request: message with poll to stop not found\"}",
921                ApiError::MessageWithPollNotFound,
922            ),
923            ("{\"data\": \"Bad Request: message is not a poll\"}", ApiError::MessageIsNotAPoll),
924            ("{\"data\": \"Bad Request: chat not found\"}", ApiError::ChatNotFound),
925            ("{\"data\": \"Bad Request: user not found\"}", ApiError::UserNotFound),
926            (
927                "{\"data\": \"Bad Request: chat description is not modified\"}",
928                ApiError::ChatDescriptionIsNotModified,
929            ),
930            (
931                "{\"data\": \"Bad Request: query is too old and response timeout expired or query \
932                 id is invalid\"}",
933                ApiError::InvalidQueryId,
934            ),
935            ("{\"data\": \"Bad Request: BUTTON_URL_INVALID\"}", ApiError::ButtonUrlInvalid),
936            ("{\"data\": \"Bad Request: BUTTON_DATA_INVALID\"}", ApiError::ButtonDataInvalid),
937            (
938                "{\"data\": \"Bad Request: can't parse inline keyboard button: Text buttons are \
939                 unallowed in the inline keyboard\"}",
940                ApiError::TextButtonsAreUnallowed,
941            ),
942            ("{\"data\": \"Bad Request: wrong file id\"}", ApiError::WrongFileId),
943            (
944                "{\"data\": \"Bad Request: wrong file identifier/HTTP URL specified\"}",
945                ApiError::WrongFileIdOrUrl,
946            ),
947            (
948                "{\"data\": \"Bad Request: failed to get HTTP URL content\"}",
949                ApiError::FailedToGetUrlContent,
950            ),
951            ("{\"data\": \"Bad Request: group is deactivated\"}", ApiError::GroupDeactivated),
952            ("{\"data\": \"Bad Request: IMAGE_PROCESS_FAILED\"}", ApiError::ImageProcessFailed),
953            (
954                "{\"data\": \"Bad Request: Photo should be uploaded as an InputFile\"}",
955                ApiError::PhotoAsInputFileRequired,
956            ),
957            ("{\"data\": \"Bad Request: STICKERSET_INVALID\"}", ApiError::InvalidStickersSet),
958            (
959                "{\"data\": \"Bad Request: sticker set name is already occupied\"}",
960                ApiError::StickerSetNameOccupied,
961            ),
962            ("{\"data\": \"Bad Request: USER_IS_BOT\"}", ApiError::StickerSetOwnerIsBot),
963            (
964                "{\"data\": \"Bad Request: invalid sticker set name is specified\"}",
965                ApiError::InvalidStickerName,
966            ),
967            (
968                "{\"data\": \"Bad Request: not enough rights to pin a message\"}",
969                ApiError::NotEnoughRightsToPinMessage,
970            ),
971            (
972                "{\"data\": \"Bad Request: not enough rights to manage pinned messages in the \
973                 chat\"}",
974                ApiError::NotEnoughRightsToManagePins,
975            ),
976            (
977                "{\"data\": \"Bad Request: not enough rights to change chat permissions\"}",
978                ApiError::NotEnoughRightsToChangeChatPermissions,
979            ),
980            (
981                "{\"data\": \"Bad Request: method is available only for supergroups and channel\"}",
982                ApiError::MethodNotAvailableInPrivateChats,
983            ),
984            (
985                "{\"data\": \"Bad Request: can't demote chat creator\"}",
986                ApiError::CantDemoteChatCreator,
987            ),
988            ("{\"data\": \"Bad Request: can't restrict self\"}", ApiError::CantRestrictSelf),
989            (
990                "{\"data\": \"Bad Request: not enough rights to restrict/unrestrict chat member\"}",
991                ApiError::NotEnoughRightsToRestrict,
992            ),
993            (
994                "{\"data\": \"Bad Request: need administrator rights in the channel chat\"}",
995                ApiError::NotEnoughRightsToPostMessages,
996            ),
997            ("{\"data\": \"Bad Request: PARTICIPANT_ID_INVALID\"}", ApiError::ParticipantIdInvalid),
998            ("{\"data\": \"Bad Request: CHAT_ADMIN_REQUIRED\"}", ApiError::ChatAdminRequired),
999            (
1000                "{\"data\": \"Bad Request: bad webhook: HTTPS url must be provided for webhook\"}",
1001                ApiError::WebhookRequireHttps,
1002            ),
1003            (
1004                "{\"data\": \"Bad Request: bad webhook: Webhook can be set up only on ports 80, \
1005                 88, 443 or 8443\"}",
1006                ApiError::BadWebhookPort,
1007            ),
1008            (
1009                "{\"data\": \"Bad Request: bad webhook: Failed to resolve host: Name or service \
1010                 not known\"}",
1011                ApiError::UnknownHost,
1012            ),
1013            ("{\"data\": \"Bad Request: can't parse URL\"}", ApiError::CantParseUrl),
1014            (
1015                "{\"data\": \"Bad Request: can't parse entities: SomeRandomString\"}",
1016                ApiError::CantParseEntities(
1017                    "Bad Request: can't parse entities: SomeRandomString".to_owned(),
1018                ),
1019            ),
1020            (
1021                "{\"data\": \"can't use getUpdates method while webhook is active\"}",
1022                ApiError::CantGetUpdates,
1023            ),
1024            ("{\"data\": \"Unauthorized: bot was kicked from a chat\"}", ApiError::BotKicked),
1025            (
1026                "{\"data\": \"Forbidden: bot was kicked from the supergroup chat\"}",
1027                ApiError::BotKickedFromSupergroup,
1028            ),
1029            ("{\"data\": \"Forbidden: user is deactivated\"}", ApiError::UserDeactivated),
1030            (
1031                "{\"data\": \"Unauthorized: bot can't initiate conversation with a user\"}",
1032                ApiError::CantInitiateConversation,
1033            ),
1034            (
1035                "{\"data\": \"Unauthorized: bot can't send messages to bots\"}",
1036                ApiError::CantTalkWithBots,
1037            ),
1038            ("{\"data\": \"Bad Request: wrong HTTP URL\"}", ApiError::WrongHttpUrl),
1039            (
1040                "{\"data\": \"Conflict: terminated by other getUpdates request; make sure that \
1041                 only one bot instance is running\"}",
1042                ApiError::TerminatedByOtherGetUpdates,
1043            ),
1044            ("{\"data\": \"Bad Request: invalid file id\"}", ApiError::FileIdInvalid),
1045            ("{\"data\": \"Request Entity Too Large\"}", ApiError::RequestEntityTooLarge),
1046            ("{\"data\": \"RandomError\"}", ApiError::Unknown("RandomError".to_string())),
1047        ];
1048
1049        #[derive(Deserialize, Debug)]
1050        struct Res<T> {
1051            data: T,
1052        }
1053
1054        for (data, expected) in cases {
1055            let raw = serde_json::from_str::<Res<String>>(data).unwrap().data;
1056            let parsed = serde_json::from_str::<Res<ApiError>>(data).unwrap().data;
1057            assert_eq!(&parsed, expected);
1058
1059            let expected_error_message = match parsed {
1060                ApiError::Unknown(_) => {
1061                    format!("Unknown error: \"{raw}\"")
1062                }
1063                ApiError::InvalidToken => "Invalid bot token".to_owned(),
1064                _ => raw,
1065            };
1066            assert_eq!(parsed.to_string(), expected_error_message);
1067        }
1068    }
1069}