1use std::fmt;
6use std::str::FromStr;
7use std::collections::HashMap;
8
9use axum::{
10 body::Bytes,
11 extract::Request as AxumRequest,
12 http::{HeaderMap, HeaderValue, Method, StatusCode, Uri, Version},
13 response::{IntoResponse, Response as AxumResponse},
14 Json,
15};
16use serde::{de::DeserializeOwned, Serialize};
17use serde_json::{json, Value};
18
19use crate::errors::{Result, RustisanError};
20
21pub struct Request {
23 pub method: Method,
25 pub uri: Uri,
27 pub version: Version,
29 pub headers: HeaderMap,
31 pub path_params: HashMap<String, String>,
33 pub query_params: HashMap<String, String>,
35 pub cookies: HashMap<String, String>,
37 body: Option<Bytes>,
39 inner: AxumRequest,
41}
42
43impl Request {
44 pub async fn from_axum(mut req: AxumRequest) -> Self {
46 let method = req.method().clone();
47 let uri = req.uri().clone();
48 let version = req.version();
49 let headers = req.headers().clone();
50
51 let cookies = Self::extract_cookies(&headers);
53
54 let query_params = Self::extract_query_params(uri.query());
56
57 let body = None;
59
60 Self {
61 method,
62 uri,
63 version,
64 headers,
65 path_params: HashMap::new(),
66 query_params,
67 cookies,
68 body,
69 inner: req,
70 }
71 }
72
73 fn extract_cookies(headers: &HeaderMap) -> HashMap<String, String> {
75 let mut cookies = HashMap::new();
76
77 if let Some(cookie_header) = headers.get("cookie") {
78 if let Ok(cookie_str) = cookie_header.to_str() {
79 for cookie in cookie_str.split(';') {
80 let cookie = cookie.trim();
81 if let Some(idx) = cookie.find('=') {
82 let (key, value) = cookie.split_at(idx);
83 cookies.insert(key.trim().to_owned(), value[1..].trim().to_owned());
84 }
85 }
86 }
87 }
88
89 cookies
90 }
91
92 fn extract_query_params(query: Option<&str>) -> HashMap<String, String> {
94 let mut params = HashMap::new();
95
96 if let Some(query_str) = query {
97 for param in query_str.split('&') {
98 if let Some(idx) = param.find('=') {
99 let (key, value) = param.split_at(idx);
100 params.insert(key.to_owned(), value[1..].to_owned());
101 } else {
102 params.insert(param.to_owned(), String::new());
103 }
104 }
105 }
106
107 params
108 }
109
110 pub fn param<T: FromStr>(&self, name: &str) -> Result<T> {
112 self.path_params
113 .get(name)
114 .ok_or_else(|| RustisanError::BadRequest(format!("Missing path parameter: {}", name)))
115 .and_then(|s| {
116 s.parse::<T>().map_err(|_| {
117 RustisanError::BadRequest(format!("Invalid path parameter: {}", name))
118 })
119 })
120 }
121
122 pub fn query<T: FromStr>(&self, name: &str) -> Result<T> {
124 self.query_params
125 .get(name)
126 .ok_or_else(|| {
127 RustisanError::BadRequest(format!("Missing query parameter: {}", name))
128 })
129 .and_then(|s| {
130 s.parse::<T>().map_err(|_| {
131 RustisanError::BadRequest(format!("Invalid query parameter: {}", name))
132 })
133 })
134 }
135
136 pub fn query_or<T: FromStr>(&self, name: &str, default: T) -> T {
138 self.query(name).unwrap_or(default)
139 }
140
141 pub fn cookie(&self, name: &str) -> Option<&String> {
143 self.cookies.get(name)
144 }
145
146 pub fn header(&self, name: &str) -> Option<&HeaderValue> {
148 self.headers.get(name)
149 }
150
151 pub fn is_json(&self) -> bool {
153 self.headers
154 .get("content-type")
155 .and_then(|v| v.to_str().ok())
156 .map(|s| s.starts_with("application/json"))
157 .unwrap_or(false)
158 }
159
160 pub async fn json<T: DeserializeOwned>(&self) -> Result<T> {
162 if !self.is_json() {
163 return Err(RustisanError::BadRequest(
164 "Content-Type must be application/json".to_string(),
165 ));
166 }
167
168 match &self.body {
169 Some(bytes) => serde_json::from_slice(bytes)
170 .map_err(|e| RustisanError::BadRequest(format!("Invalid JSON: {}", e))),
171 None => Err(RustisanError::BadRequest("Empty request body".to_string())),
172 }
173 }
174
175 pub fn body(&self) -> Option<&Bytes> {
177 self.body.as_ref()
178 }
179
180 pub fn body_string(&self) -> Result<String> {
182 match &self.body {
183 Some(bytes) => String::from_utf8(bytes.to_vec())
184 .map_err(|e| RustisanError::BadRequest(format!("Invalid UTF-8: {}", e))),
185 None => Err(RustisanError::BadRequest("Empty request body".to_string())),
186 }
187 }
188
189 pub fn inner(&self) -> &AxumRequest {
191 &self.inner
192 }
193}
194
195#[derive(Debug, Clone, Serialize)]
197pub struct Response {
198 #[serde(skip)]
200 pub status: StatusCode,
201 #[serde(flatten)]
203 pub data: Value,
204 #[serde(skip_serializing_if = "Option::is_none")]
206 pub message: Option<String>,
207}
208
209impl Response {
210 pub fn new(status: StatusCode, data: Value) -> Self {
212 Self {
213 status,
214 data,
215 message: None,
216 }
217 }
218
219 pub fn with_message(status: StatusCode, data: Value, message: String) -> Self {
221 Self {
222 status,
223 data,
224 message: Some(message),
225 }
226 }
227
228 pub fn ok<S: Into<String>>(body: S) -> Result<Self> {
230 Ok(Self {
231 status: StatusCode::OK,
232 data: json!({"data": body.into()}),
233 message: None,
234 })
235 }
236
237 pub fn json(data: Value) -> Result<Self> {
239 Ok(Self {
240 status: StatusCode::OK,
241 data,
242 message: None,
243 })
244 }
245
246 pub fn success(data: Value, message: Option<String>) -> Result<Self> {
248 Ok(Self {
249 status: StatusCode::OK,
250 data,
251 message,
252 })
253 }
254
255 pub fn created(data: Value) -> Result<Self> {
257 Ok(Self {
258 status: StatusCode::CREATED,
259 data,
260 message: None,
261 })
262 }
263
264 pub fn no_content() -> Result<Self> {
266 Ok(Self {
267 status: StatusCode::NO_CONTENT,
268 data: json!({}),
269 message: None,
270 })
271 }
272
273 pub fn bad_request<S: Into<String>>(message: S) -> Result<Self> {
275 Ok(Self {
276 status: StatusCode::BAD_REQUEST,
277 data: json!({"error": message.into()}),
278 message: None,
279 })
280 }
281
282 pub fn unauthorized<S: Into<String>>(message: S) -> Result<Self> {
284 Ok(Self {
285 status: StatusCode::UNAUTHORIZED,
286 data: json!({"error": message.into()}),
287 message: None,
288 })
289 }
290
291 pub fn forbidden<S: Into<String>>(message: S) -> Result<Self> {
293 Ok(Self {
294 status: StatusCode::FORBIDDEN,
295 data: json!({"error": message.into()}),
296 message: None,
297 })
298 }
299
300 pub fn not_found<S: Into<String>>(message: S) -> Result<Self> {
302 Ok(Self {
303 status: StatusCode::NOT_FOUND,
304 data: json!({"error": message.into()}),
305 message: None,
306 })
307 }
308
309 pub fn internal_error<S: Into<String>>(message: S) -> Result<Self> {
311 Ok(Self {
312 status: StatusCode::INTERNAL_SERVER_ERROR,
313 data: json!({"error": message.into()}),
314 message: None,
315 })
316 }
317
318 pub fn with_message_str<S: Into<String>>(mut self, message: S) -> Self {
320 self.message = Some(message.into());
321 self
322 }
323}
324
325impl IntoResponse for Response {
326 fn into_response(self) -> AxumResponse {
327 (StatusCode::OK, Json(self)).into_response()
328 }
329}
330
331impl fmt::Debug for Request {
332 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333 f.debug_struct("Request")
334 .field("method", &self.method)
335 .field("uri", &self.uri)
336 .field("version", &self.version)
337 .field("headers", &self.headers)
338 .field("path_params", &self.path_params)
339 .field("query_params", &self.query_params)
340 .field("cookies", &self.cookies)
341 .field("body", &self.body.as_ref().map(|_| "[bytes]"))
342 .finish()
343 }
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349
350 #[test]
351 fn test_response_creation() {
352 let resp = Response::ok("Hello").unwrap();
353 assert_eq!(resp.status, StatusCode::OK);
354
355 let resp = Response::json(json!({"name": "John"})).unwrap();
356 assert_eq!(resp.status, StatusCode::OK);
357
358 let resp = Response::created(json!({"id": 1})).unwrap();
359 assert_eq!(resp.status, StatusCode::CREATED);
360 }
361
362 #[test]
363 fn test_error_responses() {
364 let resp = Response::not_found("User not found").unwrap();
365 assert_eq!(resp.status, StatusCode::NOT_FOUND);
366
367 let resp = Response::bad_request("Invalid input").unwrap();
368 assert_eq!(resp.status, StatusCode::BAD_REQUEST);
369 }
370
371 #[test]
372 fn test_response_with_message() {
373 let resp = Response::success(json!({"result": true}), Some("Operation successful".to_string())).unwrap();
374 assert_eq!(resp.status, StatusCode::OK);
375 assert_eq!(resp.message, Some("Operation successful".to_string()));
376 }
377}