worker-route 0.0.3

Route handlers an data extractor for Cloudflare Workers.
Documentation
use crate::{
    http::HttpRequest,
    http::ResponseError,
    http::{InternalResponder, Responder},
    Query,
};
use futures::Future;
use serde::de::DeserializeOwned;
use worker::{Cors, Request, Response, RouteContext};

pub fn responder<T, E>(
    cors: Option<&Cors>,
    req: HttpRequest,
    res: Result<T, E>,
) -> worker::Result<Response>
where
    T: Responder,
    E: ResponseError,
{
    res.res(req, cors)
}

type WithQuery<D, T, U> = fn(Query<T>, RouteContext<D>) -> U;
type WithReq<D, T, U> = fn(Query<T>, Request, RouteContext<D>) -> U;

#[derive(Copy, Clone)]
pub enum FnType<D, T, U> {
    WithQuery(WithQuery<D, T, U>),
    WithReq(WithReq<D, T, U>),
}

#[allow(clippy::future_not_send)]
pub async fn respond_async<T: Responder, E: ResponseError>(
    req: HttpRequest,
    res: Result<impl Future<Output = Result<T, E>>, Box<dyn ResponseError>>,
    cors: Option<&Cors>,
) -> worker::Result<Response> {
    match res {
        Ok(res) => res.await.res(req, cors),
        Err(err) => Ok(err.error_response(req).into_response(cors).into_res()),
    }
}

trait Respond {
    fn res(self, req: HttpRequest, cors: Option<&Cors>) -> worker::Result<Response>;
}

impl<T, E> Respond for Result<T, E>
where
    T: Responder,
    E: ResponseError,
{
    fn res(self, req: HttpRequest, cors: Option<&Cors>) -> worker::Result<Response> {
        match self {
            Ok(res_) => Ok(res_.to_response(req).into_response(cors).into_res()),
            Err(err) => Ok(err.error_response(req).into_response(cors).into_res()),
        }
    }
}

macro_rules! impl_wrap_query {
    ($wrap:ident, $query:ident, $ctx:ident, $req:ident) => {
        match $wrap {
            Self::WithQuery(fn_) => fn_($query, $ctx),
            Self::WithReq(fn_) => fn_($query, $req, $ctx),
        }
    };

    ($ctx:ident, $req:ident) => {
        Query::_internal_query::<RouteContext<D>>($req.url(), &$ctx)
    };
}

impl<D, T, U, E> FnType<D, T, Result<U, E>>
where
    T: DeserializeOwned,
    U: Responder,
    E: ResponseError,
{
    pub fn wrap(
        wrap_: &Self,
        req: Request,
        ctx: RouteContext<D>,
        cors: Option<&Cors>,
    ) -> worker::Result<Response> {
        let http = HttpRequest::from(&req);
        match impl_wrap_query!(ctx, req) {
            Ok(query) => impl_wrap_query!(wrap_, query, ctx, req).res(http, cors),
            Err(err) => Ok(err.error_response(http).into_response(cors).into_res()),
        }
    }
}

impl<D, Q: DeserializeOwned, U> FnType<D, Q, U> {
    pub fn wrap_async<T, E>(
        wrap_: &Self,
        req: Request,
        ctx: RouteContext<D>,
    ) -> Result<U, Box<dyn ResponseError>>
    where
        U: Future<Output = Result<T, E>>,
        T: Responder,
        E: ResponseError,
    {
        match impl_wrap_query!(ctx, req) {
            Ok(query) => Ok(impl_wrap_query!(wrap_, query, ctx, req)),
            Err(err) => Err(Box::new(err)),
        }
    }
}