reinhardt_http/request.rs
1mod body;
2mod methods;
3mod params;
4
5use crate::extensions::Extensions;
6use bytes::Bytes;
7use hyper::{HeaderMap, Method, Uri, Version};
8#[cfg(feature = "parsers")]
9use reinhardt_core::parsers::parser::{ParsedData, Parser};
10use std::collections::HashMap;
11use std::collections::HashSet;
12use std::net::{IpAddr, SocketAddr};
13use std::sync::atomic::AtomicBool;
14use std::sync::{Arc, Mutex};
15
16/// Configuration for trusted proxy IPs.
17///
18/// Only proxy headers (X-Forwarded-For, X-Real-IP, X-Forwarded-Proto) from
19/// these IP addresses will be trusted. By default, no proxies are trusted
20/// and the actual connection information is used.
21#[derive(Debug, Clone, Default)]
22pub struct TrustedProxies {
23 /// Set of trusted proxy IP addresses.
24 /// Only requests originating from these IPs will have their proxy headers honored.
25 trusted_ips: HashSet<IpAddr>,
26}
27
28impl TrustedProxies {
29 /// Create with no trusted proxies (default, most secure).
30 pub fn none() -> Self {
31 Self {
32 trusted_ips: HashSet::new(),
33 }
34 }
35
36 /// Create with a set of trusted proxy IPs.
37 pub fn new(ips: impl IntoIterator<Item = IpAddr>) -> Self {
38 Self {
39 trusted_ips: ips.into_iter().collect(),
40 }
41 }
42
43 /// Check if the given address is a trusted proxy.
44 pub fn is_trusted(&self, addr: &IpAddr) -> bool {
45 self.trusted_ips.contains(addr)
46 }
47
48 /// Check if any proxies are configured.
49 pub fn has_trusted_proxies(&self) -> bool {
50 !self.trusted_ips.is_empty()
51 }
52}
53
54/// HTTP Request representation
55pub struct Request {
56 pub method: Method,
57 pub uri: Uri,
58 pub version: Version,
59 pub headers: HeaderMap,
60 body: Bytes,
61 pub path_params: HashMap<String, String>,
62 pub query_params: HashMap<String, String>,
63 /// Indicates if this request came over HTTPS
64 pub is_secure: bool,
65 /// Remote address of the client (if available)
66 pub remote_addr: Option<SocketAddr>,
67 /// Parsers for request body
68 #[cfg(feature = "parsers")]
69 parsers: Vec<Box<dyn Parser>>,
70 /// Cached parsed data (lazy parsing)
71 #[cfg(feature = "parsers")]
72 parsed_data: Arc<Mutex<Option<ParsedData>>>,
73 /// Whether the body has been consumed
74 body_consumed: Arc<AtomicBool>,
75 /// Extensions for storing arbitrary typed data
76 pub extensions: Extensions,
77}
78
79/// Builder for constructing `Request` instances.
80///
81/// Provides a fluent API for building HTTP requests with optional parameters.
82///
83/// # Examples
84///
85/// ```
86/// use reinhardt_http::Request;
87/// use hyper::Method;
88///
89/// let request = Request::builder()
90/// .method(Method::GET)
91/// .uri("/api/users?page=1")
92/// .build()
93/// .unwrap();
94///
95/// assert_eq!(request.method, Method::GET);
96/// assert_eq!(request.path(), "/api/users");
97/// assert_eq!(request.query_params.get("page"), Some(&"1".to_string()));
98/// ```
99pub struct RequestBuilder {
100 method: Method,
101 uri: Option<Uri>,
102 version: Version,
103 headers: HeaderMap,
104 body: Bytes,
105 is_secure: bool,
106 remote_addr: Option<SocketAddr>,
107 path_params: HashMap<String, String>,
108 /// Captured error from invalid URI
109 uri_error: Option<String>,
110 /// Captured error from invalid header value
111 header_error: Option<String>,
112 #[cfg(feature = "parsers")]
113 parsers: Vec<Box<dyn Parser>>,
114}
115
116impl Default for RequestBuilder {
117 fn default() -> Self {
118 Self {
119 method: Method::GET,
120 uri: None,
121 version: Version::HTTP_11,
122 headers: HeaderMap::new(),
123 body: Bytes::new(),
124 is_secure: false,
125 remote_addr: None,
126 path_params: HashMap::new(),
127 uri_error: None,
128 header_error: None,
129 #[cfg(feature = "parsers")]
130 parsers: Vec::new(),
131 }
132 }
133}
134
135impl RequestBuilder {
136 /// Set the HTTP method.
137 ///
138 /// # Examples
139 ///
140 /// ```
141 /// use reinhardt_http::Request;
142 /// use hyper::Method;
143 ///
144 /// let request = Request::builder()
145 /// .method(Method::POST)
146 /// .uri("/api/users")
147 /// .build()
148 /// .unwrap();
149 ///
150 /// assert_eq!(request.method, Method::POST);
151 /// ```
152 pub fn method(mut self, method: Method) -> Self {
153 self.method = method;
154 self
155 }
156
157 /// Set the request URI.
158 ///
159 /// Accepts either a `&str` or `Uri`. Query parameters will be automatically parsed.
160 ///
161 /// # Examples
162 ///
163 /// ```
164 /// use reinhardt_http::Request;
165 /// use hyper::Method;
166 ///
167 /// let request = Request::builder()
168 /// .method(Method::GET)
169 /// .uri("/api/users?page=1&limit=10")
170 /// .build()
171 /// .unwrap();
172 ///
173 /// assert_eq!(request.path(), "/api/users");
174 /// assert_eq!(request.query_params.get("page"), Some(&"1".to_string()));
175 /// assert_eq!(request.query_params.get("limit"), Some(&"10".to_string()));
176 /// ```
177 pub fn uri<T>(mut self, uri: T) -> Self
178 where
179 T: TryInto<Uri>,
180 T::Error: std::fmt::Display,
181 {
182 match uri.try_into() {
183 Ok(uri) => {
184 self.uri = Some(uri);
185 }
186 Err(e) => {
187 self.uri_error = Some(format!("Invalid URI: {}", e));
188 }
189 }
190 self
191 }
192
193 /// Set the HTTP version.
194 ///
195 /// Defaults to HTTP/1.1 if not specified.
196 ///
197 /// # Examples
198 ///
199 /// ```
200 /// use reinhardt_http::Request;
201 /// use hyper::{Method, Version};
202 ///
203 /// let request = Request::builder()
204 /// .method(Method::GET)
205 /// .uri("/api/users")
206 /// .version(Version::HTTP_2)
207 /// .build()
208 /// .unwrap();
209 ///
210 /// assert_eq!(request.version, Version::HTTP_2);
211 /// ```
212 pub fn version(mut self, version: Version) -> Self {
213 self.version = version;
214 self
215 }
216
217 /// Set the request headers.
218 ///
219 /// Replaces all existing headers.
220 ///
221 /// # Examples
222 ///
223 /// ```
224 /// use reinhardt_http::Request;
225 /// use hyper::{Method, HeaderMap, header};
226 ///
227 /// let mut headers = HeaderMap::new();
228 /// headers.insert(header::CONTENT_TYPE, "application/json".parse().unwrap());
229 ///
230 /// let request = Request::builder()
231 /// .method(Method::POST)
232 /// .uri("/api/users")
233 /// .headers(headers.clone())
234 /// .build()
235 /// .unwrap();
236 ///
237 /// assert_eq!(request.headers.get(header::CONTENT_TYPE).unwrap(), "application/json");
238 /// ```
239 pub fn headers(mut self, headers: HeaderMap) -> Self {
240 self.headers = headers;
241 self
242 }
243
244 /// Add a single header to the request.
245 ///
246 /// # Examples
247 ///
248 /// ```
249 /// use reinhardt_http::Request;
250 /// use hyper::{Method, header};
251 ///
252 /// let request = Request::builder()
253 /// .method(Method::POST)
254 /// .uri("/api/users")
255 /// .header(header::CONTENT_TYPE, "application/json")
256 /// .header(header::AUTHORIZATION, "Bearer token123")
257 /// .build()
258 /// .unwrap();
259 ///
260 /// assert_eq!(request.headers.get(header::CONTENT_TYPE).unwrap(), "application/json");
261 /// assert_eq!(request.headers.get(header::AUTHORIZATION).unwrap(), "Bearer token123");
262 /// ```
263 pub fn header<K, V>(mut self, key: K, value: V) -> Self
264 where
265 K: hyper::header::IntoHeaderName,
266 V: TryInto<hyper::header::HeaderValue>,
267 V::Error: std::fmt::Display,
268 {
269 match value.try_into() {
270 Ok(val) => {
271 self.headers.insert(key, val);
272 }
273 Err(e) => {
274 self.header_error = Some(format!("Invalid header value: {}", e));
275 }
276 }
277 self
278 }
279
280 /// Set the request body.
281 ///
282 /// # Examples
283 ///
284 /// ```
285 /// use reinhardt_http::Request;
286 /// use hyper::Method;
287 /// use bytes::Bytes;
288 ///
289 /// let body = Bytes::from(r#"{"name":"Alice"}"#);
290 /// let request = Request::builder()
291 /// .method(Method::POST)
292 /// .uri("/api/users")
293 /// .body(body.clone())
294 /// .build()
295 /// .unwrap();
296 ///
297 /// assert_eq!(request.body(), &body);
298 /// ```
299 pub fn body(mut self, body: Bytes) -> Self {
300 self.body = body;
301 self
302 }
303
304 /// Set whether the request is secure (HTTPS).
305 ///
306 /// Defaults to `false` if not specified.
307 ///
308 /// # Examples
309 ///
310 /// ```
311 /// use reinhardt_http::Request;
312 /// use hyper::Method;
313 ///
314 /// let request = Request::builder()
315 /// .method(Method::GET)
316 /// .uri("/")
317 /// .secure(true)
318 /// .build()
319 /// .unwrap();
320 ///
321 /// assert!(request.is_secure());
322 /// assert_eq!(request.scheme(), "https");
323 /// ```
324 pub fn secure(mut self, is_secure: bool) -> Self {
325 self.is_secure = is_secure;
326 self
327 }
328
329 /// Set the remote address of the client.
330 ///
331 /// # Examples
332 ///
333 /// ```
334 /// use reinhardt_http::Request;
335 /// use hyper::Method;
336 /// use std::net::{SocketAddr, IpAddr, Ipv4Addr};
337 ///
338 /// let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
339 /// let request = Request::builder()
340 /// .method(Method::GET)
341 /// .uri("/")
342 /// .remote_addr(addr)
343 /// .build()
344 /// .unwrap();
345 ///
346 /// assert_eq!(request.remote_addr, Some(addr));
347 /// ```
348 pub fn remote_addr(mut self, addr: SocketAddr) -> Self {
349 self.remote_addr = Some(addr);
350 self
351 }
352
353 /// Add a parser to the request.
354 ///
355 /// Parsers are used to parse the request body into specific formats.
356 /// The parser will be boxed internally.
357 ///
358 /// # Examples
359 ///
360 /// ```ignore
361 /// use reinhardt_http::Request;
362 /// use hyper::Method;
363 ///
364 /// let request = Request::builder()
365 /// .method(Method::POST)
366 /// .uri("/api/users")
367 /// .parser(JsonParser::new())
368 /// .build()
369 /// .unwrap();
370 /// ```
371 #[cfg(feature = "parsers")]
372 pub fn parser<P: Parser + 'static>(mut self, parser: P) -> Self {
373 self.parsers.push(Box::new(parser));
374 self
375 }
376
377 /// Set path parameters (used for testing views without router).
378 ///
379 /// This is primarily useful in test environments where you need to simulate
380 /// path parameters that would normally be extracted by the router.
381 ///
382 /// # Examples
383 ///
384 /// ```
385 /// use reinhardt_http::Request;
386 /// use hyper::Method;
387 /// use std::collections::HashMap;
388 ///
389 /// let mut params = HashMap::new();
390 /// params.insert("id".to_string(), "42".to_string());
391 ///
392 /// let request = Request::builder()
393 /// .method(Method::GET)
394 /// .uri("/api/users/42")
395 /// .path_params(params)
396 /// .build()
397 /// .unwrap();
398 ///
399 /// assert_eq!(request.path_params.get("id"), Some(&"42".to_string()));
400 /// ```
401 pub fn path_params(mut self, params: HashMap<String, String>) -> Self {
402 self.path_params = params;
403 self
404 }
405
406 /// Build the final `Request` instance.
407 ///
408 /// Returns an error if the URI is missing.
409 ///
410 /// # Examples
411 ///
412 /// ```
413 /// use reinhardt_http::Request;
414 /// use hyper::Method;
415 ///
416 /// let request = Request::builder()
417 /// .method(Method::GET)
418 /// .uri("/api/users")
419 /// .build()
420 /// .unwrap();
421 ///
422 /// assert_eq!(request.method, Method::GET);
423 /// assert_eq!(request.path(), "/api/users");
424 /// ```
425 pub fn build(self) -> Result<Request, String> {
426 // Report captured errors from builder methods
427 if let Some(err) = self.uri_error {
428 return Err(err);
429 }
430 if let Some(err) = self.header_error {
431 return Err(err);
432 }
433 let uri = self.uri.ok_or_else(|| "URI is required".to_string())?;
434 let query_params = Request::parse_query_params(&uri);
435
436 Ok(Request {
437 method: self.method,
438 uri,
439 version: self.version,
440 headers: self.headers,
441 body: self.body,
442 path_params: self.path_params,
443 query_params,
444 is_secure: self.is_secure,
445 remote_addr: self.remote_addr,
446 #[cfg(feature = "parsers")]
447 parsers: self.parsers,
448 #[cfg(feature = "parsers")]
449 parsed_data: Arc::new(Mutex::new(None)),
450 body_consumed: Arc::new(AtomicBool::new(false)),
451 extensions: Extensions::new(),
452 })
453 }
454}
455
456impl Request {
457 /// Create a new `RequestBuilder`.
458 ///
459 /// # Examples
460 ///
461 /// ```
462 /// use reinhardt_http::Request;
463 /// use hyper::Method;
464 ///
465 /// let request = Request::builder()
466 /// .method(Method::GET)
467 /// .uri("/api/users")
468 /// .build()
469 /// .unwrap();
470 ///
471 /// assert_eq!(request.method, Method::GET);
472 /// ```
473 pub fn builder() -> RequestBuilder {
474 RequestBuilder::default()
475 }
476
477 /// Set the DI context for this request (used by routers with dependency injection)
478 ///
479 /// This method stores the DI context in the request's extensions,
480 /// allowing handlers to access dependency injection services.
481 ///
482 /// The context will be wrapped in an Arc internally for efficient sharing.
483 /// The DI context type is generic to avoid circular dependencies.
484 ///
485 /// # Examples
486 ///
487 /// ```rust,no_run
488 /// use reinhardt_http::Request;
489 /// use hyper::Method;
490 ///
491 /// # struct DummyDiContext;
492 /// let mut request = Request::builder()
493 /// .method(Method::GET)
494 /// .uri("/")
495 /// .build()
496 /// .unwrap();
497 ///
498 /// let di_ctx = DummyDiContext;
499 /// request.set_di_context(di_ctx);
500 /// ```
501 pub fn set_di_context<T: Send + Sync + 'static>(&mut self, ctx: T) {
502 self.extensions.insert(Arc::new(ctx));
503 }
504
505 /// Get the DI context from this request
506 ///
507 /// Returns `None` if no DI context was set.
508 ///
509 /// The DI context type is generic to avoid circular dependencies.
510 /// Returns a reference to the context.
511 ///
512 /// # Examples
513 ///
514 /// ```rust,no_run
515 /// use reinhardt_http::Request;
516 /// use hyper::Method;
517 ///
518 /// # struct DummyDiContext;
519 /// let mut request = Request::builder()
520 /// .method(Method::GET)
521 /// .uri("/")
522 /// .build()
523 /// .unwrap();
524 ///
525 /// let di_ctx = DummyDiContext;
526 /// request.set_di_context(di_ctx);
527 ///
528 /// let ctx = request.get_di_context::<DummyDiContext>();
529 /// assert!(ctx.is_some());
530 /// ```
531 pub fn get_di_context<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
532 self.extensions.get::<Arc<T>>()
533 }
534
535 /// Extract Bearer token from Authorization header
536 ///
537 /// Extracts JWT or other bearer tokens from the Authorization header.
538 /// Returns `None` if the header is missing or not in "Bearer `<token>`" format.
539 ///
540 /// # Examples
541 ///
542 /// ```
543 /// use reinhardt_http::Request;
544 /// use hyper::{Method, Version, HeaderMap, header};
545 /// use bytes::Bytes;
546 ///
547 /// let mut headers = HeaderMap::new();
548 /// headers.insert(
549 /// header::AUTHORIZATION,
550 /// "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9".parse().unwrap()
551 /// );
552 ///
553 /// let request = Request::builder()
554 /// .method(Method::GET)
555 /// .uri("/")
556 /// .version(Version::HTTP_11)
557 /// .headers(headers)
558 /// .body(Bytes::new())
559 /// .build()
560 /// .unwrap();
561 ///
562 /// let token = request.extract_bearer_token();
563 /// assert_eq!(token, Some("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9".to_string()));
564 /// ```
565 ///
566 /// # Missing or invalid header
567 ///
568 /// ```
569 /// use reinhardt_http::Request;
570 /// use hyper::{Method, Version, HeaderMap};
571 /// use bytes::Bytes;
572 ///
573 /// let request = Request::builder()
574 /// .method(Method::GET)
575 /// .uri("/")
576 /// .version(Version::HTTP_11)
577 /// .headers(HeaderMap::new())
578 /// .body(Bytes::new())
579 /// .build()
580 /// .unwrap();
581 ///
582 /// let token = request.extract_bearer_token();
583 /// assert_eq!(token, None);
584 /// ```
585 pub fn extract_bearer_token(&self) -> Option<String> {
586 self.headers
587 .get(hyper::header::AUTHORIZATION)
588 .and_then(|value| value.to_str().ok())
589 .and_then(|auth_str| auth_str.strip_prefix("Bearer ").map(|s| s.to_string()))
590 }
591
592 /// Get a specific header value from the request
593 ///
594 /// Returns `None` if the header is missing or cannot be converted to a string.
595 ///
596 /// # Examples
597 ///
598 /// ```
599 /// use reinhardt_http::Request;
600 /// use hyper::{Method, Version, HeaderMap, header};
601 /// use bytes::Bytes;
602 ///
603 /// let mut headers = HeaderMap::new();
604 /// headers.insert(
605 /// header::USER_AGENT,
606 /// "Mozilla/5.0".parse().unwrap()
607 /// );
608 ///
609 /// let request = Request::builder()
610 /// .method(Method::GET)
611 /// .uri("/")
612 /// .version(Version::HTTP_11)
613 /// .headers(headers)
614 /// .body(Bytes::new())
615 /// .build()
616 /// .unwrap();
617 ///
618 /// let user_agent = request.get_header("user-agent");
619 /// assert_eq!(user_agent, Some("Mozilla/5.0".to_string()));
620 /// ```
621 ///
622 /// # Missing header
623 ///
624 /// ```
625 /// use reinhardt_http::Request;
626 /// use hyper::{Method, Version, HeaderMap};
627 /// use bytes::Bytes;
628 ///
629 /// let request = Request::builder()
630 /// .method(Method::GET)
631 /// .uri("/")
632 /// .version(Version::HTTP_11)
633 /// .headers(HeaderMap::new())
634 /// .body(Bytes::new())
635 /// .build()
636 /// .unwrap();
637 ///
638 /// let header = request.get_header("x-custom-header");
639 /// assert_eq!(header, None);
640 /// ```
641 pub fn get_header(&self, name: &str) -> Option<String> {
642 self.headers
643 .get(name)
644 .and_then(|value| value.to_str().ok())
645 .map(|s| s.to_string())
646 }
647
648 /// Extract client IP address from the request
649 ///
650 /// Only trusts proxy headers (X-Forwarded-For, X-Real-IP) when the request
651 /// originates from a configured trusted proxy. Without trusted proxies,
652 /// falls back to the actual connection address.
653 ///
654 /// # Examples
655 ///
656 /// ```
657 /// use reinhardt_http::{Request, TrustedProxies};
658 /// use hyper::{Method, Version, HeaderMap, header};
659 /// use bytes::Bytes;
660 /// use std::net::{SocketAddr, IpAddr, Ipv4Addr};
661 ///
662 /// let proxy_ip = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
663 /// let mut headers = HeaderMap::new();
664 /// headers.insert(
665 /// header::HeaderName::from_static("x-forwarded-for"),
666 /// "203.0.113.1, 198.51.100.1".parse().unwrap()
667 /// );
668 ///
669 /// let request = Request::builder()
670 /// .method(Method::GET)
671 /// .uri("/")
672 /// .version(Version::HTTP_11)
673 /// .headers(headers)
674 /// .remote_addr(SocketAddr::new(proxy_ip, 8080))
675 /// .body(Bytes::new())
676 /// .build()
677 /// .unwrap();
678 ///
679 /// // Configure trusted proxies to honor X-Forwarded-For
680 /// request.set_trusted_proxies(TrustedProxies::new(vec![proxy_ip]));
681 ///
682 /// let ip = request.get_client_ip();
683 /// assert_eq!(ip, Some("203.0.113.1".parse().unwrap()));
684 /// ```
685 ///
686 /// # No trusted proxy, fallback to remote_addr
687 ///
688 /// ```
689 /// use reinhardt_http::Request;
690 /// use hyper::{Method, Version, HeaderMap};
691 /// use bytes::Bytes;
692 /// use std::net::{SocketAddr, IpAddr, Ipv4Addr};
693 ///
694 /// let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
695 /// let request = Request::builder()
696 /// .method(Method::GET)
697 /// .uri("/")
698 /// .version(Version::HTTP_11)
699 /// .headers(HeaderMap::new())
700 /// .remote_addr(addr)
701 /// .body(Bytes::new())
702 /// .build()
703 /// .unwrap();
704 ///
705 /// let ip = request.get_client_ip();
706 /// assert_eq!(ip, Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))));
707 /// ```
708 pub fn get_client_ip(&self) -> Option<std::net::IpAddr> {
709 // Only trust proxy headers if the request comes from a configured trusted proxy
710 if self.is_from_trusted_proxy() {
711 // Try X-Forwarded-For header first (common in proxy setups)
712 if let Some(forwarded) = self.get_header("x-forwarded-for") {
713 // X-Forwarded-For can contain multiple IPs, take the first one
714 if let Some(first_ip) = forwarded.split(',').next()
715 && let Ok(ip) = first_ip.trim().parse()
716 {
717 return Some(ip);
718 }
719 }
720
721 // Try X-Real-IP header
722 if let Some(real_ip) = self.get_header("x-real-ip")
723 && let Ok(ip) = real_ip.parse()
724 {
725 return Some(ip);
726 }
727 }
728
729 // Fallback to remote_addr (actual connection info)
730 self.remote_addr.map(|addr| addr.ip())
731 }
732
733 /// Check if the request originates from a trusted proxy.
734 ///
735 /// Returns `true` only if trusted proxies are configured AND the
736 /// remote address is in the trusted set.
737 fn is_from_trusted_proxy(&self) -> bool {
738 if let Some(trusted) = self.extensions.get::<TrustedProxies>()
739 && let Some(addr) = self.remote_addr
740 {
741 return trusted.is_trusted(&addr.ip());
742 }
743 false
744 }
745
746 /// Set trusted proxy configuration for this request.
747 ///
748 /// This is typically called by the server/middleware layer to configure
749 /// which proxy IPs are trusted for header forwarding.
750 pub fn set_trusted_proxies(&self, proxies: TrustedProxies) {
751 self.extensions.insert(proxies);
752 }
753
754 /// Validate Content-Type header
755 ///
756 /// Checks if the Content-Type header matches the expected value.
757 /// Returns an error if the header is missing or doesn't match.
758 ///
759 /// # Examples
760 ///
761 /// ```
762 /// use reinhardt_http::Request;
763 /// use hyper::{Method, Version, HeaderMap, header};
764 /// use bytes::Bytes;
765 ///
766 /// let mut headers = HeaderMap::new();
767 /// headers.insert(
768 /// header::CONTENT_TYPE,
769 /// "application/json".parse().unwrap()
770 /// );
771 ///
772 /// let request = Request::builder()
773 /// .method(Method::POST)
774 /// .uri("/")
775 /// .version(Version::HTTP_11)
776 /// .headers(headers)
777 /// .body(Bytes::new())
778 /// .build()
779 /// .unwrap();
780 ///
781 /// assert!(request.validate_content_type("application/json").is_ok());
782 /// ```
783 ///
784 /// # Content-Type mismatch
785 ///
786 /// ```
787 /// use reinhardt_http::Request;
788 /// use hyper::{Method, Version, HeaderMap, header};
789 /// use bytes::Bytes;
790 ///
791 /// let mut headers = HeaderMap::new();
792 /// headers.insert(
793 /// header::CONTENT_TYPE,
794 /// "text/plain".parse().unwrap()
795 /// );
796 ///
797 /// let request = Request::builder()
798 /// .method(Method::POST)
799 /// .uri("/")
800 /// .version(Version::HTTP_11)
801 /// .headers(headers)
802 /// .body(Bytes::new())
803 /// .build()
804 /// .unwrap();
805 ///
806 /// let result = request.validate_content_type("application/json");
807 /// assert!(result.is_err());
808 /// ```
809 ///
810 /// # Missing Content-Type header
811 ///
812 /// ```
813 /// use reinhardt_http::Request;
814 /// use hyper::{Method, Version, HeaderMap};
815 /// use bytes::Bytes;
816 ///
817 /// let request = Request::builder()
818 /// .method(Method::POST)
819 /// .uri("/")
820 /// .version(Version::HTTP_11)
821 /// .headers(HeaderMap::new())
822 /// .body(Bytes::new())
823 /// .build()
824 /// .unwrap();
825 ///
826 /// let result = request.validate_content_type("application/json");
827 /// assert!(result.is_err());
828 /// ```
829 pub fn validate_content_type(&self, expected: &str) -> crate::Result<()> {
830 match self.get_header("content-type") {
831 Some(content_type) if content_type.starts_with(expected) => Ok(()),
832 Some(content_type) => Err(crate::Error::Http(format!(
833 "Invalid Content-Type: expected '{}', got '{}'",
834 expected, content_type
835 ))),
836 None => Err(crate::Error::Http(
837 "Missing Content-Type header".to_string(),
838 )),
839 }
840 }
841
842 /// Parse query parameters into typed struct
843 ///
844 /// Deserializes query string parameters into the specified type `T`.
845 /// Returns an error if deserialization fails.
846 ///
847 /// # Examples
848 ///
849 /// ```
850 /// use reinhardt_http::Request;
851 /// use hyper::{Method, Version, HeaderMap};
852 /// use bytes::Bytes;
853 /// use serde::Deserialize;
854 ///
855 /// #[derive(Deserialize, Debug, PartialEq)]
856 /// struct Pagination {
857 /// page: u32,
858 /// limit: u32,
859 /// }
860 ///
861 /// let request = Request::builder()
862 /// .method(Method::GET)
863 /// .uri("/api/users?page=2&limit=10")
864 /// .version(Version::HTTP_11)
865 /// .headers(HeaderMap::new())
866 /// .body(Bytes::new())
867 /// .build()
868 /// .unwrap();
869 ///
870 /// let params: Pagination = request.query_as().unwrap();
871 /// assert_eq!(params, Pagination { page: 2, limit: 10 });
872 /// ```
873 ///
874 /// # Type mismatch error
875 ///
876 /// ```
877 /// use reinhardt_http::Request;
878 /// use hyper::{Method, Version, HeaderMap};
879 /// use bytes::Bytes;
880 /// use serde::Deserialize;
881 ///
882 /// #[derive(Deserialize)]
883 /// struct Pagination {
884 /// page: u32,
885 /// limit: u32,
886 /// }
887 ///
888 /// let request = Request::builder()
889 /// .method(Method::GET)
890 /// .uri("/api/users?page=invalid")
891 /// .version(Version::HTTP_11)
892 /// .headers(HeaderMap::new())
893 /// .body(Bytes::new())
894 /// .build()
895 /// .unwrap();
896 ///
897 /// let result: Result<Pagination, _> = request.query_as();
898 /// assert!(result.is_err());
899 /// ```
900 pub fn query_as<T: serde::de::DeserializeOwned>(&self) -> crate::Result<T> {
901 // Convert HashMap<String, String> to Vec<(String, String)> for serde_urlencoded
902 let params: Vec<(String, String)> = self
903 .query_params
904 .iter()
905 .map(|(k, v)| (k.clone(), v.clone()))
906 .collect();
907
908 let encoded = serde_urlencoded::to_string(¶ms)
909 .map_err(|e| crate::Error::Http(format!("Failed to encode query parameters: {}", e)))?;
910 serde_urlencoded::from_str(&encoded)
911 .map_err(|e| crate::Error::Http(format!("Failed to parse query parameters: {}", e)))
912 }
913}
914
915#[cfg(test)]
916mod tests {
917 use super::*;
918 use bytes::Bytes;
919 use hyper::{HeaderMap, Method, Version, header};
920 use rstest::rstest;
921
922 #[rstest]
923 fn test_extract_bearer_token() {
924 let mut headers = HeaderMap::new();
925 headers.insert(
926 header::AUTHORIZATION,
927 "Bearer test_token_123".parse().unwrap(),
928 );
929
930 let request = Request::builder()
931 .method(Method::GET)
932 .uri("/")
933 .version(Version::HTTP_11)
934 .headers(headers)
935 .body(Bytes::new())
936 .build()
937 .unwrap();
938
939 let token = request.extract_bearer_token();
940 assert_eq!(token, Some("test_token_123".to_string()));
941 }
942
943 #[rstest]
944 fn test_extract_bearer_token_missing() {
945 let request = Request::builder()
946 .method(Method::GET)
947 .uri("/")
948 .version(Version::HTTP_11)
949 .headers(HeaderMap::new())
950 .body(Bytes::new())
951 .build()
952 .unwrap();
953
954 let token = request.extract_bearer_token();
955 assert_eq!(token, None);
956 }
957
958 #[rstest]
959 fn test_get_header() {
960 let mut headers = HeaderMap::new();
961 headers.insert(header::USER_AGENT, "TestClient/1.0".parse().unwrap());
962
963 let request = Request::builder()
964 .method(Method::GET)
965 .uri("/")
966 .version(Version::HTTP_11)
967 .headers(headers)
968 .body(Bytes::new())
969 .build()
970 .unwrap();
971
972 let user_agent = request.get_header("user-agent");
973 assert_eq!(user_agent, Some("TestClient/1.0".to_string()));
974 }
975
976 #[rstest]
977 fn test_get_header_missing() {
978 let request = Request::builder()
979 .method(Method::GET)
980 .uri("/")
981 .version(Version::HTTP_11)
982 .headers(HeaderMap::new())
983 .body(Bytes::new())
984 .build()
985 .unwrap();
986
987 let header = request.get_header("x-custom-header");
988 assert_eq!(header, None);
989 }
990
991 #[rstest]
992 fn test_get_client_ip_forwarded_for_with_trusted_proxy() {
993 // Arrange
994 let proxy_ip: std::net::IpAddr = "10.0.0.254".parse().unwrap();
995 let mut headers = HeaderMap::new();
996 headers.insert(
997 header::HeaderName::from_static("x-forwarded-for"),
998 "192.168.1.1, 10.0.0.1".parse().unwrap(),
999 );
1000
1001 let request = Request::builder()
1002 .method(Method::GET)
1003 .uri("/")
1004 .version(Version::HTTP_11)
1005 .headers(headers)
1006 .body(Bytes::new())
1007 .remote_addr(std::net::SocketAddr::new(proxy_ip, 8080))
1008 .build()
1009 .unwrap();
1010
1011 // Configure trusted proxies
1012 request.set_trusted_proxies(TrustedProxies::new(vec![proxy_ip]));
1013
1014 // Act & Assert
1015 let ip = request.get_client_ip();
1016 assert_eq!(ip, Some("192.168.1.1".parse().unwrap()));
1017 }
1018
1019 #[rstest]
1020 fn test_get_client_ip_forwarded_for_without_trusted_proxy() {
1021 // Arrange - proxy headers present but no trusted proxy configured
1022 let mut headers = HeaderMap::new();
1023 headers.insert(
1024 header::HeaderName::from_static("x-forwarded-for"),
1025 "192.168.1.1, 10.0.0.1".parse().unwrap(),
1026 );
1027
1028 let remote_ip: std::net::IpAddr = "10.0.0.254".parse().unwrap();
1029 let request = Request::builder()
1030 .method(Method::GET)
1031 .uri("/")
1032 .version(Version::HTTP_11)
1033 .headers(headers)
1034 .body(Bytes::new())
1035 .remote_addr(std::net::SocketAddr::new(remote_ip, 8080))
1036 .build()
1037 .unwrap();
1038
1039 // Act - no trusted proxies, should use remote_addr
1040 let ip = request.get_client_ip();
1041 assert_eq!(ip, Some(remote_ip));
1042 }
1043
1044 #[rstest]
1045 fn test_get_client_ip_real_ip_with_trusted_proxy() {
1046 // Arrange
1047 let proxy_ip: std::net::IpAddr = "10.0.0.254".parse().unwrap();
1048 let mut headers = HeaderMap::new();
1049 headers.insert(
1050 header::HeaderName::from_static("x-real-ip"),
1051 "203.0.113.5".parse().unwrap(),
1052 );
1053
1054 let request = Request::builder()
1055 .method(Method::GET)
1056 .uri("/")
1057 .version(Version::HTTP_11)
1058 .headers(headers)
1059 .body(Bytes::new())
1060 .remote_addr(std::net::SocketAddr::new(proxy_ip, 8080))
1061 .build()
1062 .unwrap();
1063
1064 request.set_trusted_proxies(TrustedProxies::new(vec![proxy_ip]));
1065
1066 // Act & Assert
1067 let ip = request.get_client_ip();
1068 assert_eq!(ip, Some("203.0.113.5".parse().unwrap()));
1069 }
1070
1071 #[rstest]
1072 fn test_get_client_ip_none() {
1073 let request = Request::builder()
1074 .method(Method::GET)
1075 .uri("/")
1076 .version(Version::HTTP_11)
1077 .headers(HeaderMap::new())
1078 .body(Bytes::new())
1079 .build()
1080 .unwrap();
1081
1082 let ip = request.get_client_ip();
1083 assert_eq!(ip, None);
1084 }
1085
1086 #[rstest]
1087 fn test_validate_content_type_valid() {
1088 let mut headers = HeaderMap::new();
1089 headers.insert(header::CONTENT_TYPE, "application/json".parse().unwrap());
1090
1091 let request = Request::builder()
1092 .method(Method::POST)
1093 .uri("/")
1094 .version(Version::HTTP_11)
1095 .headers(headers)
1096 .body(Bytes::new())
1097 .build()
1098 .unwrap();
1099
1100 assert!(request.validate_content_type("application/json").is_ok());
1101 }
1102
1103 #[rstest]
1104 fn test_validate_content_type_invalid() {
1105 let mut headers = HeaderMap::new();
1106 headers.insert(header::CONTENT_TYPE, "text/plain".parse().unwrap());
1107
1108 let request = Request::builder()
1109 .method(Method::POST)
1110 .uri("/")
1111 .version(Version::HTTP_11)
1112 .headers(headers)
1113 .body(Bytes::new())
1114 .build()
1115 .unwrap();
1116
1117 assert!(request.validate_content_type("application/json").is_err());
1118 }
1119
1120 #[rstest]
1121 fn test_validate_content_type_missing() {
1122 let request = Request::builder()
1123 .method(Method::POST)
1124 .uri("/")
1125 .version(Version::HTTP_11)
1126 .headers(HeaderMap::new())
1127 .body(Bytes::new())
1128 .build()
1129 .unwrap();
1130
1131 assert!(request.validate_content_type("application/json").is_err());
1132 }
1133}