use axum::{
extract::Json,
http::StatusCode,
response::{IntoResponse, Response},
};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::sync::Arc;
pub type EndpointHandler = Arc<dyn Fn(Option<EndpointRequest>) -> EndpointResponse + Send + Sync>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EndpointRequest {
#[serde(default)]
pub query: HashMap<String, String>,
#[serde(default)]
pub params: HashMap<String, String>,
pub body: Option<Value>,
}
#[derive(Debug, Clone)]
pub struct EndpointResponse {
pub status: StatusCode,
pub body: Value,
}
impl EndpointResponse {
pub fn ok(body: Value) -> Self {
Self {
status: StatusCode::OK,
body,
}
}
pub fn created(body: Value) -> Self {
Self {
status: StatusCode::CREATED,
body,
}
}
pub fn accepted(body: Value) -> Self {
Self {
status: StatusCode::ACCEPTED,
body,
}
}
pub fn bad_request(message: &str) -> Self {
Self {
status: StatusCode::BAD_REQUEST,
body: serde_json::json!({"error": message}),
}
}
pub fn not_found(message: &str) -> Self {
Self {
status: StatusCode::NOT_FOUND,
body: serde_json::json!({"error": message}),
}
}
pub fn internal_error(message: &str) -> Self {
Self {
status: StatusCode::INTERNAL_SERVER_ERROR,
body: serde_json::json!({"error": message}),
}
}
pub fn with_status(status: StatusCode, body: Value) -> Self {
Self { status, body }
}
}
impl IntoResponse for EndpointResponse {
fn into_response(self) -> Response {
(self.status, Json(self.body)).into_response()
}
}
pub struct EndpointBuilder {
path: String,
method: HttpMethod,
handler: Option<EndpointHandler>,
description: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HttpMethod {
Get,
Post,
Put,
Delete,
Patch,
}
impl EndpointBuilder {
pub fn get(path: impl Into<String>) -> Self {
Self {
path: path.into(),
method: HttpMethod::Get,
handler: None,
description: None,
}
}
pub fn post(path: impl Into<String>) -> Self {
Self {
path: path.into(),
method: HttpMethod::Post,
handler: None,
description: None,
}
}
pub fn put(path: impl Into<String>) -> Self {
Self {
path: path.into(),
method: HttpMethod::Put,
handler: None,
description: None,
}
}
pub fn delete(path: impl Into<String>) -> Self {
Self {
path: path.into(),
method: HttpMethod::Delete,
handler: None,
description: None,
}
}
pub fn patch(path: impl Into<String>) -> Self {
Self {
path: path.into(),
method: HttpMethod::Patch,
handler: None,
description: None,
}
}
pub fn json(mut self, response: Value) -> Self {
self.handler = Some(Arc::new(move |_| EndpointResponse::ok(response.clone())));
self
}
pub fn handle<F>(mut self, handler: F) -> Self
where
F: Fn(Option<EndpointRequest>) -> EndpointResponse + Send + Sync + 'static,
{
self.handler = Some(Arc::new(handler));
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn build(self) -> CustomEndpoint {
CustomEndpoint {
path: self.path,
method: self.method,
handler: self
.handler
.expect("Handler must be set with .json() or .handle()"),
description: self.description,
}
}
}
#[derive(Clone)]
pub struct CustomEndpoint {
pub path: String,
pub method: HttpMethod,
pub handler: EndpointHandler,
pub description: Option<String>,
}
pub fn get(path: impl Into<String>, response: Value) -> CustomEndpoint {
EndpointBuilder::get(path).json(response).build()
}
pub fn post(path: impl Into<String>, response: Value) -> CustomEndpoint {
EndpointBuilder::post(path).json(response).build()
}
pub fn put(path: impl Into<String>, response: Value) -> CustomEndpoint {
EndpointBuilder::put(path).json(response).build()
}
pub fn delete(path: impl Into<String>, response: Value) -> CustomEndpoint {
EndpointBuilder::delete(path).json(response).build()
}
pub fn patch(path: impl Into<String>, response: Value) -> CustomEndpoint {
EndpointBuilder::patch(path).json(response).build()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_endpoint_builder_get() {
let endpoint = EndpointBuilder::get("/api/test")
.json(serde_json::json!({"test": "data"}))
.build();
assert_eq!(endpoint.path, "/api/test");
assert_eq!(endpoint.method, HttpMethod::Get);
}
#[test]
fn test_endpoint_response_helpers() {
let ok_response = EndpointResponse::ok(serde_json::json!({"status": "ok"}));
assert_eq!(ok_response.status, StatusCode::OK);
let created_response = EndpointResponse::created(serde_json::json!({"id": 1}));
assert_eq!(created_response.status, StatusCode::CREATED);
let not_found = EndpointResponse::not_found("Resource not found");
assert_eq!(not_found.status, StatusCode::NOT_FOUND);
}
#[test]
fn test_helper_functions() {
let endpoint = get("/api/version", serde_json::json!({"version": "1.0.0"}));
assert_eq!(endpoint.path, "/api/version");
assert_eq!(endpoint.method, HttpMethod::Get);
}
}