1use axum::{
2 extract::Path,
3 http::StatusCode,
4 response::Json,
5 routing::{get, post},
6 Router,
7};
8use serde_json::{json, Value};
9use std::net::SocketAddr;
10use std::path::PathBuf;
11use tower_http::services::ServeDir;
12use tracing::info;
13
14#[derive(Clone, Debug)]
16pub struct ServerConfig {
17 pub addr: SocketAddr,
19 pub assets_path: PathBuf,
21}
22
23pub async fn start_server(config: ServerConfig) {
36 let app = Router::new()
37 .route("/api/:name", post(handle_api))
39 .nest_service("/", ServeDir::new(config.assets_path.clone()));
41
42 info!("Starting server at http://{}", config.addr);
43 info!(
44 "Serving static assets from: {}",
45 config.assets_path.display()
46 );
47
48 let listener = tokio::net::TcpListener::bind(config.addr).await.unwrap();
49 axum::serve(listener, app).await.unwrap();
50}
51
52async fn handle_api(Path(name): Path<String>) -> (StatusCode, Json<Value>) {
57 info!("Received API call for function: {}", name);
58 let response = json!({
59 "status": "success",
60 "message": format!("Called server function: '{}'", name),
61 });
62 (StatusCode::OK, Json(response))
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68 use std::time::Duration;
69 use tokio::time::timeout;
70
71 fn get_available_port() -> u16 {
73 std::net::TcpListener::bind("127.0.0.1:0")
74 .unwrap()
75 .local_addr()
76 .unwrap()
77 .port()
78 }
79
80 #[tokio::test]
81 async fn server_starts_and_responds_to_api_call() {
82 let port = get_available_port();
83 let addr = SocketAddr::from(([127, 0, 0, 1], port));
84
85 let temp_dir = tempfile::tempdir().unwrap();
87 let assets_path = temp_dir.path().to_path_buf();
88 std::fs::File::create(assets_path.join("index.html")).unwrap();
89
90 let config = ServerConfig { addr, assets_path };
91
92 let server_handle = tokio::spawn(start_server(config));
94
95 tokio::time::sleep(Duration::from_millis(100)).await;
97
98 let client = reqwest::Client::new();
100 let res = client
101 .post(format!("http://{}/api/test_function", addr))
102 .send()
103 .await
104 .expect("Failed to send request");
105
106 assert_eq!(res.status(), reqwest::StatusCode::OK);
107
108 let body: Value = res.json().await.expect("Failed to parse JSON response");
109 assert_eq!(
110 body,
111 json!({
112 "status": "success",
113 "message": "Called server function: 'test_function'",
114 })
115 );
116
117 let res_static = client
119 .get(format!("http://{}/index.html", addr))
120 .send()
121 .await
122 .expect("Failed to request static file");
123
124 assert_eq!(res_static.status(), reqwest::StatusCode::OK);
125
126 server_handle.abort();
128 let _ = timeout(Duration::from_secs(1), server_handle).await;
129 }
130}