ohkami 0.24.9

A performant, declarative, and runtime-flexible web framework for Rust
Documentation
mod into_handler;

pub use self::into_handler::IntoHandler;
use super::{BoxedFPC, FangProcCaller};
use super::{SendOnThreaded, SendOnThreadedFuture, SendSyncOnThreaded};
use crate::{Request, Response};
use std::pin::Pin;

#[cfg(feature = "openapi")]
use crate::openapi;

pub struct Handler {
    #[allow(dead_code/* read only in router */)]
    pub(crate) proc: BoxedFPC,

    #[cfg(feature = "openapi")]
    pub(crate) openapi_operation: openapi::Operation,
}

const _: () = {
    impl std::fmt::Debug for Handler {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            f.write_str("{handler}")
        }
    }
};

impl Handler {
    pub(crate) fn new(
        proc: impl Fn(&mut Request) -> Pin<Box<dyn SendOnThreadedFuture<Response> + '_>>
        + SendSyncOnThreaded
        + 'static,
        #[cfg(feature = "openapi")] openapi_operation: openapi::Operation,
    ) -> Self {
        struct HandlerProc<F>(F);
        const _: () = {
            impl<F> FangProcCaller for HandlerProc<F>
            where
                F: Fn(&mut Request) -> Pin<Box<dyn SendOnThreadedFuture<Response> + '_>>
                    + SendSyncOnThreaded
                    + 'static,
            {
                fn call_bite<'b>(
                    &'b self,
                    req: &'b mut Request,
                ) -> Pin<Box<dyn SendOnThreadedFuture<Response> + 'b>> {
                    // SAFETY: trait upcasting
                    // trait upcasting coercion is experimental <https://github.com/rust-lang/rust/issues/65991>
                    unsafe { std::mem::transmute((self.0)(req)) }
                }
            }
        };

        Self {
            proc: BoxedFPC::from_proc(HandlerProc(proc)),

            #[cfg(feature = "openapi")]
            openapi_operation,
        }
    }

    /// A utility to create an owned `Handler` instance,
    /// with `openapi_operation` cloned and `proc` replaced to a dummy (meaningless thing),
    /// from a `&Handler`.
    ///
    /// (used in [`crate::router::base::Router::to_dummy_owned_for_openapi`])
    #[cfg(feature = "openapi")]
    #[cfg(feature = "__rt__")]
    pub(crate) fn to_dummy_owned_for_openapi(&self) -> Self {
        Self::new(
            |_| Box::pin(async { Response::OK() }),
            self.openapi_operation.clone(),
        )
    }
}

#[cfg(not(feature = "__rt_threaded__"))]
const _: (/* for NOT FOUND Handler cache */) = {
    unsafe impl Send for Handler {}
    unsafe impl Sync for Handler {}
};

#[cfg(feature = "__rt__")]
impl Handler {
    pub(crate) fn default_not_found() -> Self {
        Handler::new(
            |_| Box::pin(async { Response::NotFound() }),
            #[cfg(feature = "openapi")]
            openapi::Operation::with(openapi::Responses::new([(
                404,
                openapi::Response::when("default not found"),
            )])),
        )
    }

    pub(crate) fn default_options_with(available_methods: Vec<&'static str>) -> Self {
        let available_methods = {
            let mut methods = available_methods;
            if methods.contains(&"GET") {
                methods.push("HEAD")
            }
            methods.push("OPTIONS");
            methods
        };

        let available_methods_str: &'static str = available_methods.join(", ").leak();

        /* see `fang::Cors` for more detail about what to do here */
        Handler::new(
            move |req| {
                #[cfg(debug_assertions)]
                {
                    assert_eq!(req.method, crate::Method::OPTIONS);
                }

                let response = match req.headers.access_control_request_method() {
                    Some(method) => {
                        /*
                        Ohkami, by default, does nothing more than setting
                        `Access-Control-Allow-Methods` to preflight request.
                        CORS fang must override `Not Implemented` response,
                        whitch is the default for a valid preflight request,
                        by a successful one in its proc.
                        */

                        let response = if available_methods.contains(&method) {
                            crate::Response::NotImplemented()
                        } else {
                            crate::Response::BadRequest()
                        };

                        response
                            .with_headers(|h| h.access_control_allow_methods(available_methods_str))
                    }
                    None => {
                        /*
                        For security reasons, Ohkami doesn't support the
                        normal behavior to OPTIONS request like

                        ```
                        crate::Response::NoContent()
                        .with_headers(|h| h
                        .allow(available_methods_str)
                        )
                        ```
                        */
                        crate::Response::NotFound()
                    }
                };

                Box::pin(core::future::ready(response))
            },
            #[cfg(feature = "openapi")]
            openapi::Operation::with(openapi::Responses::new([
                /* NEVER generate spec of OPTIONS operations */
            ])),
        )
    }
}

#[cfg(feature = "openapi")]
impl Handler {
    pub fn map_openapi_operation(
        mut self,
        map: impl FnOnce(openapi::Operation) -> openapi::Operation,
    ) -> Self {
        self.openapi_operation = map(self.openapi_operation);
        self
    }
}