Skip to content
This repository has been archived by the owner on Nov 8, 2022. It is now read-only.

implement default Bot's {Json,Multipart}Request #6

Merged
merged 1 commit into from
Sep 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ authors = [
futures = "0.3.5"
tokio = { version = "0.2.21", features = ["fs", "stream"] }
tokio-util = "0.3.1"
pin-project = "0.4.23"
bytes = "0.5.5"
async-trait = "0.1.36"
reqwest = { version = "0.10.6", features = ["json", "stream"] }
Expand Down
53 changes: 51 additions & 2 deletions src/bot/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
use crate::types::ParseMode;
use std::{future::Future, sync::Arc, time::Duration};

use reqwest::{
header::{HeaderMap, CONNECTION},
Client, ClientBuilder,
};
use std::{sync::Arc, time::Duration};
use serde::{de::DeserializeOwned, Serialize};

use crate::{
net,
requests::{Payload, ResponseResult},
serde_multipart,
types::ParseMode,
};

mod api;
mod download;
Expand Down Expand Up @@ -100,6 +108,47 @@ impl Bot {
}
}

impl Bot {
pub(crate) fn execute_json<P>(
&self,
payload: &P,
) -> impl Future<Output = ResponseResult<P::Output>> + 'static
where
P: Payload + Serialize,
P::Output: DeserializeOwned,
{
let client = self.client.clone();
let token = Arc::clone(&self.token);

let params = serde_json::to_vec(payload)
// this `expect` should be ok since we don't write request those may trigger error here
.expect("serialization of request to be infallible");

// async move to capture client&token
async move { net::request_json2(&client, token.as_ref(), P::NAME, params).await }
}

pub(crate) fn execute_multipart<P>(
&self,
payload: &P,
) -> impl Future<Output = ResponseResult<P::Output>>
where
P: Payload + Serialize,
P::Output: DeserializeOwned,
{
let client = self.client.clone();
let token = Arc::clone(&self.token);

let params = serde_multipart::to_form(payload);

// async move to capture client&token&params
async move {
let params = params.await?;
net::request_multipart2(&client, token.as_ref(), P::NAME, params).await
}
}
}

/// Returns a builder with safe settings.
///
/// By "safe settings" I mean that a client will be able to work in long time
Expand Down
70 changes: 70 additions & 0 deletions src/local_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,73 @@ macro_rules! forward_to_unsuported_ty {
)+
};
}

