use std::{collections::HashMap, time::Duration}; use apisdk::{ApiError, ResponseBody}; use futures::StreamExt; use serde::{de::DeserializeOwned, Deserialize}; use serde_json::json; use tokio::sync::OnceCell; use warp::{ filters::{multipart::FormData, path::FullPath}, http::HeaderMap, reply::Reply, Filter, }; pub const PORT: u16 = 3030; #[derive(Debug, Deserialize)] pub struct Payload> { pub path: String, pub headers: H, #[serde(default)] pub query: HashMap, #[serde(default)] pub form: HashMap, } impl TryFrom for Payload where H: DeserializeOwned, { type Error = ApiError; fn try_from(body: ResponseBody) -> Result { body.parse_json() } } static ONCE: OnceCell<()> = OnceCell::const_new(); pub async fn start_server() { ONCE.get_or_init(do_start_server).await; } async fn do_start_server() { tokio::spawn(async move { let dump_json = warp::path!("v1" / "path" / "json") .and(warp::path::full()) .and(warp::header::headers_cloned()) .and(warp::query()) .and_then(handle_json); let dump_xml = warp::path!("v1" / "path" / "xml") .and(warp::path::full()) .and(warp::header::headers_cloned()) .and(warp::query()) .and_then(handle_xml); let dump_text = warp::path!("v1" / "path" / "text") .and(warp::path::full()) .and(warp::header::headers_cloned()) .and(warp::query()) .and_then(handle_text); let dump_form = warp::post() .and(warp::path!("v1" / "path" / "form")) .and(warp::path::full()) .and(warp::header::headers_cloned()) .and(warp::query()) .and(warp::body::form()) .and_then(handle_form); let dump_multipart = warp::post() .and(warp::path!("v1" / "path" / "multipart")) .and(warp::path::full()) .and(warp::header::headers_cloned()) .and(warp::query()) .and(warp::multipart::form()) .and_then(handle_multipart); let not_found = warp::path!("v1" / "not-found").and_then(handle_not_found); warp::serve( dump_json .or(dump_xml) .or(dump_text) .or(dump_form) .or(dump_multipart) .or(not_found), ) .run(([127, 0, 0, 1], PORT)) .await; }); // Ensure the server is ready to work tokio::time::sleep(Duration::from_millis(200)).await; } async fn handle_json( path: FullPath, headers: HeaderMap, query: HashMap, ) -> Result { let mut headers_map = HashMap::new(); for (name, value) in headers { if let Some(name) = name { if let Ok(value) = value.to_str() { headers_map.insert(name.as_str().to_string(), value.to_string()); } } } let resp = json!({ "code": 0, "message": "OK", "data": { "path": path.as_str(), "headers": headers_map, "query": query, }, "extra-field": "extra" }); Ok(warp::reply::json(&resp)) } async fn handle_xml( path: FullPath, headers: HeaderMap, query: HashMap, ) -> Result { warp::http::Response::builder() .header("Content-Type", "text/xml") .body( r#" 0 world "# .trim(), ) .map_err(|_| warp::reject()) } async fn handle_text( path: FullPath, headers: HeaderMap, query: HashMap, ) -> Result { warp::http::Response::builder() .header("Content-Type", "text/plain") .body("text goes here") .map_err(|_| warp::reject()) } async fn handle_form( path: FullPath, headers: HeaderMap, query: HashMap, form: HashMap, ) -> Result { let mut headers_map = HashMap::new(); for (name, value) in headers { if let Some(name) = name { if let Ok(value) = value.to_str() { headers_map.insert(name.as_str().to_string(), value.to_string()); } } } let resp = json!({ "code": 0, "message": "OK", "data": { "path": path.as_str(), "headers": headers_map, "query": query, "form": form, }, "extra-field": "extra" }); Ok(warp::reply::json(&resp)) } async fn handle_multipart( path: FullPath, headers: HeaderMap, query: HashMap, mut multipart: FormData, ) -> Result { let mut headers_map = HashMap::new(); for (name, value) in headers { if let Some(name) = name { if let Ok(value) = value.to_str() { headers_map.insert(name.as_str().to_string(), value.to_string()); } } } let mut parts = HashMap::new(); while let Some(Ok(part)) = multipart.next().await { parts.insert( part.name().to_string(), part.content_type() .map(|v| v.to_string()) .unwrap_or_default(), ); } let resp = json!({ "code": 0, "message": "OK", "data": { "path": path.as_str(), "headers": headers_map, "query": query, "multipart": parts, }, "extra-field": "extra" }); Ok(warp::reply::json(&resp)) } async fn handle_not_found() -> Result { Err(warp::reject::not_found()) } #[tokio::test] #[ignore] async fn standalone_server() { start_server().await; tokio::time::sleep(Duration::from_secs(60 * 5)).await }