use std::convert::Infallible;
use std::mem;
use std::net::SocketAddr;
use std::sync::OnceLock;
use std::sync::PoisonError;
use std::sync::RwLock;
use galvyn_core::registry::builder::RegistryBuilder;
use galvyn_core::router::GalvynRoute;
use galvyn_core::GalvynRouter;
use tokio::net::TcpListener;
use tokio::sync::oneshot;
use tracing::debug;
use tracing::info;
use tracing::Level;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter;
use crate::core::Module;
use crate::error::GalvynError;
use crate::panic_hook::set_panic_hook;
#[non_exhaustive]
pub struct Galvyn {
routes: Vec<GalvynRoute>,
shutdown: RwLock<Option<oneshot::Sender<Infallible>>>,
}
#[derive(Default)]
#[cfg_attr(doc, non_exhaustive)]
pub struct GalvynSetup {
#[cfg(feature = "sessions")]
pub disable_sessions: bool,
pub disable_panic_hook: bool,
#[doc(hidden)]
pub _non_exhaustive: (),
}
impl Galvyn {
pub fn builder(setup: GalvynSetup) -> ModuleBuilder {
ModuleBuilder::new(setup)
}
pub fn global() -> &'static Self {
Self::try_global().unwrap_or_else(|| panic!("Galvyn has not been started yet."))
}
pub fn try_global() -> Option<&'static Self> {
INSTANCE.get()
}
#[doc(hidden)]
pub fn get_routes(&self) -> &[GalvynRoute] {
&self.routes
}
pub fn shutdown(&self) {
let mut shutdown_tx = self
.shutdown
.write()
.unwrap_or_else(PoisonError::into_inner);
shutdown_tx.take();
}
}
#[derive(Default)]
pub struct ModuleBuilder {
modules: RegistryBuilder,
setup: GalvynSetup,
}
impl ModuleBuilder {
fn new(setup: GalvynSetup) -> ModuleBuilder {
if !setup.disable_panic_hook {
set_panic_hook();
}
let registry = tracing_subscriber::registry()
.with(EnvFilter::try_from_default_env().unwrap_or(EnvFilter::new(Level::INFO.as_str())))
.with(tracing_subscriber::fmt::layer());
if registry.try_init().is_ok() {
debug!("Initialized galvyn's subscriber");
} else {
debug!("Using external subscriber");
}
ModuleBuilder {
modules: Default::default(),
setup,
}
}
pub fn register_module<T: Module>(&mut self, setup: T::Setup) -> &mut Self {
self.modules.register_module::<T>(setup);
self
}
pub async fn init_modules(&mut self) -> Result<RouterBuilder, GalvynError> {
self.modules.init().await?;
Ok(RouterBuilder {
routes: GalvynRouter::new(),
setup: mem::take(&mut self.setup),
})
}
}
pub struct RouterBuilder {
routes: GalvynRouter,
setup: GalvynSetup,
}
impl RouterBuilder {
pub fn add_routes(&mut self, router: GalvynRouter) -> &mut Self {
let this = mem::take(&mut self.routes);
self.routes = this.merge(router);
self
}
pub async fn start(&mut self, socket_addr: SocketAddr) -> Result<(), GalvynError> {
#[allow(unused_mut, reason = "Usage is behind feature flags")]
let (mut router, routes) = mem::take(&mut self.routes).finish();
#[cfg(feature = "sessions")]
if !self.setup.disable_sessions {
router = router.layer(galvyn_core::session::layer());
}
let (shutdown_tx, shutdown_rx) = oneshot::channel();
INSTANCE.set(Galvyn {
routes,
shutdown: RwLock::new(Some(shutdown_tx)),
})
.unwrap_or_else(|_| panic!("Galvyn has already been started. There can't be more than one instance per process."));
let socket = TcpListener::bind(socket_addr).await?;
info!("Starting to serve webserver on http://{socket_addr}");
let serve_future = axum::serve(socket, router);
#[cfg(feature = "graceful-shutdown")]
let signal = {
debug!("Registering signals for graceful shutdown");
crate::graceful_shutdown::wait_for_signal()?
};
#[cfg(not(feature = "graceful-shutdown"))]
let signal = std::future::pending::<()>();
serve_future
.with_graceful_shutdown(async move {
tokio::select! {
_ = signal => (),
_ = shutdown_rx => (),
}
})
.await?;
Ok(())
}
}
static INSTANCE: OnceLock<Galvyn> = OnceLock::new();