use std::{future::Future, marker::PhantomData};
use crate::{
Body, FromRequest, FromRequestParts, HttpService, IntoHttpService, IntoResponse, Response,
};
pub struct FnService<F, A, B> {
inner: F,
_phantom: PhantomData<fn(A) -> B>,
}
impl<F, A, B> FnService<F, A, B> {
fn new(inner: F) -> Self {
Self { inner, _phantom: PhantomData }
}
}
impl<F, X, B> HttpService<B> for FnService<F, X, B>
where
B: Send,
F: ServiceFn<X, B> + Sync,
{
type ResponseBody = Body;
fn call(
&self,
request: http::Request<B>,
) -> impl Future<Output = Response<Self::ResponseBody>> + Send {
self.inner.do_call(request)
}
}
#[doc(hidden)]
pub struct ImplIntoHttpServiceForServiceFnTag<X>(PhantomData<X>);
impl<F, X, B> IntoHttpService<B, ImplIntoHttpServiceForServiceFnTag<X>> for F
where
F: ServiceFn<X, B> + Sync,
B: Send,
{
type Service = FnService<Self, X, B>;
type ResponseBody = Body;
fn into_svc(self) -> Self::Service {
FnService::new(self)
}
}
pub trait ServiceFn<X, B> {
#[doc(hidden)]
fn do_call(&self, request: http::Request<B>) -> impl Future<Output = Response> + Send;
}
impl<F, B, R> ServiceFn<(), B> for F
where
F: Fn() -> R,
B: Send,
R: Future<Output: IntoResponse> + Send,
{
#[allow(clippy::manual_async_fn)]
#[inline]
fn do_call(&self, _request: http::Request<B>) -> impl Future<Output = Response> + Send {
run_into_response_fut(self())
}
}
macro_rules! impl_service_fn {
( [ $($args:ident),* ], $last:ident ) => {
impl<F, $($args,)* $last, M, B, R> ServiceFn<(M, $($args,)* $last), B> for F
where
F: Fn( $($args,)* $last ) -> R + Sync,
$( $args: FromRequestParts, )*
$last: FromRequest<B, M>,
B: Send,
R: Future<Output: IntoResponse> + Send,
{
#[allow(non_snake_case)]
async fn do_call(&self, request: http::Request<B>) -> Response {
#[allow(unused_mut)]
let (mut parts, body) = request.into_parts();
$(
let $args = match $args::from_request_parts(&mut parts).await {
Ok(extracted) => extracted,
Err(response) => return into_response_and_unwrap(response),
};
)*
let request = http::Request::from_parts(parts, body);
let $last = match $last::from_request(request).await {
Ok(extracted) => extracted,
Err(response) => return into_response_and_unwrap(response),
};
run_into_response_fut(self($($args,)* $last)).await
}
}
};
}
impl_service_fn!([], A);
impl_service_fn!([A1], A2);
impl_service_fn!([A1, A2], A3);
impl_service_fn!([A1, A2, A3], A4);
#[inline]
fn into_response_and_unwrap(res: impl IntoResponse) -> Response {
let (Ok(res) | Err(res)) = res.into_response();
res
}
#[allow(clippy::manual_async_fn)]
#[inline]
fn run_into_response_fut(fut: impl Future<Output: IntoResponse>) -> impl Future<Output = Response> {
async { into_response_and_unwrap(fut.await) }
}
#[cfg(test)]
mod tests {
use crate::{Body, HttpService, IntoHttpService};
#[test]
fn simple_service_fn() {
async fn service_fn(_req: http::Request<Body>) -> http::Response<Body> {
http::Response::new(Body::empty())
}
let svc = service_fn.into_svc();
_ = svc.call(http::Request::new(Body::empty()));
}
}