use axum::{
http::{StatusCode, Uri, header},
response::{IntoResponse, Response},
};
use rust_embed::Embed;
#[derive(Embed)]
#[folder = "frontend/dist"]
#[allow_missing = true]
struct Assets;
pub async fn serve(uri: Uri) -> Response {
let path = uri.path().trim_start_matches('/');
if path.is_empty() {
return index_html();
}
match Assets::get(path) {
Some(file) => {
let mime = file.metadata.mimetype().to_string();
(
StatusCode::OK,
[
(header::CONTENT_TYPE, mime),
(header::CACHE_CONTROL, cache_control(path).to_string()),
],
file.data,
)
.into_response()
}
None => index_html(),
}
}
fn index_html() -> Response {
match Assets::get("index.html") {
Some(file) => (
StatusCode::OK,
[
(header::CONTENT_TYPE, "text/html; charset=utf-8".to_string()),
(header::CACHE_CONTROL, "no-cache".to_string()),
],
file.data,
)
.into_response(),
None => (
StatusCode::NOT_FOUND,
[(header::CONTENT_TYPE, "text/plain; charset=utf-8")],
"no web UI was built into this binary; the API is at /api/v1",
)
.into_response(),
}
}
fn cache_control(path: &str) -> &'static str {
if path.starts_with("assets/") {
"public, max-age=31536000, immutable"
} else {
"public, max-age=3600"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fingerprinted_bundles_are_cached_forever_and_others_are_not() {
assert_eq!(cache_control("assets/index-X9yN6mCH.js"), "public, max-age=31536000, immutable");
assert_eq!(cache_control("favicon.svg"), "public, max-age=3600");
assert_eq!(cache_control("index.html"), "public, max-age=3600");
}
fn frontend_was_embedded() -> bool {
Assets::get("index.html").is_some()
}
#[tokio::test]
async fn a_client_side_route_is_answered_with_the_document() {
let response = serve("/privacy".parse().unwrap()).await;
let content_type = response
.headers()
.get(header::CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.unwrap_or_default()
.to_string();
if frontend_was_embedded() {
assert_eq!(response.status(), StatusCode::OK, "a built app must answer its own routes");
assert_eq!(content_type, "text/html; charset=utf-8");
} else {
assert_eq!(response.status(), StatusCode::NOT_FOUND);
assert_eq!(content_type, "text/plain; charset=utf-8");
}
}
#[tokio::test]
async fn the_root_is_the_document_too() {
let response = serve("/".parse().unwrap()).await;
let expected = if frontend_was_embedded() { StatusCode::OK } else { StatusCode::NOT_FOUND };
assert_eq!(response.status(), expected);
}
#[tokio::test]
async fn an_embedded_asset_is_served_with_its_own_type() {
let Some(name) = Assets::iter().find(|name| name.ends_with(".js")) else {
eprintln!("skipped: no frontend build is embedded in this binary");
return;
};
let response = serve(format!("/{name}").parse().unwrap()).await;
assert_eq!(response.status(), StatusCode::OK);
let content_type = response.headers().get(header::CONTENT_TYPE).unwrap().to_str().unwrap();
assert!(content_type.contains("javascript"), "a bundle served as `{content_type}` will not execute");
let cache = response.headers().get(header::CACHE_CONTROL).unwrap().to_str().unwrap();
assert!(cache.contains("immutable"), "a fingerprinted bundle should be cached hard, got `{cache}`");
}
}