diff --git a/Cargo.toml b/Cargo.toml
index 24f295da..720ae832 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -87,6 +87,7 @@ redis = { version = "0.20", features = ["tokio-comp"], optional = true }
serde_cbor = { version = "0.11", optional = true }
bincode = { version = "1.3", optional = true }
frunk = { version = "0.4", optional = true }
+aquamarine = "0.1.11"
[dev-dependencies]
smart-default = "0.6.0"
diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs
index 078f52a2..18d294d1 100644
--- a/src/dispatching/update_listeners.rs
+++ b/src/dispatching/update_listeners.rs
@@ -1,107 +1,27 @@
//! Receiving updates from Telegram.
//!
-//! The key trait here is [`UpdateListener`]. You can get it by these functions:
+//! The key trait here is [`UpdateListener`]. You can get its implementation
+//! using one these functions:
//!
-//! - [`polling_default`], which returns a default long polling listener.
-//! - [`polling`], which returns a long/short polling listener with your
-//! configuration.
+//! - [`polling_default`], which returns a default long polling listener.
+//! - [`polling`], which returns a long polling listener with your
+//! configuration.
//!
-//! And then you can extract updates from it and pass them directly to a
-//! dispatcher.
+//! And then you can extract updates from it or pass them directly to a
+//! [`Dispatcher`].
//!
-//! Telegram supports two ways of [getting updates]: [long]/[short] polling and
-//! [webhook].
-//!
-//! # Long Polling
-//!
-//! In long polling, you just call [`Box::get_updates`] every N seconds.
-//!
-//! ## Example
-//!
-//!
-//! tg bot
-//! | |
-//! |<---------------------------| Updates? (Bot::get_updates call)
-//! ↑ ↑
-//! | timeout^1 |
-//! ↓ ↓
-//! Nope |--------------------------->|
-//! ↑ ↑
-//! | delay between Bot::get_updates^2 |
-//! ↓ ↓
-//! |<---------------------------| Updates?
-//! ↑ ↑
-//! | timeout^3 |
-//! ↓ ↓
-//! Yes |-------[updates 0, 1]------>|
-//! ↑ ↑
-//! | delay |
-//! ↓ ↓
-//! |<-------[offset = 1]--------| Updates?^4
-//! ↑ ↑
-//! | timeout |
-//! ↓ ↓
-//! Yes |---------[update 2]-------->|
-//! ↑ ↑
-//! | delay |
-//! ↓ ↓
-//! |<-------[offset = 2]--------| Updates?
-//! ↑ ↑
-//! | timeout |
-//! ↓ ↓
-//! Nope |--------------------------->|
-//! ↑ ↑
-//! | delay |
-//! ↓ ↓
-//! |<-------[offset = 2]--------| Updates?
-//! ↑ ↑
-//! | timeout |
-//! ↓ ↓
-//! Nope |--------------------------->|
-//! ↑ ↑
-//! | delay |
-//! ↓ ↓
-//! |<-------[offset = 2]--------| Updates?
-//! ↑ ↑
-//! | timeout |
-//! ↓ ↓
-//! Yes |-------[updates 2..5]------>|
-//! ↑ ↑
-//! | delay |
-//! ↓ ↓
-//! |<-------[offset = 5]--------| Updates?
-//! ↑ ↑
-//! | timeout |
-//! ↓ ↓
-//! Nope |--------------------------->|
-//! | |
-//! ~ and so on, and so on ~
-//!
-//!
-//! ^1 A timeout can be even 0
-//! (this is also called short polling),
-//! but you should use it **only** for testing purposes.
-//!
-//! ^2 Large delays will cause in bot lags,
-//! so delay shouldn't exceed second.
-//!
-//! ^3 Note that if Telegram already have updates for
-//! you it will answer you **without** waiting for a timeout.
-//!
-//! ^4 `offset = N` means that we've already received
-//! updates `0..=N`.
-//!
-//! # Webhooks
-//! See the [README FAQ about webhooks](https://github.com/teloxide/teloxide/blob/master/README.md#faq).
+//! Telegram supports two ways of [getting updates]: [long polling] and
+//! [webhooks]. Currently, only the former one is implemented (see [`polling()`]
+//! and [`polling_default`]). See also [README FAQ about webhooks](https://github.com/teloxide/teloxide/blob/master/README.md#faq).
//!
//! [`UpdateListener`]: UpdateListener
//! [`polling_default`]: polling_default
//! [`polling`]: polling()
+//! [`Dispatcher`]: crate::dispatching::Dispatcher
//! [`Box::get_updates`]: crate::requests::Requester::get_updates
//! [getting updates]: https://core.telegram.org/bots/api#getting-updates
-//! [long]: https://en.wikipedia.org/wiki/Push_technology#Long_polling
-//! [short]: https://en.wikipedia.org/wiki/Polling_(computer_science)
-//! [webhook]: https://en.wikipedia.org/wiki/Webhook
+//! [long polling]: https://en.wikipedia.org/wiki/Push_technology#Long_polling
+//! [webhooks]: https://en.wikipedia.org/wiki/Webhook
use futures::Stream;
@@ -122,19 +42,15 @@ pub use self::{
/// An update listener.
///
-/// Implementors of this trait allow getting updates from Telegram.
-///
-/// Currently Telegram has 2 ways of getting updates -- [polling] and
-/// [webhooks]. Currently, only the former one is implemented (see [`polling()`]
-/// and [`polling_default`])
+/// Implementors of this trait allow getting updates from Telegram. See
+/// [module-level documentation] for more.
///
/// Some functions of this trait are located in the supertrait
/// ([`AsUpdateStream`]), see also:
/// - [`AsUpdateStream::Stream`]
/// - [`AsUpdateStream::as_stream`]
///
-/// [polling]: self#long-polling
-/// [webhooks]: self#webhooks
+/// [module-level documentation]: mod@self
pub trait UpdateListener: for<'a> AsUpdateStream<'a, E> {
/// The type of token which allows to stop this listener.
type StopToken: StopToken;
@@ -150,8 +66,8 @@ pub trait UpdateListener: for<'a> AsUpdateStream<'a, E> {
/// Implementors of this function are encouraged to stop listening for
/// updates as soon as possible and return `None` from the update stream as
/// soon as all cached updates are returned.
- #[must_use = "This function doesn't stop listening, to stop listening you need to call stop on \
- the returned token"]
+ #[must_use = "This function doesn't stop listening, to stop listening you need to call `stop` \
+ on the returned token"]
fn stop_token(&mut self) -> Self::StopToken;
/// Hint which updates should the listener listen for.
diff --git a/src/dispatching/update_listeners/polling.rs b/src/dispatching/update_listeners/polling.rs
index 9a0bf8f1..27e850fe 100644
--- a/src/dispatching/update_listeners/polling.rs
+++ b/src/dispatching/update_listeners/polling.rs
@@ -31,20 +31,99 @@ where
polling(requester, Some(Duration::from_secs(10)), None, None)
}
-/// Returns a long/short polling update listener with some additional options.
+#[cfg_attr(doc, aquamarine::aquamarine)]
+/// Returns a long polling update listener with some additional options.
///
/// - `bot`: Using this bot, the returned update listener will receive updates.
-/// - `timeout`: A timeout for polling.
+/// - `timeout`: A timeout in seconds for polling.
/// - `limit`: Limits the number of updates to be retrieved at once. Values
/// between 1—100 are accepted.
/// - `allowed_updates`: A list the types of updates you want to receive.
+///
/// See [`GetUpdates`] for defaults.
///
/// See also: [`polling_default`](polling_default).
///
-/// [`GetUpdates`]: crate::payloads::GetUpdates
+/// ## Notes
+///
+/// - `timeout` should not be bigger than http client timeout, see
+/// [`default_reqwest_settings`] for default http client settings.
+/// - [`repl`]s and [`Dispatcher`] use [`hint_allowed_updates`] to set
+/// `allowed_updates`, so you rarely need to pass `allowed_updates`
+/// explicitly.
+///
+/// [`default_reqwest_settings`]: teloxide::net::default_reqwest_settings
+/// [`repl`]: fn@crate::repl
+/// [`Dispatcher`]: crate::dispatching::Dispatcher
+/// [`hint_allowed_updates`]: crate::dispatching::update_listeners::UpdateListener::hint_allowed_updates
+///
+/// ## How it works
+///
+/// Long polling works by repeatedly calling [`Bot::get_updates`][get_updates].
+/// If telegram has any updates, it returns them immediately, otherwise it waits
+/// until either it has any updates or `timeout` expires.
+///
+/// Each [`get_updates`][get_updates] call includes an `offset` parameter equal
+/// to the latest update id + one, that allows to only receive updates that has
+/// not been received before.
+///
+/// When telegram receives a [`get_updates`][get_updates] request with `offset =
+/// N` it forgets any updates with id < `N`. When `polling` listener is stopped,
+/// it sends [`get_updates`][get_updates] with `timeout = 0, limit = 1` and
+/// appropriate `offset`, so future bot restarts won't see updates that were
+/// already seen.
+///
+/// Consumers of a `polling` update listener then need to repeatedly call
+/// [`futures::StreamExt::next`] to get the updates.
+///
+/// Here is an example diagram that shows these interactions between consumers
+/// like [`Dispatcher`], `polling` update listener and telegram.
+///
+/// ```mermaid
+/// sequenceDiagram
+/// participant C as Consumer
+/// participant P as polling
+/// participant T as Telegram
+///
+/// link C: Dispatcher @ ../struct.Dispatcher.html
+/// link C: repl @ ../../fn.repl.html
+///
+/// C->>P: next
+///
+/// P->>+T: Updates? (offset = 0)
+/// Note right of T: timeout
+/// T->>-P: None
+///
+/// P->>+T: Updates? (offset = 0)
+/// Note right of T: <= timeout
+/// T->>-P: updates with ids [3, 4]
+///
+/// P->>C: update(3)
+///
+/// C->>P: next
+/// P->>C: update(4)
+///
+/// C->>P: next
+///
+/// P->>+T: Updates? (offset = 5)
+/// Note right of T: <= timeout
+/// T->>-P: updates with ids [5]
+///
+/// C->>P: stop signal
+///
+/// P->>C: update(5)
+///
+/// C->>P: next
+///
+/// P->>T: *Acknolegment of update(5)*
+/// T->>P: ok
+///
+/// P->>C: None
+/// ```
+///
+/// [get_updates]: crate::requests::Requester::get_updates
pub fn polling(
- requester: R,
+ bot: R,
timeout: Option,
limit: Option,
allowed_updates: Option>,
@@ -119,7 +198,7 @@ where
let (token, flag) = AsyncStopToken::new_pair();
let state = State {
- bot: requester,
+ bot,
timeout: timeout.map(|t| t.as_secs().try_into().expect("timeout is too big")),
limit,
allowed_updates,
diff --git a/src/lib.rs b/src/lib.rs
index 9114d239..0c73df12 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -44,8 +44,6 @@
html_logo_url = "https://github.com/teloxide/teloxide/raw/master/ICON.png",
html_favicon_url = "https://github.com/teloxide/teloxide/raw/master/ICON.png"
)]
-#![allow(clippy::match_bool)]
-#![forbid(unsafe_code)]
// We pass "--cfg docsrs" when building docs to add `This is supported on
// feature="..." only.`
//
@@ -56,6 +54,9 @@
// $ RUSTFLAGS="--cfg dep_docsrs" RUSTDOCFLAGS="--cfg docsrs -Znormalize-docs" cargo +nightly doc --open --all-features
// ```
#![cfg_attr(all(docsrs, feature = "nightly"), feature(doc_cfg))]
+#![forbid(unsafe_code)]
+#![warn(rustdoc::broken_intra_doc_links)]
+#![allow(clippy::match_bool)]
#![allow(clippy::redundant_pattern_matching)]
// https://github.com/rust-lang/rust-clippy/issues/7422
#![allow(clippy::nonstandard_macro_braces)]