use axum::{
extract::{Path, State},
http::{header, StatusCode},
response::IntoResponse,
Json,
};
use serde_json::json;
use std::sync::Arc;
use crate::server::AppState;
pub async fn trigger_build(State(state): State<Arc<AppState>>) -> impl IntoResponse {
match state.wasm_builder.ensure_built().await {
Ok(_) => Json(json!({"status": "ready"})),
Err(msg) => Json(json!({"status": "error", "message": msg})),
}
}
pub async fn build_status(State(state): State<Arc<AppState>>) -> impl IntoResponse {
let status = state.wasm_builder.status().await;
Json(json!({"status": status}))
}
pub async fn serve_artifact(
State(state): State<Arc<AppState>>,
Path(file_path_str): Path<String>,
) -> impl IntoResponse {
let artifact_dir = match state.wasm_builder.artifact_dir().await {
Some(dir) => dir,
None => {
return (
StatusCode::NOT_FOUND,
"WASM build not ready. Trigger a build first via POST /api/wasm/build",
)
.into_response();
},
};
let file_path = artifact_dir.join(&file_path_str);
let canonical = match file_path.canonicalize() {
Ok(p) => p,
Err(_) => {
return (
StatusCode::NOT_FOUND,
format!("Artifact not found: {file_path_str}"),
)
.into_response();
},
};
let canonical_base = match artifact_dir.canonicalize() {
Ok(p) => p,
Err(_) => {
return (StatusCode::NOT_FOUND, "WASM build directory not found").into_response();
},
};
if !canonical.starts_with(&canonical_base) {
return (StatusCode::BAD_REQUEST, "Invalid path").into_response();
}
match tokio::fs::read(&canonical).await {
Ok(contents) => {
let content_type = mime_for_extension(&file_path_str);
([(header::CONTENT_TYPE, content_type)], contents).into_response()
},
Err(_) => (
StatusCode::NOT_FOUND,
format!("Artifact not found: {file_path_str}"),
)
.into_response(),
}
}
fn mime_for_extension(filename: &str) -> &'static str {
if filename.ends_with(".wasm") {
"application/wasm"
} else if filename.ends_with(".d.ts") {
"application/typescript"
} else if filename.ends_with(".js") {
"application/javascript"
} else {
"application/octet-stream"
}
}