use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use typeway_core::ApiSpec;
use crate::body::BoxBody;
use crate::extract::{FromRequest, FromRequestParts};
use crate::handler::BoxedHandler;
use crate::handler_for::{BindableEndpoint, BoundHandler};
use crate::response::IntoResponse;
pub struct Protected<Auth, E> {
_marker: PhantomData<(Auth, E)>,
}
impl<Auth, E: ApiSpec> ApiSpec for Protected<Auth, E> {}
impl<Auth, E, Provided, Idx> typeway_core::effects::AllProvided<Provided, Idx>
for Protected<Auth, E>
where
E: typeway_core::effects::AllProvided<Provided, Idx>,
{
}
pub trait AuthHandler<Auth, Args>: Clone + Send + Sync + 'static {
fn call(
self,
parts: http::request::Parts,
body: bytes::Bytes,
) -> Pin<Box<dyn Future<Output = http::Response<BoxBody>> + Send>>;
}
impl<F, Fut, Res, Auth> AuthHandler<Auth, ()> for F
where
F: FnOnce(Auth) -> Fut + Clone + Send + Sync + 'static,
Fut: Future<Output = Res> + Send,
Res: IntoResponse,
Auth: FromRequestParts + 'static,
{
fn call(
self,
parts: http::request::Parts,
_body: bytes::Bytes,
) -> Pin<Box<dyn Future<Output = http::Response<BoxBody>> + Send>> {
Box::pin(async move {
let auth = match Auth::from_request_parts(&parts) {
Ok(v) => v,
Err(e) => return e.into_response(),
};
self(auth).await.into_response()
})
}
}
macro_rules! impl_auth_handler_parts {
([$($T:ident),+], [$($t:ident),+]) => {
#[allow(non_snake_case)]
impl<F, Fut, Res, Auth, $($T,)+> AuthHandler<Auth, ($($T,)+)> for F
where
F: FnOnce(Auth, $($T,)+) -> Fut + Clone + Send + Sync + 'static,
Fut: Future<Output = Res> + Send,
Res: IntoResponse,
Auth: FromRequestParts + 'static,
$($T: FromRequestParts + 'static,)+
{
fn call(
self,
parts: http::request::Parts,
_body: bytes::Bytes,
) -> Pin<Box<dyn Future<Output = http::Response<BoxBody>> + Send>> {
Box::pin(async move {
let auth = match Auth::from_request_parts(&parts) {
Ok(v) => v,
Err(e) => return e.into_response(),
};
$(
let $t = match $T::from_request_parts(&parts) {
Ok(v) => v,
Err(e) => return e.into_response(),
};
)+
self(auth, $($t,)+).await.into_response()
})
}
}
};
}
impl_auth_handler_parts!([T1], [t1]);
impl_auth_handler_parts!([T1, T2], [t1, t2]);
impl_auth_handler_parts!([T1, T2, T3], [t1, t2, t3]);
impl_auth_handler_parts!([T1, T2, T3, T4], [t1, t2, t3, t4]);
impl_auth_handler_parts!([T1, T2, T3, T4, T5], [t1, t2, t3, t4, t5]);
impl_auth_handler_parts!([T1, T2, T3, T4, T5, T6], [t1, t2, t3, t4, t5, t6]);
macro_rules! impl_auth_handler_with_body {
([], []) => {
impl<F, Fut, Res, Auth, B> AuthHandler<Auth, AuthWithBody<(), B>> for F
where
F: FnOnce(Auth, B) -> Fut + Clone + Send + Sync + 'static,
Fut: Future<Output = Res> + Send,
Res: IntoResponse,
Auth: FromRequestParts + 'static,
B: FromRequest + 'static,
{
fn call(
self,
parts: http::request::Parts,
body: bytes::Bytes,
) -> Pin<Box<dyn Future<Output = http::Response<BoxBody>> + Send>> {
Box::pin(async move {
let auth = match Auth::from_request_parts(&parts) {
Ok(v) => v,
Err(e) => return e.into_response(),
};
let b = match B::from_request(&parts, body).await {
Ok(v) => v,
Err(e) => return e.into_response(),
};
self(auth, b).await.into_response()
})
}
}
};
([$($T:ident),+], [$($t:ident),+]) => {
#[allow(non_snake_case)]
impl<F, Fut, Res, Auth, $($T,)+ B> AuthHandler<Auth, AuthWithBody<($($T,)+), B>> for F
where
F: FnOnce(Auth, $($T,)+ B) -> Fut + Clone + Send + Sync + 'static,
Fut: Future<Output = Res> + Send,
Res: IntoResponse,
Auth: FromRequestParts + 'static,
$($T: FromRequestParts + 'static,)+
B: FromRequest + 'static,
{
fn call(
self,
parts: http::request::Parts,
body: bytes::Bytes,
) -> Pin<Box<dyn Future<Output = http::Response<BoxBody>> + Send>> {
Box::pin(async move {
let auth = match Auth::from_request_parts(&parts) {
Ok(v) => v,
Err(e) => return e.into_response(),
};
$(
let $t = match $T::from_request_parts(&parts) {
Ok(v) => v,
Err(e) => return e.into_response(),
};
)+
let b = match B::from_request(&parts, body).await {
Ok(v) => v,
Err(e) => return e.into_response(),
};
self(auth, $($t,)+ b).await.into_response()
})
}
}
};
}
pub struct AuthWithBody<Parts, Body>(PhantomData<(Parts, Body)>);
impl_auth_handler_with_body!([], []);
impl_auth_handler_with_body!([T1], [t1]);
impl_auth_handler_with_body!([T1, T2], [t1, t2]);
impl_auth_handler_with_body!([T1, T2, T3], [t1, t2, t3]);
impl_auth_handler_with_body!([T1, T2, T3, T4], [t1, t2, t3, t4]);
impl_auth_handler_with_body!([T1, T2, T3, T4, T5], [t1, t2, t3, t4, t5]);
pub trait ProtectedEndpoint {
type Auth;
type Inner: BindableEndpoint;
}
impl<Auth, E: BindableEndpoint> ProtectedEndpoint for Protected<Auth, E> {
type Auth = Auth;
type Inner = E;
}
pub fn bind_protected<P, H, Args>(handler: H) -> BoundHandler<P>
where
P: ProtectedEndpoint,
P::Auth: FromRequestParts + 'static,
P::Inner: BindableEndpoint,
H: AuthHandler<P::Auth, Args>,
Args: 'static,
{
let method = P::Inner::method();
let pattern = P::Inner::pattern();
let match_fn = P::Inner::match_fn();
let boxed: BoxedHandler = std::sync::Arc::new(move |parts, body| {
let h = handler.clone();
h.call(parts, body)
});
BoundHandler::new(method, pattern, match_fn, boxed)
}
#[macro_export]
macro_rules! bind_auth {
($handler:expr) => {
$crate::auth::bind_protected::<_, _, _>($handler)
};
}