1use axum::{
7 extract::Json,
8 http::StatusCode,
9 response::{IntoResponse, Response},
10};
11use serde::{Deserialize, Serialize};
12use serde_json::Value;
13use std::collections::HashMap;
14use std::sync::Arc;
15
16pub type EndpointHandler = Arc<dyn Fn(Option<EndpointRequest>) -> EndpointResponse + Send + Sync>;
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct EndpointRequest {
23 #[serde(default)]
25 pub query: HashMap<String, String>,
26
27 #[serde(default)]
29 pub params: HashMap<String, String>,
30
31 pub body: Option<Value>,
33}
34
35#[derive(Debug, Clone)]
37pub struct EndpointResponse {
38 pub status: StatusCode,
40
41 pub body: Value,
43}
44
45impl EndpointResponse {
46 pub fn ok(body: Value) -> Self {
48 Self {
49 status: StatusCode::OK,
50 body,
51 }
52 }
53
54 pub fn created(body: Value) -> Self {
56 Self {
57 status: StatusCode::CREATED,
58 body,
59 }
60 }
61
62 pub fn accepted(body: Value) -> Self {
64 Self {
65 status: StatusCode::ACCEPTED,
66 body,
67 }
68 }
69
70 pub fn bad_request(message: &str) -> Self {
72 Self {
73 status: StatusCode::BAD_REQUEST,
74 body: serde_json::json!({"error": message}),
75 }
76 }
77
78 pub fn not_found(message: &str) -> Self {
80 Self {
81 status: StatusCode::NOT_FOUND,
82 body: serde_json::json!({"error": message}),
83 }
84 }
85
86 pub fn internal_error(message: &str) -> Self {
88 Self {
89 status: StatusCode::INTERNAL_SERVER_ERROR,
90 body: serde_json::json!({"error": message}),
91 }
92 }
93
94 pub fn with_status(status: StatusCode, body: Value) -> Self {
96 Self { status, body }
97 }
98}
99
100impl IntoResponse for EndpointResponse {
101 fn into_response(self) -> Response {
102 (self.status, Json(self.body)).into_response()
103 }
104}
105
106pub struct EndpointBuilder {
108 path: String,
109 method: HttpMethod,
110 handler: Option<EndpointHandler>,
111 description: Option<String>,
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub enum HttpMethod {
117 Get,
118 Post,
119 Put,
120 Delete,
121 Patch,
122}
123
124impl EndpointBuilder {
125 pub fn get(path: impl Into<String>) -> Self {
127 Self {
128 path: path.into(),
129 method: HttpMethod::Get,
130 handler: None,
131 description: None,
132 }
133 }
134
135 pub fn post(path: impl Into<String>) -> Self {
137 Self {
138 path: path.into(),
139 method: HttpMethod::Post,
140 handler: None,
141 description: None,
142 }
143 }
144
145 pub fn put(path: impl Into<String>) -> Self {
147 Self {
148 path: path.into(),
149 method: HttpMethod::Put,
150 handler: None,
151 description: None,
152 }
153 }
154
155 pub fn delete(path: impl Into<String>) -> Self {
157 Self {
158 path: path.into(),
159 method: HttpMethod::Delete,
160 handler: None,
161 description: None,
162 }
163 }
164
165 pub fn patch(path: impl Into<String>) -> Self {
167 Self {
168 path: path.into(),
169 method: HttpMethod::Patch,
170 handler: None,
171 description: None,
172 }
173 }
174
175 pub fn json(mut self, response: Value) -> Self {
178 self.handler = Some(Arc::new(move |_| EndpointResponse::ok(response.clone())));
179 self
180 }
181
182 pub fn handle<F>(mut self, handler: F) -> Self
185 where
186 F: Fn(Option<EndpointRequest>) -> EndpointResponse + Send + Sync + 'static,
187 {
188 self.handler = Some(Arc::new(handler));
189 self
190 }
191
192 pub fn description(mut self, description: impl Into<String>) -> Self {
194 self.description = Some(description.into());
195 self
196 }
197
198 pub fn build(self) -> CustomEndpoint {
200 CustomEndpoint {
201 path: self.path,
202 method: self.method,
203 handler: self
204 .handler
205 .expect("Handler must be set with .json() or .handle()"),
206 description: self.description,
207 }
208 }
209}
210
211#[derive(Clone)]
213pub struct CustomEndpoint {
214 pub path: String,
215 pub method: HttpMethod,
216 pub handler: EndpointHandler,
217 pub description: Option<String>,
218}
219
220pub fn get(path: impl Into<String>, response: Value) -> CustomEndpoint {
231 EndpointBuilder::get(path).json(response).build()
232}
233
234pub fn post(path: impl Into<String>, response: Value) -> CustomEndpoint {
236 EndpointBuilder::post(path).json(response).build()
237}
238
239pub fn put(path: impl Into<String>, response: Value) -> CustomEndpoint {
241 EndpointBuilder::put(path).json(response).build()
242}
243
244pub fn delete(path: impl Into<String>, response: Value) -> CustomEndpoint {
246 EndpointBuilder::delete(path).json(response).build()
247}
248
249pub fn patch(path: impl Into<String>, response: Value) -> CustomEndpoint {
251 EndpointBuilder::patch(path).json(response).build()
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn test_endpoint_builder_get() {
260 let endpoint = EndpointBuilder::get("/api/test")
261 .json(serde_json::json!({"test": "data"}))
262 .build();
263
264 assert_eq!(endpoint.path, "/api/test");
265 assert_eq!(endpoint.method, HttpMethod::Get);
266 }
267
268 #[test]
269 fn test_endpoint_response_helpers() {
270 let ok_response = EndpointResponse::ok(serde_json::json!({"status": "ok"}));
271 assert_eq!(ok_response.status, StatusCode::OK);
272
273 let created_response = EndpointResponse::created(serde_json::json!({"id": 1}));
274 assert_eq!(created_response.status, StatusCode::CREATED);
275
276 let not_found = EndpointResponse::not_found("Resource not found");
277 assert_eq!(not_found.status, StatusCode::NOT_FOUND);
278 }
279
280 #[test]
281 fn test_helper_functions() {
282 let endpoint = get("/api/version", serde_json::json!({"version": "1.0.0"}));
283 assert_eq!(endpoint.path, "/api/version");
284 assert_eq!(endpoint.method, HttpMethod::Get);
285 }
286}