mirror of
https://github.com/teloxide/teloxide.git
synced 2025-03-13 11:18:17 +01:00
Merge pull request #6 from teloxide/requests_redisign_p1
implement default Bot's {Json,Multipart}Request
This commit is contained in:
commit
7d7fd89bfa
10 changed files with 413 additions and 5 deletions
|
@ -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"] }
|
||||
|
|
|
@ -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¶ms
|
||||
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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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
106
src/requests/json.rs
Normal 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,
|
||||
}
|
|
@ -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
116
src/requests/multipart.rs
Normal 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,
|
||||
}
|
|
@ -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::*;
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue