Files
sustenance/src/assets.rs
T

86 lines
2.8 KiB
Rust

use std::collections::HashMap;
use std::sync::LazyLock;
use sha2::{Digest, Sha256};
/// A single static asset: its embedded bytes and a content hash used to
/// version its URL.
pub struct StaticAsset {
pub data: &'static [u8],
pub hash: String,
}
/// A registry of the app's static assets, keyed by filename. Each asset's
/// content hash is computed once on first use and cached, so the versioned URL
/// changes automatically whenever the underlying file changes.
pub struct StaticAssetStore {
assets: HashMap<&'static str, StaticAsset>,
}
impl StaticAssetStore {
fn new(entries: &[(&'static str, &'static [u8])]) -> Self {
let assets = entries
.iter()
.map(|(name, data)| {
let hash = content_hash(data);
(*name, StaticAsset { data, hash })
})
.collect();
Self { assets }
}
/// Looks up an asset by its filename (e.g. `"style.css"`).
pub fn get(&self, name: &str) -> Option<&StaticAsset> {
self.assets.get(name)
}
/// Returns the versioned URL for an asset, e.g. `/static/style.css?v=<hash>`.
pub fn url(&self, name: &str) -> Option<String> {
self.assets
.get(name)
.map(|asset| format!("/static/{name}?v={}", asset.hash))
}
}
fn content_hash(data: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(data);
hex::encode(hasher.finalize())
}
/// Constructs a `StaticAssetStore` from `name => path` pairs, embedding each
/// file's bytes at compile time via `include_bytes!`.
macro_rules! static_assets {
($($name:literal => $path:literal),* $(,)?) => {
StaticAssetStore::new(&[
$(($name, include_bytes!($path))),*
])
};
}
/// The app's static assets, loaded once on first use.
pub static STORE: LazyLock<StaticAssetStore> = LazyLock::new(|| {
static_assets! {
"style.css" => "../static/style.css",
"passkey-login.js" => "../static/passkey-login.js",
"passkey-register.js" => "../static/passkey-register.js",
"password-toggle.js" => "../static/password-toggle.js",
"rewards.js" => "../static/rewards.js",
"htmx.min.js" => "../static/htmx.min.js",
"idiomorph-ext.min.js" => "../static/idiomorph-ext.min.js",
"htmx-ws.min.js" => "../static/htmx-ws.min.js",
"favicon.ico" => "../static/favicon.ico",
"favicon-32x32.png" => "../static/favicon-32x32.png",
"apple-touch-icon.png" => "../static/apple-touch-icon.png",
"logo.svg" => "../static/logo.svg",
}
});
/// Returns the versioned URL for a known asset, panicking if the name isn't
/// registered (a programmer error, since these are compile-time constants).
pub fn url(name: &str) -> String {
STORE
.url(name)
.unwrap_or_else(|| panic!("unknown static asset: {name}"))
}