Merge pull request #6 from teloxide/requests_redisign_p1

implement default Bot's {Json,Multipart}Request
This commit is contained in:
Waffle Lapkin 2020-09-20 20:00:09 +03:00 committed by GitHub
commit 7d7fd89bfa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 413 additions and 5 deletions

View file

@ -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"] }

View file

@ -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;
@ -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

View file

@ -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)
}
}
};
}

View file

@ -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,
};

View file

@ -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::{
@ -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,

106
src/requests/json.rs Normal file
View file

@ -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,
}

View file

@ -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>;

116
src/requests/multipart.rs Normal file
View file

@ -0,0 +1,116 @@
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 [`multipart/form-data`]
///
/// [`multipart/form-data`]: https://core.telegram.org/bots/api#making-requests
#[must_use = "requests do nothing until sent"]
pub struct MultipartRequest<P> {
bot: Bot,
payload: P,
}
impl<P> MultipartRequest<P> {
pub fn new(bot: Bot, payload: P) -> Self {
Self { bot, payload }
}
}
impl<P> Request for MultipartRequest<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 MultipartRequest<P>
where
P: Payload,
{
type Payload = P;
}
impl<P> AsRef<P> for MultipartRequest<P> {
fn as_ref(&self) -> &P {
&self.payload
}
}
impl<P> AsMut<P> for MultipartRequest<P> {
fn as_mut(&mut self) -> &mut P {
&mut self.payload
}
}
impl<P> core::ops::Deref for MultipartRequest<P>
where
P: 'static,
P: Payload + Serialize,
P::Output: DeserializeOwned,
{
type Target = P;
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl<P> core::ops::DerefMut for MultipartRequest<P>
where
P: 'static,
P: Payload + Serialize,
P::Output: DeserializeOwned,
{
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_mut()
}
}
req_future! {
def: |it: MultipartRequest<U>| {
it.bot.execute_multipart(&it.payload)
}
pub Send<U> (inner0) -> ResponseResult<U::Output>
where
U: 'static,
U: Payload + Serialize,
U::Output: DeserializeOwned,
}
req_future! {
def: |it: &MultipartRequest<U>| {
it.bot.execute_multipart(&it.payload)
}
pub SendRef<U> (inner1) -> ResponseResult<U::Output>
where
U: 'static,
U: Payload + Serialize,
U::Output: DeserializeOwned,
}

View file

@ -50,7 +50,7 @@ pub trait Request: HasPayload {
/// and then serializing it, this method should just serialize the data)
///
/// ## Examples
// FIXME(waffle): ignored until full request redisign lands
// FIXME(waffle): ignored until full request redesign lands
/// ```ignore
/// # async {
/// use teloxide_core::prelude::*;

View file

@ -1,6 +1,7 @@
use crate::{
serde_multipart::unserializers::{InputFileUnserializer, StringUnserializer},
types::InputFile,
RequestError,
};
use futures::{
future::{ready, BoxFuture},
@ -53,6 +54,20 @@ impl std::error::Error for Error {
}
}
impl From<Error> for RequestError {
fn from(err: Error) -> Self {
match err {
Error::Io(ioerr) => RequestError::Io(ioerr),
// this should be ok since we don't write request those may trigger errors and
// Error is internal.
_ => unreachable!(
"we don't create requests those fail to serialize (if you see this, open an issue \
:|)"
),
}
}
}
pub(crate) struct MultipartTopLvlSerializer {}
impl Serializer for MultipartTopLvlSerializer {