zon_core 0.0.4

part of a new WIP, very incomplete async http service stack
Documentation
//! Turn functions into services.
//!
//! This module only exists to contain the documentation of [`FnService`] and
//! [`ServiceFn`]. You should never have to name these directly anywhere, which
//! is why this module intentionally has a name that's quite awkward to write.

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,
{
    // I'm worried that using async fn sugar is going to mean the request is
    // going to be included in the generated future type, whereas with the
    // async block this is clearly not the case.
    #[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);

// Give the compiler a few opportunities to introduce function boundaries, by
// splitting the logic into `#[inline]` (not `#[inline(always)]`!) functions.
#[inline]
fn into_response_and_unwrap(res: impl IntoResponse) -> Response {
    let (Ok(res) | Err(res)) = res.into_response();
    res
}

// async fn has some inefficiencies around future size, so don't use it
// for such a low-level thing that's going to be invoked a lot.
// Upstream issue: https://github.com/rust-lang/rust/issues/62958
#[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()));
    }
}