use axum::extract::Request;
use axum::http::{HeaderValue, StatusCode, header};
use axum::response::{IntoResponse, Response};
use rust_embed::RustEmbed;
#[derive(RustEmbed)]
#[folder = "ui/dist"]
struct Assets;
pub async fn serve_asset(request: Request) -> Response {
let path = request.uri().path().trim_start_matches('/');
if let Some(response) = asset_response(path) {
return response;
}
if request.uri().path().starts_with("/api/") {
return (StatusCode::NOT_FOUND, "not found").into_response();
}
asset_response("index.html").unwrap_or_else(|| {
(
StatusCode::INTERNAL_SERVER_ERROR,
"admin UI bundle is missing from this build",
)
.into_response()
})
}
fn asset_response(path: &str) -> Option<Response> {
let path = if path.is_empty() { "index.html" } else { path };
let file = Assets::get(path)?;
let mime = content_type(path);
let mut response = file.data.into_owned().into_response();
response
.headers_mut()
.insert(header::CONTENT_TYPE, HeaderValue::from_static(mime));
response.headers_mut().insert(
header::CACHE_CONTROL,
HeaderValue::from_static("no-cache, must-revalidate"),
);
Some(response)
}
fn content_type(path: &str) -> &'static str {
match path.rsplit_once('.').map(|(_, ext)| ext) {
Some("html") => "text/html; charset=utf-8",
Some("js" | "mjs") => "text/javascript; charset=utf-8",
Some("css") => "text/css; charset=utf-8",
Some("json") => "application/json",
Some("svg") => "image/svg+xml",
Some("png") => "image/png",
Some("ico") => "image/x-icon",
Some("woff2") => "font/woff2",
_ => "application/octet-stream",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn index_is_embedded() {
assert!(Assets::get("index.html").is_some());
}
#[test]
fn content_types_are_mapped() {
assert_eq!(content_type("index.html"), "text/html; charset=utf-8");
assert_eq!(
content_type("assets/app.js"),
"text/javascript; charset=utf-8"
);
assert_eq!(content_type("assets/app.css"), "text/css; charset=utf-8");
assert_eq!(content_type("noext"), "application/octet-stream");
}
}