use crate::{
client::{Bot, Reqwest},
context::Context,
either::Either,
errors::ExtractionError,
extensions::Extension,
Extensions, Request,
};
use std::{any::type_name, convert::Infallible, future::Future};
pub trait Extractor<Client = Reqwest>: Sized {
type Error: Into<ExtractionError>;
fn extract(request: &Request<Client>)
-> impl Future<Output = Result<Self, Self::Error>> + Send;
}
impl<Client, T: Extractor<Client>> Extractor<Client> for Option<T>
where
Client: Sync,
{
type Error = Infallible;
#[inline]
async fn extract(request: &Request<Client>) -> Result<Self, Self::Error> {
match T::extract(request).await {
Ok(value) => Ok(Some(value)),
Err(_) => Ok(None),
}
}
}
impl<Client, T, E> Extractor<Client> for Result<T, E>
where
T: Extractor<Client>,
T::Error: Into<E>,
Client: Sync,
{
type Error = Infallible;
#[inline]
async fn extract(request: &Request<Client>) -> Result<Self, Self::Error> {
Ok(T::extract(request).await.map_err(Into::into))
}
}
impl<Client, T, U> Extractor<Client> for Either<T, U>
where
T: Extractor<Client>,
U: Extractor<Client>,
Client: Sync,
{
type Error = U::Error;
#[inline]
async fn extract(request: &Request<Client>) -> Result<Self, Self::Error> {
if let Ok(value) = T::extract(request).await {
return Ok(Either::Left(value));
}
U::extract(request).await.map(Either::Right)
}
}
impl<Client> Extractor<Client> for () {
type Error = Infallible;
#[allow(clippy::manual_async_fn)]
#[inline]
fn extract(
_request: &Request<Client>,
) -> impl Future<Output = Result<Self, Self::Error>> + Send {
async move { Ok(()) }
}
}
impl<Client> Extractor<Client> for Bot<Client>
where
Client: Clone + Send,
{
type Error = Infallible;
#[inline]
fn extract(
request: &Request<Client>,
) -> impl Future<Output = Result<Self, Self::Error>> + Send {
let bot = request.bot.clone();
async move { Ok(bot) }
}
}
impl<Client> Extractor<Client> for Context {
type Error = Infallible;
#[inline]
fn extract(
request: &Request<Client>,
) -> impl Future<Output = Result<Self, Self::Error>> + Send {
let context = request.context.clone();
async move { Ok(context) }
}
}
impl<Client> Extractor<Client> for Extensions {
type Error = Infallible;
#[inline]
fn extract(
request: &Request<Client>,
) -> impl Future<Output = Result<Self, Self::Error>> + Send {
let extensions = request.extensions.clone();
async move { Ok(extensions) }
}
}
impl<Client, Value> Extractor<Client> for Extension<Value>
where
Value: Clone + Send + Sync + 'static,
{
type Error = ExtractionError;
fn extract(
request: &Request<Client>,
) -> impl Future<Output = Result<Self, Self::Error>> + Send {
let res = match request.extensions.get::<Value>() {
Some(value) => Ok(Self(value.clone())),
None => Err(ExtractionError::new(if request.extensions.is_empty() {
format!(
"Failed to extract data with type {}. Extensions are empty, it looks like you \
forgot to add a value.",
type_name::<Value>()
)
} else {
format!(
"Failed to extract data with type {}. It looks like you forgot to add a value \
of this type.",
type_name::<Value>()
)
})),
};
async move { res }
}
}
#[allow(non_snake_case)]
mod factory_extractor {
use super::{ExtractionError, Extractor, Request};
macro_rules! factory ({ $($param:ident)* } => {
impl<Client: Sync, $($param: Extractor<Client> + Send,)*> Extractor<Client> for ($($param,)*) {
type Error = ExtractionError;
async fn extract(request: &Request<Client>) -> Result<Self, Self::Error> {
Ok(($($param::extract(request).await.map_err(Into::into)?,)*))
}
}
});
factory! { A }
factory! { A B }
factory! { A B C }
factory! { A B C D }
factory! { A B C D E}
factory! { A B C D E F }
factory! { A B C D E F G}
factory! { A B C D E F G H }
factory! { A B C D E F G H I}
factory! { A B C D E F G H I J }
factory! { A B C D E F G H I J K}
factory! { A B C D E F G H I J K L }
factory! { A B C D E F G H I J K L M}
factory! { A B C D E F G H I J K L M N }
factory! { A B C D E F G H I J K L M N O}
factory! { A B C D E F G H I J K L M N O P }
}
#[allow(unreachable_code, clippy::extra_unused_type_parameters)]
#[cfg(test)]
mod tests {
use super::*;
use crate::{
errors::ConvertToTypeError,
types::{Message, MessageText, Update},
};
use std::sync::Arc;
#[test]
fn test_arg_number() {
fn assert_impl_handler<Client, T: Extractor<Client>>(_: T) {}
assert_impl_handler::<Reqwest, _>(());
assert_impl_handler::<Reqwest, _>((
(), (), (), (), (), (), (), (), (), (), (), (), (), (), (), (), ));
}
fn _check_bounds<Client, T: Extractor<Client>>() {
unimplemented!("This function is only used for checking bounds");
_check_bounds::<Client, ()>();
_check_bounds::<_, Bot>();
_check_bounds::<Client, Update>();
_check_bounds::<Client, Arc<Update>>();
_check_bounds::<Client, Context>();
_check_bounds::<Client, Extensions>();
_check_bounds::<Client, Message>();
_check_bounds::<Client, MessageText>();
}
fn _check_bounds_option<Client: Sync, T: Extractor<Client>>() {
unimplemented!("This function is only used for checking bounds");
_check_bounds::<Client, Option<()>>();
_check_bounds::<_, Option<Bot>>();
_check_bounds::<Client, Option<Update>>();
_check_bounds::<Client, Option<Arc<Update>>>();
_check_bounds::<Client, Option<Context>>();
_check_bounds::<Client, Option<Extensions>>();
_check_bounds::<Client, Option<Message>>();
_check_bounds::<Client, Option<MessageText>>();
}
fn _check_bounds_result<Client: Sync, T: Extractor<Client>, Err: Into<ExtractionError>>() {
unimplemented!("This function is only used for checking bounds");
_check_bounds::<Client, Result<(), Infallible>>();
_check_bounds::<_, Result<Bot, Infallible>>();
_check_bounds::<Client, Result<Update, Infallible>>();
_check_bounds::<Client, Result<Arc<Update>, Infallible>>();
_check_bounds::<Client, Result<Context, Infallible>>();
_check_bounds::<Client, Result<Extensions, Infallible>>();
_check_bounds::<Client, Result<Message, ConvertToTypeError>>();
_check_bounds::<Client, Result<MessageText, ConvertToTypeError>>();
}
fn _check_bounds_either<Client: Sync>() {
unimplemented!("This function is only used for checking bounds");
_check_bounds::<Client, Either<(), ()>>();
_check_bounds::<_, Either<Bot, Bot>>();
_check_bounds::<Client, Either<Update, Context>>();
_check_bounds::<Client, Either<Message, MessageText>>();
_check_bounds::<_, Either<Extension<i32>, Bot>>();
_check_bounds::<Client, Either<Option<Message>, ()>>();
}
#[tokio::test]
async fn extract_either_prefers_left_then_right() {
use crate::types::{ChatPrivate, UpdateMessage};
let request = Request::<Reqwest> {
update: Arc::new(Update::Message(UpdateMessage::new(
0,
MessageText::new(0, 0, ChatPrivate::new(0), ""),
))),
bot: Bot::default(),
context: Context::default(),
extensions: Extensions::default(),
};
let left = <Either<Bot, Extension<i32>> as Extractor>::extract(&request)
.await
.expect("left side is infallible");
assert!(matches!(left, Either::Left(_)));
let right = <Either<Extension<i32>, Bot> as Extractor>::extract(&request)
.await
.expect("right side is infallible");
assert!(matches!(right, Either::Right(_)));
let result =
<Either<Extension<i32>, Extension<String>> as Extractor>::extract(&request).await;
assert!(result.is_err());
}
}