Merge pull request #8 from teloxide/requests_redisign_p3

Implement auto sending requests
This commit is contained in:
Temirkhan Myrzamadi 2020-10-02 00:19:05 +06:00 committed by GitHub
commit 745d4a1ffe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 208 additions and 3 deletions

191
src/bot/auto_send.rs Normal file
View file

@ -0,0 +1,191 @@
use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
use futures::future::FusedFuture;
use crate::requests::{HasPayload, Output, Request, Requester};
/// Send requests automatically.
///
/// Requests returned by `<AutoSend<_> as `[`Requester`]`>` are [`Future`]s
/// which means that you can simply `.await` them instead of using
/// `.send().await`.
///
/// Notes:
/// 1. This wrapper should be the most outer i.e.: `AutoSend<CacheMe<Bot>>`
/// will automatically send requests, while `CacheMe<AutoSend<Bot>>` - won't.
/// 2. After first call to `poll` on a request you will unable to access payload
/// nor could you use [`send_ref`](Request::send_ref).
///
/// ## Examples
///
/// ```rust
/// use teloxide_core::{
/// requests::{Requester, RequesterExt},
/// types::User,
/// Bot,
/// };
///
/// # async {
/// let bot = Bot::new("TOKEN").auto_send();
/// let myself: User = bot.get_me().await?; // No .send()!
/// # Ok::<_, teloxide_core::RequestError>(()) };
/// ```
pub struct AutoSend<B> {
bot: B,
}
impl<B> AutoSend<B> {
/// Creates new `AutoSend`.
///
/// Note: it's recommended to use [`RequesterExt::auto_send`] instead.
///
/// [`RequesterExt::auto_send`]: crate::requests::RequesterExt::auto_send
pub fn new(inner: B) -> AutoSend<B> {
Self { bot: inner }
}
/// Allows to access the inner bot.
pub fn inner(&self) -> &B {
&self.bot
}
/// Unwraps the inner bot.
pub fn into_inner(self) -> B {
self.bot
}
}
impl<B: Requester> Requester for AutoSend<B> {
type GetMe = AutoRequest<B::GetMe>;
fn get_me(&self) -> Self::GetMe {
AutoRequest::new(self.bot.get_me())
}
}
#[pin_project::pin_project]
pub struct AutoRequest<R: Request>(#[pin] Inner<R>);
impl<R: Request> AutoRequest<R> {
pub fn new(inner: R) -> Self {
Self(Inner::Request(inner))
}
}
/// Data of the `AutoRequest` used to not expose variants (I wish there were
/// private enum variants).
#[pin_project::pin_project(project = InnerProj, project_replace = InnerRepl)]
enum Inner<R: Request> {
/// An unsent modifiable request.
Request(R),
/// A sent request.
Future(#[pin] R::Send),
/// Done state. Set after `R::Send::poll` returned `Ready(_)`.
///
/// Also used as a temporary replacement to turn pinned `Request(req)`
/// into `Future(req.send())` in `AutoRequest::poll`.
Done,
}
impl<R: Request> Request for AutoRequest<R> {
type Err = R::Err;
type Send = R::Send;
type SendRef = R::SendRef;
fn send(self) -> Self::Send {
match self.0 {
Inner::Request(req) => req.send(),
Inner::Future(fut) => fut,
Inner::Done => done_unreachable(),
}
}
fn send_ref(&self) -> Self::SendRef {
match &self.0 {
Inner::Request(req) => req.send_ref(),
Inner::Future(_) => already_polled(),
Inner::Done => done_unreachable(),
}
}
}
impl<R: Request> HasPayload for AutoRequest<R> {
type Payload = R::Payload;
fn payload_mut(&mut self) -> &mut Self::Payload {
match &mut self.0 {
Inner::Request(req) => req.payload_mut(),
Inner::Future(_) => already_polled(),
Inner::Done => done_unreachable(),
}
}
fn payload_ref(&self) -> &Self::Payload {
match &self.0 {
Inner::Request(req) => req.payload_ref(),
Inner::Future(_) => already_polled(),
Inner::Done => done_unreachable(),
}
}
}
impl<R: Request> Future for AutoRequest<R> {
type Output = Result<Output<R>, R::Err>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this: Pin<&mut Inner<_>> = self.as_mut().project().0;
match this.as_mut().project() {
// Poll the underling future.
InnerProj::Future(fut) => {
let res = futures::ready!(fut.poll(cx));
// We've got the result, so we set the state to done.
this.set(Inner::Done);
Poll::Ready(res)
}
// This future is fused.
InnerProj::Done => Poll::Pending,
// The `AutoRequest` future was polled for the first time after
// creation. We need to transform it into sent form by calling
// `R::send` and doing some magic around Pin.
InnerProj::Request(_) => {
// Replace `Request(_)` by `Done(_)` to obtain ownership over
// the former.
let inner = this.as_mut().project_replace(Inner::Done);
// Map Request(req) to `Future(req.send())`.
let inner = match inner {
InnerRepl::Request(req) => Inner::Future(req.send()),
// Practically this is unreachable, because we've just checked for
// both `Future(_)` and `Done` variants.
InnerRepl::Future(_) | InnerRepl::Done => done_unreachable(),
};
// Set the resulting `Future(_)` back to pin.
this.set(inner);
// Poll `self`. This time another brunch will be executed, returning `Poll`.
self.poll(cx)
}
}
}
}
impl<R: Request> FusedFuture for AutoRequest<R> {
fn is_terminated(&self) -> bool {
matches!(&self.0, Inner::Done)
}
}
#[inline(never)]
fn done_unreachable() -> ! {
unreachable!("future is completed and as such doesn't provide any functionality")
}
#[inline(never)]
fn already_polled() -> ! {
panic!("AutoRequest was already polled once")
}

View file

@ -27,6 +27,8 @@ impl<B> CacheMe<B> {
/// Creates new cache.
///
/// Note: it's recommended to use [`RequesterExt::cache_me`] instead.
///
/// [`RequesterExt::cache_me`]: crate::requests::RequesterExt::cache_me
pub fn new(bot: B) -> CacheMe<B> {
Self { bot, me: Arc::new(OnceCell::new()) }
}

View file

@ -14,9 +14,11 @@ use crate::{
};
mod api;
mod auto_send;
mod cache_me;
mod download;
pub use auto_send::AutoSend;
pub use cache_me::CacheMe;
pub(crate) const TELOXIDE_TOKEN: &str = "TELOXIDE_TOKEN";

View file

@ -13,10 +13,12 @@
//#![deny(missing_docs)]
#[macro_use]
mod local_macros; // internal helper macros
// The internal helper macros.
mod local_macros;
// FIXME(waffle): rethink modules, find a place for wrappers.
pub use self::{
bot::{Bot, BotBuilder},
bot::{AutoSend, Bot, BotBuilder, CacheMe},
errors::{ApiErrorKind, DownloadError, KnownApiErrorKind, RequestError},
};

View file

@ -1,4 +1,4 @@
use crate::{bot::CacheMe, requests::Requester};
use crate::{requests::Requester, AutoSend, CacheMe};
pub trait RequesterExt: Requester {
/// Add `get_me` caching ability, see [`CacheMe`] for more.
@ -8,6 +8,14 @@ pub trait RequesterExt: Requester {
{
CacheMe::new(self)
}
/// Send requests automatically, see [`AutoSend`] for more.
fn auto_send(self) -> AutoSend<Self>
where
Self: Sized,
{
AutoSend::new(self)
}
}
impl<T> RequesterExt for T