#[cfg(not(debug_assertions))]
use std::collections::HashMap;
#[cfg(not(debug_assertions))]
use std::sync::OnceLock;
#[cfg(not(debug_assertions))]
#[derive(Debug, serde::Deserialize)]
struct AssetManifest {
files: HashMap<String, String>,
}
#[cfg(not(debug_assertions))]
static ASSET_MANIFEST: OnceLock<Option<AssetManifest>> = OnceLock::new();
#[cfg(not(debug_assertions))]
fn load_manifest() -> &'static Option<AssetManifest> {
ASSET_MANIFEST.get_or_init(|| {
let manifest_path =
crate::app::project_dir("static", &crate::config::OsEnv).join(".autumn-manifest.json");
let contents = std::fs::read_to_string(manifest_path).ok()?;
serde_json::from_str(&contents).ok()
})
}
#[must_use]
pub fn asset_url(path: &str) -> String {
#[cfg(debug_assertions)]
{
format!("/static/{path}")
}
#[cfg(not(debug_assertions))]
{
if let Some(manifest) = load_manifest() {
if let Some(fingerprinted) = manifest.files.get(path) {
return format!("/static/{fingerprinted}");
}
}
format!("/static/{path}")
}
}
#[cfg(not(debug_assertions))]
pub(crate) fn is_manifest_asset(rel_path: &str) -> bool {
load_manifest()
.as_ref()
.is_some_and(|m| m.files.values().any(|v| v == rel_path))
}
#[cfg(debug_assertions)]
pub(crate) const fn is_manifest_asset(_rel_path: &str) -> bool {
false
}
#[cfg(test)]
pub(crate) fn is_fingerprinted_path(uri_path: &str) -> bool {
let filename = uri_path.rsplit('/').next().unwrap_or("");
let parts: Vec<&str> = filename.split('.').collect();
if parts.len() < 3 {
return false;
}
let hash_candidate = parts[parts.len() - 2];
hash_candidate.len() == 8
&& hash_candidate
.bytes()
.all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'f'))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn asset_url_returns_static_prefix() {
let url = asset_url("css/autumn.css");
assert!(
url.starts_with("/static/"),
"url must have /static/ prefix: {url}"
);
assert!(
url.contains("autumn.css"),
"url must contain asset name: {url}"
);
}
#[test]
fn fingerprinted_path_detected() {
assert!(is_fingerprinted_path("/static/css/autumn.a1b2c3d4.css"));
assert!(is_fingerprinted_path("/static/js/app.00000000.js"));
assert!(is_fingerprinted_path("/static/img/logo.deadbeef.png"));
}
#[test]
fn non_fingerprinted_paths_rejected() {
assert!(!is_fingerprinted_path("/static/css/autumn.css"));
assert!(!is_fingerprinted_path("/static/js/htmx.min.js"));
assert!(!is_fingerprinted_path("/static/img/logo.png"));
assert!(!is_fingerprinted_path("/static/css/autumn.abc.css"));
assert!(!is_fingerprinted_path("/static/css/autumn.a1b2c3d4e5.css"));
assert!(!is_fingerprinted_path("/static/css/autumn.A1B2C3D4.css"));
assert!(!is_fingerprinted_path("/static/file.a1b2c3d4"));
}
}