use axum::{
extract::Request as AxumRequest,
http::StatusCode,
response::{IntoResponse, Response as AxumResponse},
routing::{
delete as axum_delete, get as axum_get, patch as axum_patch, post as axum_post,
put as axum_put,
},
Router as AxumRouter,
};
use std::collections::HashMap;
use std::net::SocketAddr;
use tokio::runtime::Runtime;
#[derive(Debug, Clone)]
pub struct Response {
pub status: u16,
pub body: Vec<u8>,
pub headers: HashMap<String, String>,
}
impl Response {
pub fn text(&self) -> Result<String, String> {
String::from_utf8(self.body.clone()).map_err(|e| e.to_string())
}
pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T, String> {
serde_json::from_slice(&self.body).map_err(|e| e.to_string())
}
pub fn bytes(&self) -> Vec<u8> {
self.body.clone()
}
pub fn status_code(&self) -> u16 {
self.status
}
pub fn is_success(&self) -> bool {
self.status >= 200 && self.status < 300
}
pub fn header(&self, key: &str) -> Option<String> {
self.headers.get(key).cloned()
}
}
pub fn get(url: &str) -> Result<Response, String> {
let rt = Runtime::new().map_err(|e| e.to_string())?;
rt.block_on(async {
let response = reqwest::get(url).await.map_err(|e| e.to_string())?;
let status = response.status().as_u16();
let headers = response
.headers()
.iter()
.map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string()))
.collect();
let body = response.bytes().await.map_err(|e| e.to_string())?.to_vec();
Ok(Response {
status,
body,
headers,
})
})
}
pub fn post_json<T: serde::Serialize>(url: &str, body: &T) -> Result<Response, String> {
let rt = Runtime::new().map_err(|e| e.to_string())?;
rt.block_on(async {
let client = reqwest::Client::new();
let response = client
.post(url)
.json(body)
.send()
.await
.map_err(|e| e.to_string())?;
let status = response.status().as_u16();
let headers = response
.headers()
.iter()
.map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string()))
.collect();
let body = response.bytes().await.map_err(|e| e.to_string())?.to_vec();
Ok(Response {
status,
body,
headers,
})
})
}
pub fn post(url: &str, body: &str) -> Result<Response, String> {
let rt = Runtime::new().map_err(|e| e.to_string())?;
rt.block_on(async {
let client = reqwest::Client::new();
let response = client
.post(url)
.body(body.to_string())
.send()
.await
.map_err(|e| e.to_string())?;
let status = response.status().as_u16();
let headers = response
.headers()
.iter()
.map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string()))
.collect();
let body = response.bytes().await.map_err(|e| e.to_string())?.to_vec();
Ok(Response {
status,
body,
headers,
})
})
}
#[derive(Debug, Clone)]
pub struct Request {
pub method: String,
pub path: String,
pub query: HashMap<String, String>,
pub headers: HashMap<String, String>,
pub body: Vec<u8>,
}
impl Request {
pub fn method(&self) -> &str {
&self.method
}
pub fn path(&self) -> &str {
&self.path
}
pub fn query_param(&self, key: &str) -> Option<String> {
self.query.get(key).cloned()
}
pub fn header(&self, key: &str) -> Option<String> {
self.headers.get(key).cloned()
}
pub fn body_string(&self) -> Result<String, String> {
String::from_utf8(self.body.clone()).map_err(|e| e.to_string())
}
pub fn body_json<T: serde::de::DeserializeOwned>(&self) -> Result<T, String> {
serde_json::from_slice(&self.body).map_err(|e| e.to_string())
}
}
#[derive(Debug, Clone)]
pub struct ServerResponse {
pub status: u16,
pub body: String,
pub headers: HashMap<String, String>,
}
impl ServerResponse {
pub fn ok(body: String) -> Self {
Self {
status: 200,
body,
headers: HashMap::new(),
}
}
pub fn json<T: serde::Serialize>(data: &T) -> Result<Self, String> {
let body = serde_json::to_string(data).map_err(|e| e.to_string())?;
let mut headers = HashMap::new();
headers.insert("Content-Type".to_string(), "application/json".to_string());
Ok(Self {
status: 200,
body,
headers,
})
}
pub fn created(body: String) -> Self {
Self {
status: 201,
body,
headers: HashMap::new(),
}
}
pub fn no_content() -> Self {
Self {
status: 204,
body: String::new(),
headers: HashMap::new(),
}
}
pub fn bad_request(body: String) -> Self {
Self {
status: 400,
body,
headers: HashMap::new(),
}
}
pub fn unauthorized(body: String) -> Self {
Self {
status: 401,
body,
headers: HashMap::new(),
}
}
pub fn forbidden(body: String) -> Self {
Self {
status: 403,
body,
headers: HashMap::new(),
}
}
pub fn not_found() -> Self {
Self {
status: 404,
body: "Not Found".to_string(),
headers: HashMap::new(),
}
}
pub fn internal_error(body: String) -> Self {
Self {
status: 500,
body,
headers: HashMap::new(),
}
}
pub fn with_status(status: u16, body: String) -> Self {
Self {
status,
body,
headers: HashMap::new(),
}
}
pub fn with_header(mut self, key: String, value: String) -> Self {
self.headers.insert(key, value);
self
}
}
impl IntoResponse for ServerResponse {
fn into_response(self) -> AxumResponse {
let mut response = (
StatusCode::from_u16(self.status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
self.body,
)
.into_response();
for (key, value) in self.headers {
if let Ok(header_name) = axum::http::HeaderName::from_bytes(key.as_bytes()) {
if let Ok(header_value) = axum::http::HeaderValue::from_str(&value) {
response.headers_mut().insert(header_name, header_value);
}
}
}
response
}
}
pub struct Router {
inner: AxumRouter,
}
impl Router {
pub fn new() -> Self {
Self {
inner: AxumRouter::new(),
}
}
pub fn get<F>(self, path: &str, handler: F) -> Self
where
F: Fn(Request) -> ServerResponse + Clone + Send + Sync + 'static,
{
let path = path.to_string();
Self {
inner: self.inner.route(
&path,
axum_get(move |req: AxumRequest| {
let handler = handler.clone();
async move {
let request = extract_request(req).await;
handler(request)
}
}),
),
}
}
pub fn post<F>(self, path: &str, handler: F) -> Self
where
F: Fn(Request) -> ServerResponse + Clone + Send + Sync + 'static,
{
let path = path.to_string();
Self {
inner: self.inner.route(
&path,
axum_post(move |req: AxumRequest| {
let handler = handler.clone();
async move {
let request = extract_request(req).await;
handler(request)
}
}),
),
}
}
pub fn put<F>(self, path: &str, handler: F) -> Self
where
F: Fn(Request) -> ServerResponse + Clone + Send + Sync + 'static,
{
let path = path.to_string();
Self {
inner: self.inner.route(
&path,
axum_put(move |req: AxumRequest| {
let handler = handler.clone();
async move {
let request = extract_request(req).await;
handler(request)
}
}),
),
}
}
pub fn delete<F>(self, path: &str, handler: F) -> Self
where
F: Fn(Request) -> ServerResponse + Clone + Send + Sync + 'static,
{
let path = path.to_string();
Self {
inner: self.inner.route(
&path,
axum_delete(move |req: AxumRequest| {
let handler = handler.clone();
async move {
let request = extract_request(req).await;
handler(request)
}
}),
),
}
}
pub fn patch<F>(self, path: &str, handler: F) -> Self
where
F: Fn(Request) -> ServerResponse + Clone + Send + Sync + 'static,
{
let path = path.to_string();
Self {
inner: self.inner.route(
&path,
axum_patch(move |req: AxumRequest| {
let handler = handler.clone();
async move {
let request = extract_request(req).await;
handler(request)
}
}),
),
}
}
}
impl Default for Router {
fn default() -> Self {
Self::new()
}
}
async fn extract_request(req: AxumRequest) -> Request {
let method = req.method().to_string();
let path = req.uri().path().to_string();
let query: HashMap<String, String> = req
.uri()
.query()
.map(|q| {
url::form_urlencoded::parse(q.as_bytes())
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect()
})
.unwrap_or_default();
let headers: HashMap<String, String> = req
.headers()
.iter()
.map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string()))
.collect();
let body = axum::body::to_bytes(req.into_body(), usize::MAX)
.await
.unwrap_or_default()
.to_vec();
Request {
method,
path,
query,
headers,
body,
}
}
pub fn serve(addr: &str, router: Router) -> Result<(), String> {
let rt = Runtime::new().map_err(|e| e.to_string())?;
rt.block_on(async {
let addr: SocketAddr = addr
.parse()
.map_err(|e: std::net::AddrParseError| e.to_string())?;
let listener = tokio::net::TcpListener::bind(addr)
.await
.map_err(|e| e.to_string())?;
axum::serve(listener, router.inner)
.await
.map_err(|e| e.to_string())
})
}
pub fn serve_fn<F>(addr: &str, handler: F) -> Result<(), String>
where
F: Fn(&Request) -> ServerResponse + Clone + Send + Sync + 'static,
{
let router = Router::new().get("/*path", move |req: Request| handler(&req));
serve(addr, router)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_response_constructors() {
let resp = ServerResponse::ok("Hello".to_string());
assert_eq!(resp.status, 200);
assert_eq!(resp.body, "Hello");
let resp = ServerResponse::not_found();
assert_eq!(resp.status, 404);
let resp = ServerResponse::with_status(418, "I'm a teapot".to_string());
assert_eq!(resp.status, 418);
}
#[test]
fn test_response_with_header() {
let resp = ServerResponse::ok("test".to_string())
.with_header("X-Custom".to_string(), "value".to_string());
assert_eq!(resp.headers.get("X-Custom"), Some(&"value".to_string()));
}
}