1use super::ElifMethod;
6use crate::errors::{HttpError, HttpResult};
7use crate::response::ElifHeaderMap;
8use axum::{body::Bytes, http::Uri};
9use serde::de::DeserializeOwned;
10use std::any::{Any, TypeId};
11use std::collections::HashMap;
12
13#[derive(Debug)]
16pub struct ElifRequest {
17 pub method: ElifMethod,
18 pub uri: Uri,
19 pub headers: ElifHeaderMap,
20 pub path_params: HashMap<String, String>,
21 pub query_params: HashMap<String, String>,
22 pub extensions: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
23 body_bytes: Option<Bytes>,
24}
25
26impl ElifRequest {
27 pub fn new(method: ElifMethod, uri: Uri, headers: ElifHeaderMap) -> Self {
29 Self {
30 method,
31 uri,
32 headers,
33 path_params: HashMap::new(),
34 query_params: HashMap::new(),
35 extensions: HashMap::new(),
36 body_bytes: None,
37 }
38 }
39
40 pub fn extract_elif_request(
42 method: ElifMethod,
43 uri: Uri,
44 headers: ElifHeaderMap,
45 body: Option<Bytes>,
46 ) -> ElifRequest {
47 let mut request = ElifRequest::new(method, uri, headers);
48 if let Some(body) = body {
49 request = request.with_body(body);
50 }
51 request
52 }
53
54 pub fn with_path_params(mut self, params: HashMap<String, String>) -> Self {
56 self.path_params = params;
57 self
58 }
59
60 pub fn with_query_params(mut self, params: HashMap<String, String>) -> Self {
62 self.query_params = params;
63 self
64 }
65
66 pub fn with_body(mut self, body: Bytes) -> Self {
68 self.body_bytes = Some(body);
69 self
70 }
71
72 pub fn set_body(&mut self, body: Bytes) {
74 self.body_bytes = Some(body);
75 }
76
77 pub fn add_header<K, V>(&mut self, key: K, value: V) -> HttpResult<()>
79 where
80 K: AsRef<str>,
81 V: AsRef<str>,
82 {
83 use crate::response::{ElifHeaderName, ElifHeaderValue};
84
85 let header_name = ElifHeaderName::from_str(key.as_ref())
86 .map_err(|e| HttpError::bad_request(format!("Invalid header name: {}", e)))?;
87 let header_value = ElifHeaderValue::from_str(value.as_ref())
88 .map_err(|e| HttpError::bad_request(format!("Invalid header value: {}", e)))?;
89
90 self.headers.insert(header_name, header_value);
91 Ok(())
92 }
93
94 pub fn add_path_param<K, V>(&mut self, key: K, value: V)
96 where
97 K: Into<String>,
98 V: Into<String>,
99 {
100 self.path_params.insert(key.into(), value.into());
101 }
102
103 pub fn add_query_param<K, V>(&mut self, key: K, value: V)
105 where
106 K: Into<String>,
107 V: Into<String>,
108 {
109 self.query_params.insert(key.into(), value.into());
110 }
111
112 pub fn path_param(&self, name: &str) -> Option<&String> {
114 self.path_params.get(name)
115 }
116
117 pub fn path_param_parsed<T>(&self, name: &str) -> HttpResult<T>
119 where
120 T: std::str::FromStr,
121 T::Err: std::fmt::Display,
122 {
123 let param = self
124 .path_param(name)
125 .ok_or_else(|| HttpError::bad_request(format!("Missing path parameter: {}", name)))?;
126
127 param
128 .parse::<T>()
129 .map_err(|e| HttpError::bad_request(format!("Invalid path parameter {}: {}", name, e)))
130 }
131
132 pub fn query_param(&self, name: &str) -> Option<&String> {
134 self.query_params.get(name)
135 }
136
137 pub fn query_param_parsed<T>(&self, name: &str) -> HttpResult<Option<T>>
139 where
140 T: std::str::FromStr,
141 T::Err: std::fmt::Display,
142 {
143 if let Some(param) = self.query_param(name) {
144 let parsed = param.parse::<T>().map_err(|e| {
145 HttpError::bad_request(format!("Invalid query parameter {}: {}", name, e))
146 })?;
147 Ok(Some(parsed))
148 } else {
149 Ok(None)
150 }
151 }
152
153 pub fn query_param_required<T>(&self, name: &str) -> HttpResult<T>
155 where
156 T: std::str::FromStr,
157 T::Err: std::fmt::Display,
158 {
159 self.query_param_parsed(name)?.ok_or_else(|| {
160 HttpError::bad_request(format!("Missing required query parameter: {}", name))
161 })
162 }
163
164 pub fn header(&self, name: &str) -> Option<&crate::response::ElifHeaderValue> {
166 self.headers.get_str(name)
167 }
168
169 pub fn header_string(&self, name: &str) -> HttpResult<Option<String>> {
171 if let Some(value) = self.header(name) {
172 let str_value = value.to_str().map_err(|_| {
173 HttpError::bad_request(format!("Invalid header value for {}", name))
174 })?;
175 Ok(Some(str_value.to_string()))
176 } else {
177 Ok(None)
178 }
179 }
180
181 pub fn content_type(&self) -> HttpResult<Option<String>> {
183 self.header_string("content-type")
184 }
185
186 pub fn is_json(&self) -> bool {
188 if let Ok(Some(content_type)) = self.content_type() {
189 content_type.contains("application/json")
190 } else {
191 false
192 }
193 }
194
195 pub fn body_bytes(&self) -> Option<&Bytes> {
197 self.body_bytes.as_ref()
198 }
199
200 pub fn json<T: DeserializeOwned>(&self) -> HttpResult<T> {
202 let bytes = self
203 .body_bytes()
204 .ok_or_else(|| HttpError::bad_request("No request body".to_string()))?;
205
206 serde_json::from_slice(bytes)
207 .map_err(|e| HttpError::bad_request(format!("Invalid JSON body: {}", e)))
208 }
209
210 pub async fn json_async<T: DeserializeOwned>(&self) -> HttpResult<T> {
212 self.json()
213 }
214
215 pub fn form<T: DeserializeOwned>(&self) -> HttpResult<T> {
217 let bytes = self
218 .body_bytes()
219 .ok_or_else(|| HttpError::bad_request("No request body".to_string()))?;
220
221 let body_str = std::str::from_utf8(bytes)
222 .map_err(|_| HttpError::bad_request("Invalid UTF-8 in form body".to_string()))?;
223
224 serde_urlencoded::from_str(body_str)
225 .map_err(|e| HttpError::bad_request(format!("Invalid form data: {}", e)))
226 }
227
228 pub fn query<T: DeserializeOwned>(&self) -> HttpResult<T> {
230 let query_str = self.query_string().unwrap_or("");
231 serde_urlencoded::from_str::<T>(query_str)
232 .map_err(|e| HttpError::bad_request(format!("Invalid query parameters: {}", e)))
233 }
234
235 pub fn path_params<T: DeserializeOwned>(&self) -> HttpResult<T> {
237 let json_value = serde_json::to_value(&self.path_params)
238 .map_err(|e| HttpError::internal(format!("Failed to serialize path params: {}", e)))?;
239
240 serde_json::from_value::<T>(json_value)
241 .map_err(|e| HttpError::bad_request(format!("Invalid path parameters: {}", e)))
242 }
243
244 pub fn query_param_as<T>(&self, name: &str) -> HttpResult<Option<T>>
246 where
247 T: std::str::FromStr,
248 T::Err: std::fmt::Display,
249 {
250 match self.query_param(name) {
251 Some(param) => {
252 let parsed = param.parse::<T>().map_err(|e| {
253 HttpError::bad_request(format!(
254 "Invalid {} query parameter '{}': {}",
255 name, param, e
256 ))
257 })?;
258 Ok(Some(parsed))
259 }
260 None => Ok(None),
261 }
262 }
263
264 pub fn user_agent(&self) -> Option<String> {
266 self.header_string("user-agent").unwrap_or(None)
267 }
268
269 pub fn authorization(&self) -> Option<String> {
271 self.header_string("authorization").unwrap_or(None)
272 }
273
274 pub fn bearer_token(&self) -> Option<String> {
276 if let Some(auth) = self.authorization() {
277 if auth.starts_with("Bearer ") {
278 Some(auth[7..].to_string())
279 } else {
280 None
281 }
282 } else {
283 None
284 }
285 }
286
287 pub fn client_ip(&self) -> Option<String> {
289 if let Ok(Some(forwarded)) = self.header_string("x-forwarded-for") {
291 if let Some(ip) = forwarded.split(',').next() {
293 return Some(ip.trim().to_string());
294 }
295 }
296
297 if let Ok(Some(real_ip)) = self.header_string("x-real-ip") {
298 return Some(real_ip);
299 }
300
301 None
303 }
304
305 pub fn is_secure(&self) -> bool {
307 self.uri
308 .scheme()
309 .map(|s| s == &axum::http::uri::Scheme::HTTPS)
310 .unwrap_or(false)
311 }
312
313 pub fn host(&self) -> Option<&str> {
315 self.uri.host()
316 }
317
318 pub fn path(&self) -> &str {
320 self.uri.path()
321 }
322
323 pub fn query_string(&self) -> Option<&str> {
325 self.uri.query()
326 }
327
328 pub(crate) fn into_axum_request(self) -> axum::extract::Request {
330 use axum::body::Body;
331
332 let body = match self.body_bytes {
333 Some(bytes) => Body::from(bytes),
334 None => Body::empty(),
335 };
336
337 let mut builder = axum::extract::Request::builder()
338 .method(self.method.to_axum())
339 .uri(self.uri);
340
341 for (key, value) in self.headers.iter() {
343 builder = builder.header(key.to_axum(), value.to_axum());
344 }
345
346 builder
347 .body(body)
348 .expect("Failed to construct Axum request")
349 }
350
351 pub(crate) async fn from_axum_request(request: axum::extract::Request) -> Self {
353 let (parts, body) = request.into_parts();
354
355 let body_bytes = (axum::body::to_bytes(body, usize::MAX).await).ok();
357
358 Self::extract_elif_request(
359 ElifMethod::from_axum(parts.method),
360 parts.uri,
361 ElifHeaderMap::from_axum(parts.headers),
362 body_bytes,
363 )
364 }
365
366 pub fn extensions(&self) -> &HashMap<TypeId, Box<dyn Any + Send + Sync>> {
368 &self.extensions
369 }
370
371 pub fn extensions_mut(&mut self) -> &mut HashMap<TypeId, Box<dyn Any + Send + Sync>> {
373 &mut self.extensions
374 }
375
376 pub fn insert_extension<T: Send + Sync + 'static>(&mut self, data: T) {
378 self.extensions.insert(TypeId::of::<T>(), Box::new(data));
379 }
380
381 pub fn get_extension<T: Send + Sync + 'static>(&self) -> Option<&T> {
383 self.extensions
384 .get(&TypeId::of::<T>())
385 .and_then(|any| any.downcast_ref::<T>())
386 }
387}
388
389impl ElifRequest {
391 pub fn path_param_typed<T>(&self, name: &str) -> Result<T, crate::request::pipeline::ParamError>
396 where
397 T: std::str::FromStr,
398 T::Err: std::fmt::Debug + std::fmt::Display,
399 {
400 crate::request::pipeline::parameter_extraction::extract_path_param(self, name)
401 }
402
403 pub fn path_param_int(&self, name: &str) -> Result<i32, crate::request::pipeline::ParamError> {
405 self.path_param_typed(name)
406 }
407
408 pub fn path_param_u32(&self, name: &str) -> Result<u32, crate::request::pipeline::ParamError> {
410 self.path_param_typed(name)
411 }
412
413 pub fn path_param_i64(&self, name: &str) -> Result<i64, crate::request::pipeline::ParamError> {
415 self.path_param_typed(name)
416 }
417
418 pub fn path_param_u64(&self, name: &str) -> Result<u64, crate::request::pipeline::ParamError> {
420 self.path_param_typed(name)
421 }
422
423 pub fn path_param_uuid(
425 &self,
426 name: &str,
427 ) -> Result<uuid::Uuid, crate::request::pipeline::ParamError> {
428 self.path_param_typed(name)
429 }
430
431 pub fn path_param_string(
433 &self,
434 name: &str,
435 ) -> Result<String, crate::request::pipeline::ParamError> {
436 let value: String = self.path_param_typed(name)?;
437 if value.is_empty() {
438 return Err(crate::request::pipeline::ParamError::ParseError {
439 param: name.to_string(),
440 value: value.clone(),
441 error: "Parameter cannot be empty".to_string(),
442 });
443 }
444 Ok(value)
445 }
446
447 pub fn query_param_typed_new<T>(
449 &self,
450 name: &str,
451 ) -> Result<Option<T>, crate::request::pipeline::ParamError>
452 where
453 T: std::str::FromStr,
454 T::Err: std::fmt::Debug + std::fmt::Display,
455 {
456 crate::request::pipeline::parameter_extraction::extract_query_param(self, name)
457 }
458
459 pub fn query_param_required_typed<T>(
461 &self,
462 name: &str,
463 ) -> Result<T, crate::request::pipeline::ParamError>
464 where
465 T: std::str::FromStr,
466 T::Err: std::fmt::Debug + std::fmt::Display,
467 {
468 crate::request::pipeline::parameter_extraction::extract_required_query_param(self, name)
469 }
470
471 pub fn query_param_int_new(
473 &self,
474 name: &str,
475 ) -> Result<Option<i32>, crate::request::pipeline::ParamError> {
476 self.query_param_typed_new(name)
477 }
478
479 pub fn query_param_int_required(
481 &self,
482 name: &str,
483 ) -> Result<i32, crate::request::pipeline::ParamError> {
484 self.query_param_required_typed(name)
485 }
486
487 pub fn query_param_u32_new(
489 &self,
490 name: &str,
491 ) -> Result<Option<u32>, crate::request::pipeline::ParamError> {
492 self.query_param_typed_new(name)
493 }
494
495 pub fn query_param_u32_required(
497 &self,
498 name: &str,
499 ) -> Result<u32, crate::request::pipeline::ParamError> {
500 self.query_param_required_typed(name)
501 }
502
503 pub fn query_param_bool_new(
505 &self,
506 name: &str,
507 ) -> Result<Option<bool>, crate::request::pipeline::ParamError> {
508 self.query_param_typed_new(name)
509 }
510
511 pub fn query_param_bool_required(
513 &self,
514 name: &str,
515 ) -> Result<bool, crate::request::pipeline::ParamError> {
516 self.query_param_required_typed(name)
517 }
518
519 pub fn query_param_string_new(
521 &self,
522 name: &str,
523 ) -> Result<Option<String>, crate::request::pipeline::ParamError> {
524 self.query_param_typed_new(name)
525 }
526
527 pub fn query_param_string_required(
529 &self,
530 name: &str,
531 ) -> Result<String, crate::request::pipeline::ParamError> {
532 self.query_param_required_typed(name)
533 }
534
535 pub fn validate_path_param(
537 &self,
538 name: &str,
539 ) -> Result<&String, crate::request::pipeline::ParamError> {
540 let param = self
541 .path_params
542 .get(name)
543 .ok_or_else(|| crate::request::pipeline::ParamError::Missing(name.to_string()))?;
544
545 if param.is_empty() {
546 return Err(crate::request::pipeline::ParamError::ParseError {
547 param: name.to_string(),
548 value: param.clone(),
549 error: "Parameter cannot be empty".to_string(),
550 });
551 }
552
553 Ok(param)
554 }
555
556 pub fn has_path_param(&self, name: &str) -> bool {
558 self.path_params.contains_key(name)
559 }
560
561 pub fn has_query_param(&self, name: &str) -> bool {
563 self.query_params.contains_key(name)
564 }
565
566 pub fn path_param_names(&self) -> Vec<&String> {
568 self.path_params.keys().collect()
569 }
570
571 pub fn query_param_names(&self) -> Vec<&String> {
573 self.query_params.keys().collect()
574 }
575}
576
577#[cfg(test)]
578mod tests {
579 use super::*;
580 use crate::response::ElifHeaderMap;
581 use axum::http::Uri;
582 use std::collections::HashMap;
583
584 #[test]
585 fn test_new_path_param_methods() {
586 let mut params = HashMap::new();
587 params.insert("id".to_string(), "123".to_string());
588 params.insert("slug".to_string(), "test-post".to_string());
589
590 let mut request = ElifRequest::new(
591 ElifMethod::GET,
592 "/users/123/posts/test-post".parse().unwrap(),
593 ElifHeaderMap::new(),
594 );
595 request.path_params = params;
596
597 assert_eq!(request.path_param("id"), Some(&"123".to_string()));
599 assert_eq!(request.path_param("slug"), Some(&"test-post".to_string()));
600 assert_eq!(request.path_param("nonexistent"), None);
601
602 let id: u32 = request.path_param_parsed("id").unwrap();
604 assert_eq!(id, 123);
605
606 let slug: String = request.path_param_parsed("slug").unwrap();
607 assert_eq!(slug, "test-post");
608
609 assert!(request.path_param_parsed::<u32>("slug").is_err());
611 }
612
613 #[test]
614 fn test_query_param_methods() {
615 let mut query_params = HashMap::new();
616 query_params.insert("page".to_string(), "2".to_string());
617 query_params.insert("search".to_string(), "hello world".to_string());
618
619 let mut request = ElifRequest::new(
620 ElifMethod::GET,
621 "/search?page=2&search=hello%20world".parse().unwrap(),
622 ElifHeaderMap::new(),
623 );
624 request.query_params = query_params;
625
626 assert_eq!(request.query_param("page"), Some(&"2".to_string()));
628 assert_eq!(
629 request.query_param("search"),
630 Some(&"hello world".to_string())
631 );
632 assert_eq!(request.query_param("nonexistent"), None);
633
634 let page: Option<u32> = request.query_param_as("page").unwrap();
636 assert_eq!(page, Some(2));
637
638 let nonexistent: Option<u32> = request.query_param_as("nonexistent").unwrap();
639 assert_eq!(nonexistent, None);
640
641 assert!(request.query_param_as::<u32>("search").is_err());
643 }
644
645 #[test]
646 fn test_header_method() {
647 let mut headers = ElifHeaderMap::new();
648 headers.insert(
649 "Content-Type".parse().unwrap(),
650 "application/json".parse().unwrap(),
651 );
652 headers.insert(
653 "User-Agent".parse().unwrap(),
654 "test-client/1.0".parse().unwrap(),
655 );
656
657 let request = ElifRequest::new(ElifMethod::POST, "/api/test".parse().unwrap(), headers);
658
659 assert_eq!(
661 request.header_string("content-type").unwrap(),
662 Some("application/json".to_string())
663 );
664 assert_eq!(
665 request.header_string("user-agent").unwrap(),
666 Some("test-client/1.0".to_string())
667 );
668 assert_eq!(request.header_string("nonexistent").unwrap(), None);
669 }
670
671 #[test]
672 fn test_query_param_extraction() {
673 let mut query_params = HashMap::new();
674 query_params.insert("page".to_string(), "2".to_string());
675 query_params.insert("per_page".to_string(), "25".to_string());
676 query_params.insert("search".to_string(), "rust".to_string());
677
678 let request = ElifRequest::new(
679 ElifMethod::GET,
680 "/posts?page=2&per_page=25&search=rust".parse().unwrap(),
681 ElifHeaderMap::new(),
682 )
683 .with_query_params(query_params);
684
685 assert_eq!(request.query_param("page"), Some(&"2".to_string()));
686 let page: u32 = request.query_param_required("page").unwrap();
687 assert_eq!(page, 2);
688
689 let per_page: Option<u32> = request.query_param_parsed("per_page").unwrap();
690 assert_eq!(per_page, Some(25));
691
692 assert!(request.query_param_parsed::<u32>("search").is_err()); }
694
695 #[test]
696 fn test_json_detection() {
697 let mut headers = ElifHeaderMap::new();
698 let header_name = crate::response::ElifHeaderName::from_str("content-type").unwrap();
699 let header_value = crate::response::ElifHeaderValue::from_str("application/json").unwrap();
700 headers.insert(header_name, header_value);
701
702 let request = ElifRequest::new(ElifMethod::POST, "/api/users".parse().unwrap(), headers);
703
704 assert!(request.is_json());
705 }
706
707 #[test]
708 fn test_bearer_token_extraction() {
709 let mut headers = ElifHeaderMap::new();
710 let header_name = crate::response::ElifHeaderName::from_str("authorization").unwrap();
711 let header_value = crate::response::ElifHeaderValue::from_str("Bearer abc123xyz").unwrap();
712 headers.insert(header_name, header_value);
713
714 let request = ElifRequest::new(ElifMethod::GET, "/api/protected".parse().unwrap(), headers);
715
716 let token = request.bearer_token().unwrap();
717 assert_eq!(token, "abc123xyz");
718 }
719
720 #[test]
721 fn test_extract_elif_request() {
722 let method = ElifMethod::POST;
723 let uri: Uri = "/test".parse().unwrap();
724 let headers = ElifHeaderMap::new();
725 let body = Some(Bytes::from("test body"));
726
727 let request = ElifRequest::extract_elif_request(
728 method.clone(),
729 uri.clone(),
730 headers.clone(),
731 body.clone(),
732 );
733
734 assert_eq!(request.method, method);
735 assert_eq!(request.uri, uri);
736 assert_eq!(request.body_bytes(), body.as_ref());
737 }
738
739 #[test]
740 fn test_borrowing_api_headers() {
741 let mut request = ElifRequest::new(
742 ElifMethod::GET,
743 "/test".parse().unwrap(),
744 ElifHeaderMap::new(),
745 );
746
747 request.add_header("x-middleware", "processed").unwrap();
749 request.add_header("x-custom", "value").unwrap();
750
751 let middleware_header =
752 crate::response::headers::ElifHeaderName::from_str("x-middleware").unwrap();
753 let custom_header = crate::response::headers::ElifHeaderName::from_str("x-custom").unwrap();
754 assert!(request.headers.contains_key(&middleware_header));
755 assert!(request.headers.contains_key(&custom_header));
756 assert_eq!(
757 request
758 .headers
759 .get(&middleware_header)
760 .unwrap()
761 .to_str()
762 .unwrap(),
763 "processed"
764 );
765 }
766
767 #[test]
768 fn test_borrowing_api_params() {
769 let mut request = ElifRequest::new(
770 ElifMethod::GET,
771 "/users/123?page=2".parse().unwrap(),
772 ElifHeaderMap::new(),
773 );
774
775 request.add_path_param("id", "123");
777 request.add_path_param("section", "profile");
778 request.add_query_param("page", "2");
779 request.add_query_param("limit", "10");
780
781 assert_eq!(request.path_param("id"), Some(&"123".to_string()));
782 assert_eq!(request.path_param("section"), Some(&"profile".to_string()));
783 assert_eq!(request.query_param("page"), Some(&"2".to_string()));
784 assert_eq!(request.query_param("limit"), Some(&"10".to_string()));
785 }
786
787 #[test]
788 fn test_borrowing_api_body() {
789 let mut request = ElifRequest::new(
790 ElifMethod::POST,
791 "/test".parse().unwrap(),
792 ElifHeaderMap::new(),
793 );
794
795 let body_data = Bytes::from("test body content");
796 request.set_body(body_data.clone());
797
798 assert_eq!(request.body_bytes(), Some(&body_data));
799 }
800
801 #[test]
802 fn test_borrowing_api_middleware_pattern() {
803 let mut request = ElifRequest::new(
804 ElifMethod::GET,
805 "/api/users".parse().unwrap(),
806 ElifHeaderMap::new(),
807 );
808
809 request.add_header("x-request-id", "req-123").unwrap();
811 request.add_path_param("user_id", "456");
812 request.add_query_param("enriched", "true");
813
814 let request_id_header =
816 crate::response::headers::ElifHeaderName::from_str("x-request-id").unwrap();
817 assert!(request.headers.contains_key(&request_id_header));
818 assert_eq!(request.path_param("user_id"), Some(&"456".to_string()));
819 assert_eq!(request.query_param("enriched"), Some(&"true".to_string()));
820 }
821}