fob_cli/dev/
asset_middleware.rs1use crate::dev::SharedState;
7use axum::{
8 body::Body,
9 extract::{Path, State},
10 http::{header, StatusCode},
11 response::Response,
12};
13use tracing;
14
15pub async fn handle_asset(
26 State(state): State<SharedState>,
27 Path(asset_path): Path<String>,
28) -> Result<Response, Response> {
29 let registry = state.asset_registry();
31
32 let url_path = format!("/__fob_assets__/{}", asset_path);
34
35 let asset = registry
37 .get_by_url(&url_path)
38 .ok_or_else(|| not_found(&asset_path))?;
39
40 let content = tokio::fs::read(&asset.source_path).await.map_err(|e| {
42 tracing::error!("Error reading asset {}: {}", asset.source_path.display(), e);
43 internal_error("Failed to read asset".to_string())
44 })?;
45
46 Ok(Response::builder()
48 .status(StatusCode::OK)
49 .header(header::CONTENT_TYPE, &asset.content_type)
50 .header(header::CONTENT_LENGTH, content.len())
51 .header(header::CACHE_CONTROL, "no-cache") .body(Body::from(content))
53 .unwrap())
54}
55
56fn not_found(path: &str) -> Response {
58 Response::builder()
59 .status(StatusCode::NOT_FOUND)
60 .header(header::CONTENT_TYPE, "text/plain")
61 .body(Body::from(format!("Asset not found: {}", path)))
62 .unwrap()
63}
64
65fn internal_error(message: String) -> Response {
67 Response::builder()
68 .status(StatusCode::INTERNAL_SERVER_ERROR)
69 .header(header::CONTENT_TYPE, "text/plain")
70 .body(Body::from(message))
71 .unwrap()
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77 use crate::dev::DevServerState;
78 use fob_bundler::builders::asset_registry::AssetRegistry;
79 use std::fs;
80 use std::sync::Arc;
81 use tempfile::TempDir;
82
83 #[tokio::test]
84 async fn test_serve_asset() {
85 let temp = TempDir::new().unwrap();
86 let asset_file = temp.path().join("test.wasm");
87 fs::write(&asset_file, b"test content").unwrap();
88
89 let registry = Arc::new(AssetRegistry::new());
91 registry.register(
92 asset_file.clone(),
93 "index.js".to_string(),
94 "./test.wasm".to_string(),
95 );
96
97 let url_path = "/__fob_assets__/test.wasm";
99 registry.set_url_path(&asset_file, url_path.to_string());
100
101 let state = DevServerState::new_with_registry(registry);
103 let shared_state = Arc::new(state);
104
105 let _response = handle_asset(State(shared_state), Path("test.wasm".to_string()))
107 .await
108 .unwrap();
109
110 }
113
114 #[tokio::test]
115 async fn test_asset_not_found() {
116 let registry = Arc::new(AssetRegistry::new());
117 let state = DevServerState::new_with_registry(registry);
118 let shared_state = Arc::new(state);
119
120 let response = handle_asset(State(shared_state), Path("nonexistent.wasm".to_string()))
121 .await
122 .unwrap_err();
123
124 assert_eq!(response.status(), StatusCode::NOT_FOUND);
125 }
126}