#[macro_use]
macro_rules! req_future {
(
$v2:vis def: | $( $arg:ident: $ArgTy:ty ),* $(,)? | $body:block

$(#[$($meta:tt)*])*
$v:vis $i:ident<$T:ident> ($inner:ident) -> $Out:ty
$(where $($wh:tt)*)?
) => {
#[pin_project::pin_project]
$v struct $i<$T>
$(where $($wh)*)?
{
#[pin]
inner: $inner::$i<$T>
}

impl<$T> $i<$T>
$(where $($wh)*)?
{
$v2 fn new($( $arg: $ArgTy ),*) -> Self {
Self { inner: $inner::def($( $arg ),*) }
}
}

// HACK(waffle): workaround for https://github.com/rust-lang/rust/issues/55997
mod $inner {
#![allow(type_alias_bounds)]

// Mostly to bring `use`s
#[allow(unused_imports)]
use super::{*, $i as _};

#[cfg(feature = "nightly")]
pub(crate) type $i<$T>
$(where $($wh)*)? = impl ::core::future::Future<Output = $Out>;

#[cfg(feature = "nightly")]
pub(crate) fn def<$T>($( $arg: $ArgTy ),*) -> $i<$T>
$(where $($wh)*)?
{
$body
}

#[cfg(not(feature = "nightly"))]
pub(crate) type $i<$T>
$(where $($wh)*)? = ::core::pin::Pin<Box<dyn ::core::future::Future<Output = $Out>>>;

#[cfg(not(feature = "nightly"))]
pub(crate) fn def<$T>($( $arg: $ArgTy ),*) -> $i<$T>
$(where $($wh)*)?
{
Box::pin($body)
}
}

impl<$T> ::core::future::Future for $i<$T>
$(where $($wh)*)?
{
type Output = $Out;

fn poll(self: ::core::pin::Pin<&mut Self>, cx: &mut ::core::task::Context<'_>) -> ::core::task::Poll<Self::Output> {
let this = self.project();
this.inner.poll(cx)
}
}

};
}
2 changes: 1 addition & 1 deletion src/net/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pub use download::download_file_stream;

pub use self::{
download::download_file,
request::{request_json, request_multipart},
request::{request_json, request_json2, request_multipart, request_multipart2},
telegram_response::TelegramResponse,
};

Expand Down
49 changes: 48 additions & 1 deletion src/net/request.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::time::Duration;

use reqwest::{Client, Response};
use reqwest::{
header::{HeaderValue, CONTENT_TYPE},
Client, Response,
};
use serde::{de::DeserializeOwned, Serialize};

use crate::{
Expand Down Expand Up @@ -61,6 +64,50 @@ where
process_response(response).await
}

// FIXME(waffle):
// request_{json,mutipart} are currently used in old code, so we keep them
// for now when they will not be used anymore, we should remove them
// and rename request_{json,mutipart}2 => request_{json,mutipart}

pub async fn request_multipart2<T>(
client: &Client,
token: &str,
method_name: &str,
params: reqwest::multipart::Form,
) -> ResponseResult<T>
where
T: DeserializeOwned,
{
let response = client
.post(&super::method_url(TELEGRAM_API_URL, token, method_name))
.multipart(params)
.send()
.await
.map_err(RequestError::NetworkError)?;

process_response(response).await
}

pub async fn request_json2<T>(
client: &Client,
token: &str,
method_name: &str,
params: Vec<u8>,
) -> ResponseResult<T>
where
T: DeserializeOwned,
{
let response = client
.post(&super::method_url(TELEGRAM_API_URL, token, method_name))
.header(CONTENT_TYPE, HeaderValue::from_static("application/json"))
.body(params)
.send()
.await
.map_err(RequestError::NetworkError)?;

process_response(response).await
}

async fn process_response<T>(response: Response) -> ResponseResult<T>
where
T: DeserializeOwned,
Expand Down
106 changes: 106 additions & 0 deletions src/requests/json.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use serde::{de::DeserializeOwned, Serialize};

use crate::{
bot::Bot,
requests::{HasPayload, Payload, Request, ResponseResult},
RequestError,
};

/// Ready-to-send telegram request.
///
/// Note: payload will be sent to telegram using [`json`]
///
/// [`json`]: https://core.telegram.org/bots/api#making-requests
#[must_use = "requests do nothing until sent"]
pub struct JsonRequest<P> {
bot: Bot,
payload: P,
}

impl<P> JsonRequest<P> {
pub const fn new(bot: Bot, payload: P) -> Self {
Self { bot, payload }
}
}

impl<P> Request for JsonRequest<P>
where
// FIXME(waffle):
// this is required on stable because of
// https://github.com/rust-lang/rust/issues/76882
// when it's resolved or `type_alias_impl_trait` feature
// stabilized, we should remove 'static restriction
//
// (though critically, currently we have no
// non-'static payloads)
P: 'static,
P: Payload + Serialize,
P::Output: DeserializeOwned,
{
type Err = RequestError;
type Send = Send<P>;
type SendRef = SendRef<P>;

fn send(self) -> Self::Send {
Send::new(self)
}

fn send_ref(&self) -> Self::SendRef {
SendRef::new(self)
}
}

impl<P> HasPayload for JsonRequest<P>
where
P: Payload,
{
type Payload = P;
}

impl<P> AsRef<P> for JsonRequest<P> {
fn as_ref(&self) -> &P {
&self.payload
}
}

impl<P> AsMut<P> for JsonRequest<P> {
fn as_mut(&mut self) -> &mut P {
&mut self.payload
}
}

impl<P: Payload + Serialize> core::ops::Deref for JsonRequest<P> {
type Target = P;

fn deref(&self) -> &Self::Target {
self.as_ref()
}
}

impl<P: Payload + Serialize> core::ops::DerefMut for JsonRequest<P> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_mut()
}
}

req_future! {
def: |it: JsonRequest<U>| {
it.bot.execute_json(&it.payload)
}
pub Send<U> (inner0) -> ResponseResult<U::Output>
where
U: 'static,
U: Payload + Serialize,
U::Output: DeserializeOwned,
}

req_future! {
def: |it: &JsonRequest<U>| {
it.bot.execute_json(&it.payload)
}
pub SendRef<U> (inner1) -> ResponseResult<U::Output>
where
U: 'static,
U: Payload + Serialize,
U::Output: DeserializeOwned,
}
4 changes: 4 additions & 0 deletions src/requests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ mod request;
pub use self::{has_payload::HasPayload, payload::Payload, request::Request};

mod all;
mod json;
mod multipart;
mod utils;

pub use all::*;
pub use json::JsonRequest;
pub use multipart::MultipartRequest;

/// A type that is returned after making a request to Telegram.
pub type ResponseResult<T> = Result<T, crate::RequestError>;
Expand Down
Loading