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> {
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(),
}
}
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
}
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
}
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;
}
}