elif_http/request/
request.rs

1//! Request abstraction for handling HTTP requests
2//!
3//! Provides rich request parsing and data extraction capabilities.
4
5use 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/// Request abstraction that wraps Axum's request types
14/// with additional parsing and extraction capabilities
15#[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    /// Create new ElifRequest from Axum components
28    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    /// Extract ElifRequest from request components
41    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    /// Set path parameters extracted from route
55    pub fn with_path_params(mut self, params: HashMap<String, String>) -> Self {
56        self.path_params = params;
57        self
58    }
59
60    /// Set query parameters
61    pub fn with_query_params(mut self, params: HashMap<String, String>) -> Self {
62        self.query_params = params;
63        self
64    }
65
66    /// Set request body bytes (consuming)
67    pub fn with_body(mut self, body: Bytes) -> Self {
68        self.body_bytes = Some(body);
69        self
70    }
71
72    /// Set request body bytes (borrowing - for middleware use)
73    pub fn set_body(&mut self, body: Bytes) {
74        self.body_bytes = Some(body);
75    }
76
77    /// Add header to request (for middleware use)
78    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    /// Add path parameter (for middleware use)
95    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    /// Add query parameter (for middleware use)
104    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    /// Get path parameter by name
113    pub fn path_param(&self, name: &str) -> Option<&String> {
114        self.path_params.get(name)
115    }
116
117    /// Get path parameter by name, parsed to specific type
118    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    /// Get query parameter by name
133    pub fn query_param(&self, name: &str) -> Option<&String> {
134        self.query_params.get(name)
135    }
136
137    /// Get query parameter by name, parsed to specific type
138    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    /// Get required query parameter by name, parsed to specific type
154    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    /// Get header value by name
165    pub fn header(&self, name: &str) -> Option<&crate::response::ElifHeaderValue> {
166        self.headers.get_str(name)
167    }
168
169    /// Get header value as string
170    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    /// Get Content-Type header
182    pub fn content_type(&self) -> HttpResult<Option<String>> {
183        self.header_string("content-type")
184    }
185
186    /// Check if request has JSON content type
187    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    /// Get request body as bytes
196    pub fn body_bytes(&self) -> Option<&Bytes> {
197        self.body_bytes.as_ref()
198    }
199
200    /// Parse JSON body to specified type
201    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    /// Parse JSON body to specified type (async version for consistency)
211    pub async fn json_async<T: DeserializeOwned>(&self) -> HttpResult<T> {
212        self.json()
213    }
214
215    /// Parse form data body to specified type
216    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    /// Parse query parameters to specified type
229    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    /// Parse path parameters to specified type
236    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    /// Get a query parameter as a specific type
245    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    /// Get User-Agent header
265    pub fn user_agent(&self) -> Option<String> {
266        self.header_string("user-agent").unwrap_or(None)
267    }
268
269    /// Get Authorization header
270    pub fn authorization(&self) -> Option<String> {
271        self.header_string("authorization").unwrap_or(None)
272    }
273
274    /// Extract Bearer token from Authorization header
275    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    /// Get request IP address from headers or connection
288    pub fn client_ip(&self) -> Option<String> {
289        // Try common forwarded headers first
290        if let Ok(Some(forwarded)) = self.header_string("x-forwarded-for") {
291            // Take first IP if multiple
292            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        // Could extend with connection info if available
302        None
303    }
304
305    /// Check if request is HTTPS
306    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    /// Get request host
314    pub fn host(&self) -> Option<&str> {
315        self.uri.host()
316    }
317
318    /// Get request path
319    pub fn path(&self) -> &str {
320        self.uri.path()
321    }
322
323    /// Get query string
324    pub fn query_string(&self) -> Option<&str> {
325        self.uri.query()
326    }
327
328    /// Convert ElifRequest to Axum Request for backward compatibility
329    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        // Add headers one by one
342        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    /// Convert Axum Request to ElifRequest for backward compatibility
352    pub(crate) async fn from_axum_request(request: axum::extract::Request) -> Self {
353        let (parts, body) = request.into_parts();
354
355        // Extract body bytes
356        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    /// Get a reference to the extensions map for reading middleware-added data
367    pub fn extensions(&self) -> &HashMap<TypeId, Box<dyn Any + Send + Sync>> {
368        &self.extensions
369    }
370
371    /// Get a mutable reference to the extensions map for adding middleware data
372    pub fn extensions_mut(&mut self) -> &mut HashMap<TypeId, Box<dyn Any + Send + Sync>> {
373        &mut self.extensions
374    }
375
376    /// Insert typed data into request extensions (helper for middleware)
377    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    /// Get typed data from request extensions (helper for middleware)
382    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
389/// Enhanced parameter extraction methods with better error handling and type safety
390impl ElifRequest {
391    /// Extract and parse a path parameter with proper error handling
392    ///
393    /// This is the preferred method for extracting path parameters as it provides
394    /// better error messages and type safety compared to the legacy methods.
395    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    /// Extract and parse a path parameter as i32
404    pub fn path_param_int(&self, name: &str) -> Result<i32, crate::request::pipeline::ParamError> {
405        self.path_param_typed(name)
406    }
407
408    /// Extract and parse a path parameter as u32
409    pub fn path_param_u32(&self, name: &str) -> Result<u32, crate::request::pipeline::ParamError> {
410        self.path_param_typed(name)
411    }
412
413    /// Extract and parse a path parameter as i64
414    pub fn path_param_i64(&self, name: &str) -> Result<i64, crate::request::pipeline::ParamError> {
415        self.path_param_typed(name)
416    }
417
418    /// Extract and parse a path parameter as u64
419    pub fn path_param_u64(&self, name: &str) -> Result<u64, crate::request::pipeline::ParamError> {
420        self.path_param_typed(name)
421    }
422
423    /// Extract and parse a path parameter as UUID
424    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    /// Extract and parse a path parameter as String (validates non-empty)
432    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    /// Extract and parse an optional query parameter with proper error handling
448    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    /// Extract and parse a required query parameter with proper error handling  
460    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    /// Extract query parameter as optional i32
472    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    /// Extract query parameter as required i32
480    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    /// Extract query parameter as optional u32
488    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    /// Extract query parameter as required u32
496    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    /// Extract query parameter as optional bool
504    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    /// Extract query parameter as required bool
512    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    /// Extract query parameter as optional String
520    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    /// Extract query parameter as required String
528    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    /// Validate that path parameter exists and is not empty
536    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    /// Check if request has a specific path parameter
557    pub fn has_path_param(&self, name: &str) -> bool {
558        self.path_params.contains_key(name)
559    }
560
561    /// Check if request has a specific query parameter
562    pub fn has_query_param(&self, name: &str) -> bool {
563        self.query_params.contains_key(name)
564    }
565
566    /// Get all path parameter names
567    pub fn path_param_names(&self) -> Vec<&String> {
568        self.path_params.keys().collect()
569    }
570
571    /// Get all query parameter names
572    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        // Test existing convenient methods
598        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        // Test typed path params (existing method)
603        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        // Test error on invalid type conversion
610        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        // Test query param access
627        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        // Test typed query params
635        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        // Test error on invalid type conversion
642        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        // Test header access
660        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()); // Should fail parsing
693    }
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        // Test adding headers with borrowing API
748        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        // Test adding parameters with borrowing API
776        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        // Simulate middleware adding context data
810        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        // Verify all modifications were applied
815        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}