use std::sync::Arc;
use crate::environment::Environment;
use super::shutdown::Shutdown;
#[cfg(feature = "templates")]
use {
super::error::WebServerError,
crate::templates::{BaseTemplateData, PageTemplateData, TemplateData, TemplateRegistry},
axum::response::Html,
serde::Serialize,
std::collections::BTreeMap,
std::sync::LazyLock,
};
#[cfg(feature = "templates")]
static NO_CACHE_BUSTER: LazyLock<BTreeMap<String, String>> = LazyLock::new(BTreeMap::new);
#[derive(Debug)]
pub struct WebServerState<S = ()>(Arc<Inner<S>>);
impl<S> Clone for WebServerState<S> {
fn clone(&self) -> Self {
Self(Arc::clone(&self.0))
}
}
#[derive(Debug)]
struct Inner<S> {
host: String,
port: u16,
environment: Environment,
shutdown: Shutdown,
#[cfg(feature = "templates")]
base: Option<BaseTemplateData>,
#[cfg(feature = "templates")]
templates: Option<TemplateRegistry<'static>>,
#[cfg(feature = "webserver")]
cache_buster: Option<crate::assets::CacheBuster>,
#[cfg(feature = "templates")]
frontend: Option<crate::templates::FrontendRuntime>,
app: S,
}
pub(super) struct StateParts<S> {
pub(super) host: String,
pub(super) port: u16,
pub(super) environment: Environment,
pub(super) shutdown: Shutdown,
#[cfg(feature = "templates")]
pub(super) base: Option<BaseTemplateData>,
#[cfg(feature = "templates")]
pub(super) templates: Option<TemplateRegistry<'static>>,
#[cfg(feature = "webserver")]
pub(super) cache_buster: Option<crate::assets::CacheBuster>,
#[cfg(feature = "templates")]
pub(super) frontend: Option<crate::templates::FrontendRuntime>,
pub(super) app: S,
}
impl<S> WebServerState<S> {
pub(super) fn new(parts: StateParts<S>) -> Self {
Self(Arc::new(Inner {
host: parts.host,
port: parts.port,
environment: parts.environment,
shutdown: parts.shutdown,
#[cfg(feature = "templates")]
base: parts.base,
#[cfg(feature = "templates")]
templates: parts.templates,
#[cfg(feature = "webserver")]
cache_buster: parts.cache_buster,
#[cfg(feature = "templates")]
frontend: parts.frontend,
app: parts.app,
}))
}
#[must_use]
pub fn host(&self) -> &str {
&self.0.host
}
#[must_use]
pub fn port(&self) -> u16 {
self.0.port
}
#[must_use]
pub fn environment(&self) -> Environment {
self.0.environment
}
#[must_use]
pub fn shutdown(&self) -> &Shutdown {
&self.0.shutdown
}
#[must_use]
pub fn app(&self) -> &S {
&self.0.app
}
#[cfg(feature = "templates")]
#[must_use]
pub fn base_template_data(&self) -> Option<&BaseTemplateData> {
self.0.base.as_ref()
}
#[cfg(feature = "templates")]
#[must_use]
pub fn templates(&self) -> Option<&TemplateRegistry<'static>> {
self.0.templates.as_ref()
}
#[cfg(feature = "webserver")]
#[must_use]
pub fn cache_buster(&self) -> Option<&crate::assets::CacheBuster> {
self.0.cache_buster.as_ref()
}
#[cfg(feature = "webserver")]
#[must_use]
pub fn asset(&self, original_asset_file_path: &str) -> String {
self.0.cache_buster.as_ref().map_or_else(
|| original_asset_file_path.to_string(),
|cache_buster| cache_buster.get_file(original_asset_file_path),
)
}
#[cfg(feature = "templates")]
#[must_use]
pub fn frontend_runtime(&self) -> Option<&crate::templates::FrontendRuntime> {
self.0.frontend.as_ref()
}
#[cfg(feature = "templates")]
pub fn render<A>(
&self,
page: &PageTemplateData,
data: A,
) -> Result<Html<String>, WebServerError>
where
A: Serialize,
{
let (Some(base), Some(registry)) = (self.0.base.as_ref(), self.0.templates.as_ref()) else {
return Err(WebServerError::TemplatesNotConfigured);
};
let template_data: TemplateData<'_, A> = TemplateData::assemble(
base,
page,
self.0.frontend.as_ref(),
self.0.environment,
self.cache_buster_map(),
data,
)?;
Ok(Html(registry.render(page.template(), &template_data)?))
}
#[cfg(feature = "templates")]
fn cache_buster_map(&self) -> &BTreeMap<String, String> {
#[cfg(feature = "webserver")]
{
self.0
.cache_buster
.as_ref()
.map_or(&NO_CACHE_BUSTER, crate::assets::CacheBuster::cache)
}
#[cfg(not(feature = "webserver"))]
{
&NO_CACHE_BUSTER
}
}
}