modi 0.1.1

An out-of-the-box modular dependency injection web application framework.
Documentation
use ::core::time::Duration;
use ::std::{env, path::PathBuf};
use ::tokio::signal;
use std::cell::Cell;

use crate::dyn_mod::{DmRouter, Module, ModuleBuilder};
use crate::mw::*;
use crate::otel;
use crate::prelude::*;

#[macro_export]
macro_rules! new_app {
    ($($module:ty $(, $module_more:ty)*)*) => {
        {
            $crate::dyn_mod::dyn_modules![$($module $(, $module_more)*)*];
            App::<DynModules>::new(
                env!("CARGO_PKG_NAME"),
                env!("CARGO_PKG_VERSION"),
                DynModules::builder(),
            )
        }
    };
}

pub const MANIFEST_DIR: &'static str = "output/manifest";
pub const ENV_MODI_DEBUG: &'static str = "MODI_DEBUG";
pub const ENV_MODI_SERVICE_IP: &'static str = "MODI_SERVICE_IP";
pub const ENV_MODI_SERVICE_PORT: &'static str = "MODI_SERVICE_PORT";
pub const ENV_MODI_WORKER_ID: &'static str = "MODI_WORKER_ID";
pub const ENV_MODI_SHUTDOWN_SECS: &'static str = "MODI_SHUTDOWN_SECS";

pub struct App<M: Module> {
    name: &'static str,
    version: &'static str,
    module_builder: Cell<Option<ModuleBuilder<M>>>,
    more_routers: Vec<Router>,
}

impl<M: Module> App<M> {
    /// It is recommended to use the `new_app![ModuleType,...]` macro to create an App.
    /// This method is mainly provided for use by this macro.
    pub fn new(
        name: &'static str,
        version: &'static str,
        module_builder: ModuleBuilder<M>,
    ) -> Self {
        Self {
            name,
            version,
            module_builder: Cell::new(Some(module_builder)),
            more_routers: ::std::vec::Vec::new(),
        }
    }

    /// Set the parameters of the specified component. If the parameters are not
    /// manually set, the defaults will be used.
    pub fn with_component_parameters<C: Component<M>>(self, params: C::Parameters) -> Self
    where
        M: HasComponent<C::Interface>,
    {
        self.set_module_builder(
            self.take_module_builder()
                .with_component_parameters::<C>(params),
        );
        self
    }

    /// Override a component implementation. This method is best used when the
    /// overriding component has no injected dependencies.
    pub fn with_component_override<I: Interface + ?Sized>(self, component: Box<I>) -> Self
    where
        M: HasComponent<I>,
    {
        self.set_module_builder(
            self.take_module_builder()
                .with_component_override::<I>(component),
        );
        self
    }

    /// Override a component implementation. This method is best used when the
    /// overriding component has injected dependencies.
    pub fn with_component_override_fn<I: Interface + ?Sized>(
        self,
        component_fn: ComponentFn<M, I>,
    ) -> Self
    where
        M: HasComponent<I>,
    {
        self.set_module_builder(
            self.take_module_builder()
                .with_component_override_fn::<I>(component_fn),
        );
        self
    }

    fn take_module_builder(&self) -> ModuleBuilder<M> {
        self.module_builder.take().unwrap()
    }

    fn set_module_builder(&self, module_builder: ModuleBuilder<M>) {
        self.module_builder.set(Some(module_builder))
    }

    pub fn push_more_router(mut self, router: Router) -> Self {
        self.more_routers.push(router);
        self
    }

    pub async fn run(mut self) {
        otel::init(self.name.to_owned()).await;

        let worker_id: u16 = env::var(ENV_MODI_WORKER_ID)
            .map_or(None, |v| v.parse().ok())
            .unwrap_or(0);

        let dm_router = DmRouter::new()
            .hoop(TraceHoop::new())
            .hoop(Metrics::new(self.name.to_owned()))
            .hoop(RequestIdHoop::new(worker_id));

        let _ = self
            .take_module_builder()
            .with_router(dm_router.clone())
            .with_manifest_dir(PathBuf::from(MANIFEST_DIR))
            .build();

        let mut router = dm_router.append(&mut self.more_routers).router();

        if env::var(ENV_MODI_DEBUG).map_or(false, |v| v == "1" || v.parse().unwrap_or_default()) {
            let doc = OpenApi::new(format!("{} API", self.name.to_uppercase()), self.version)
                .merge_router(&router);
            router = router
                .push(doc.into_router("/api-doc/openapi.json"))
                .push(SwaggerUi::new("/api-doc/openapi.json").into_router("swagger-ui"));
        }

        let ip = env::var(ENV_MODI_SERVICE_IP).unwrap_or("0.0.0.0".to_owned());
        let port = env::var(ENV_MODI_SERVICE_PORT).unwrap_or("8888".to_owned());
        let addr = format!("{ip}:{port}");
        let acceptor = TcpListener::new(addr).bind().await;
        let server = Server::new(acceptor);
        let handle = server.handle();
        tokio::spawn(async move {
            let mut sigint_stream =
                signal::unix::signal(signal::unix::SignalKind::interrupt()).unwrap();
            let mut sigterm_stream =
                signal::unix::signal(signal::unix::SignalKind::terminate()).unwrap();
            tokio::select! {
                _ = sigint_stream.recv() => {
                    println!("Received SIGINT");
                },
                _ = sigterm_stream.recv() => {
                    println!("Received SIGTERM");
                },
            }
            let shutdown_secs = env::var(ENV_MODI_SHUTDOWN_SECS)
                .map_or(None, |v| v.parse().ok().map(|d| Duration::from_secs(d)));
            handle.stop_graceful(shutdown_secs);
            otel::shutdown_all_providers();
        });
        server.serve(router).await;
    }
}