use crate::serve::error::ServeError;
use axum::extract::Path;
use axum::http::{HeaderMap, Method, StatusCode, header};
use axum::response::{IntoResponse, Response};
#[derive(rust_embed::RustEmbed)]
#[folder = "src/serve/ui/"]
struct UiAssets;
fn serve_asset(path: &str) -> Response {
match UiAssets::get(path) {
Some(file) => {
let mime = file.metadata.mimetype();
(
StatusCode::OK,
[
(header::CONTENT_TYPE, mime.to_string()),
(header::CACHE_CONTROL, "no-cache".to_string()),
],
file.data.into_owned(),
)
.into_response()
}
None => ServeError::NotFound.into_response(),
}
}
pub async fn index() -> Response {
serve_asset("index.html")
}
pub async fn asset(Path(path): Path<String>) -> Response {
serve_asset(&path)
}
pub(crate) fn wants_html(headers: &HeaderMap) -> bool {
headers
.get(header::ACCEPT)
.and_then(|v| v.to_str().ok())
.map(|a| a.contains("text/html"))
.unwrap_or(false)
}
pub async fn spa_fallback(method: Method, headers: HeaderMap) -> Response {
if method == Method::GET && wants_html(&headers) {
index().await
} else {
ServeError::NotFound.into_response()
}
}
#[cfg(test)]
mod tests {
use super::*;
use axum::http::HeaderValue;
#[test]
fn wants_html_true_for_browser_accept() {
let mut h = HeaderMap::new();
h.insert(
header::ACCEPT,
HeaderValue::from_static("text/html,application/xhtml+xml"),
);
assert!(wants_html(&h));
}
#[test]
fn wants_html_false_for_json_or_missing() {
let mut h = HeaderMap::new();
h.insert(header::ACCEPT, HeaderValue::from_static("application/json"));
assert!(!wants_html(&h));
assert!(!wants_html(&HeaderMap::new()));
}
#[test]
fn index_asset_is_embedded_and_html() {
let resp = serve_asset("index.html");
assert_eq!(resp.status(), StatusCode::OK);
let ct = resp.headers().get(header::CONTENT_TYPE).unwrap();
assert!(ct.to_str().unwrap().contains("text/html"));
}
#[test]
fn missing_asset_is_not_found() {
let resp = serve_asset("does-not-exist.xyz");
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
}
}