//! Embedded, pre-compressed web assets.
//!
//! `build.rs` places these in `OUT_DIR` — either by copying the pre-built files
//! checked into `assets/`, or, with `feature = "rebuild-assets"`, by running
//! the Tailwind/DaisyUI pipeline over `web/`. Either way the bytes land in
//! flash via `include_bytes!` and are served with `Content-Encoding: gzip`
//! (woff2 is already compressed, so it is served as-is).
//!
//! This module is host-buildable: it is only bytes and header tables.
/// A static response body plus the headers it should be served with.
#[derive(Clone, Copy)]
pub struct Asset {
pub bytes: &'static [u8],
pub headers: &'static [(&'static str, &'static str)],
}
pub const H_HTML_GZ: &[(&str, &str)] = &[
("Content-Type", "text/html; charset=utf-8"),
("Content-Encoding", "gzip"),
];
pub const H_CSS_GZ: &[(&str, &str)] = &[
("Content-Type", "text/css"),
("Content-Encoding", "gzip"),
("Cache-Control", "max-age=86400"),
];
pub const H_WOFF2: &[(&str, &str)] = &[
("Content-Type", "font/woff2"),
("Cache-Control", "max-age=604800"),
];
pub const H_JSON: &[(&str, &str)] = &[("Content-Type", "application/json")];
/// The provisioning form. With `feature = "mqtt"` this is the variant that
/// includes the broker section.
pub const PORTAL_HTML: Asset = Asset {
bytes: include_bytes!(concat!(env!("OUT_DIR"), "/portal.html.gz")),
headers: H_HTML_GZ,
};
/// The "saved, rebooting" confirmation page.
pub const SAVED_HTML: Asset = Asset {
bytes: include_bytes!(concat!(env!("OUT_DIR"), "/saved.html.gz")),
headers: H_HTML_GZ,
};
/// A generic status page. Consumers usually pass their own instead.
pub const STATUS_HTML: Asset = Asset {
bytes: include_bytes!(concat!(env!("OUT_DIR"), "/status.html.gz")),
headers: H_HTML_GZ,
};
pub const APP_CSS: Asset = Asset {
bytes: include_bytes!(concat!(env!("OUT_DIR"), "/app.css.gz")),
headers: H_CSS_GZ,
};
pub const FONT_400: Asset = Asset {
bytes: include_bytes!(concat!(env!("OUT_DIR"), "/font-400.woff2")),
headers: H_WOFF2,
};
pub const FONT_700: Asset = Asset {
bytes: include_bytes!(concat!(env!("OUT_DIR"), "/font-700.woff2")),
headers: H_WOFF2,
};
/// The style shell every page needs. Both the portal and the status server
/// register these, so the two share one look without duplicating bytes.
pub const SHELL: &[(&str, Asset)] = &[
("/app.css", APP_CSS),
("/font-400.woff2", FONT_400),
("/font-700.woff2", FONT_700),
];
#[cfg(test)]
mod tests {
use super::*;
/// Guards against build.rs silently emitting an empty or missing file.
#[test]
fn assets_are_non_empty() {
for (name, asset) in [
("portal.html.gz", PORTAL_HTML),
("saved.html.gz", SAVED_HTML),
("status.html.gz", STATUS_HTML),
("app.css.gz", APP_CSS),
("font-400.woff2", FONT_400),
("font-700.woff2", FONT_700),
] {
assert!(!asset.bytes.is_empty(), "{name} is empty");
}
}
#[test]
fn gzipped_assets_have_gzip_magic() {
for asset in [PORTAL_HTML, SAVED_HTML, STATUS_HTML, APP_CSS] {
assert_eq!(&asset.bytes[0..2], &[0x1f, 0x8b], "not gzip data");
}
}
#[test]
fn fonts_are_woff2() {
for asset in [FONT_400, FONT_700] {
assert_eq!(&asset.bytes[0..4], b"wOF2");
}
}
/// Pins the feature -> page mapping build.rs implements. Skipped under
/// `rebuild-assets`, where the bytes come from a fresh Tailwind run and
/// need not match the checked-in copies.
#[test]
#[cfg(not(feature = "rebuild-assets"))]
fn mqtt_feature_selects_the_matching_portal_page() {
#[cfg(feature = "mqtt")]
const EXPECTED: &[u8] = include_bytes!("../assets/portal-mqtt.html.gz");
#[cfg(not(feature = "mqtt"))]
const EXPECTED: &[u8] = include_bytes!("../assets/portal-wifi.html.gz");
assert_eq!(
PORTAL_HTML.bytes, EXPECTED,
"build.rs copied the wrong portal variant for this feature set"
);
}
#[test]
fn shell_paths_are_absolute() {
for (path, _) in SHELL {
assert!(path.starts_with('/'), "{path} must be an absolute URI");
}
}
}
neiam
/neiam-portal
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
4.2 KiB · text
History
b85db9e