Skip to main content

reinhardt_http/
request.rs

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