Skip to main content

rustapi_core/
extract.rs

1//! Extractors for RustAPI
2//!
3//! Extractors automatically parse and validate data from incoming HTTP requests.
4//! They implement the [`FromRequest`] or [`FromRequestParts`] traits and can be
5//! used as handler function parameters.
6//!
7//! # Available Extractors
8//!
9//! | Extractor | Description | Consumes Body |
10//! |-----------|-------------|---------------|
11//! | [`Json<T>`] | Parse JSON request body | Yes |
12//! | [`ValidatedJson<T>`] | Parse and validate JSON body | Yes |
13//! | [`Query<T>`] | Parse query string parameters | No |
14//! | [`Path<T>`] | Extract path parameters | No |
15//! | [`State<T>`] | Access shared application state | No |
16//! | [`Body`] | Raw request body bytes | Yes |
17//! | [`Headers`] | Access all request headers | No |
18//! | [`HeaderValue`] | Extract a specific header | No |
19//! | [`Extension<T>`] | Access middleware-injected data | No |
20//! | [`ClientIp`] | Extract client IP address | No |
21//! | [`Cookies`] | Parse request cookies (requires `cookies` feature) | No |
22//!
23//! # Example
24//!
25//! ```rust,ignore
26//! use rustapi_core::{Json, Query, Path, State};
27//! use serde::{Deserialize, Serialize};
28//!
29//! #[derive(Deserialize)]
30//! struct CreateUser {
31//!     name: String,
32//!     email: String,
33//! }
34//!
35//! #[derive(Deserialize)]
36//! struct Pagination {
37//!     page: Option<u32>,
38//!     limit: Option<u32>,
39//! }
40//!
41//! // Multiple extractors can be combined
42//! async fn create_user(
43//!     State(db): State<DbPool>,
44//!     Query(pagination): Query<Pagination>,
45//!     Json(body): Json<CreateUser>,
46//! ) -> impl IntoResponse {
47//!     // Use db, pagination, and body...
48//! }
49//! ```
50//!
51//! # Extractor Order
52//!
53//! When using multiple extractors, body-consuming extractors (like `Json` or `Body`)
54//! must come last since they consume the request body. Non-body extractors can be
55//! in any order.
56
57use crate::error::{ApiError, Result};
58use crate::json;
59use crate::request::Request;
60use crate::response::IntoResponse;
61use crate::stream::{StreamingBody, StreamingConfig};
62use crate::validation::Validatable;
63use bytes::Bytes;
64use http::{header, StatusCode};
65use rustapi_validate::v2::{AsyncValidate, ValidationContext};
66
67use rustapi_openapi::schema::{RustApiSchema, SchemaCtx, SchemaRef};
68use serde::de::DeserializeOwned;
69use serde::Serialize;
70use std::collections::BTreeMap;
71use std::future::Future;
72use std::ops::{Deref, DerefMut};
73use std::str::FromStr;
74
75/// Trait for extracting data from request parts (headers, path, query)
76///
77/// This is used for extractors that don't need the request body.
78///
79/// # Example: Implementing a custom extractor that requires a specific header
80///
81/// ```rust
82/// use rustapi_core::FromRequestParts;
83/// use rustapi_core::{Request, ApiError, Result};
84/// use http::StatusCode;
85///
86/// struct ApiKey(String);
87///
88/// impl FromRequestParts for ApiKey {
89///     fn from_request_parts(req: &Request) -> Result<Self> {
90///         if let Some(key) = req.headers().get("x-api-key") {
91///             if let Ok(key_str) = key.to_str() {
92///                 return Ok(ApiKey(key_str.to_string()));
93///             }
94///         }
95///         Err(ApiError::unauthorized("Missing or invalid API key"))
96///     }
97/// }
98/// ```
99pub trait FromRequestParts: Sized {
100    /// Extract from request parts
101    fn from_request_parts(req: &Request) -> Result<Self>;
102}
103
104/// Trait for extracting data from the full request (including body)
105///
106/// This is used for extractors that consume the request body.
107///
108/// # Example: Implementing a custom extractor that consumes the body
109///
110/// ```rust
111/// use rustapi_core::FromRequest;
112/// use rustapi_core::{Request, ApiError, Result};
113/// use std::future::Future;
114///
115/// struct PlainText(String);
116///
117/// impl FromRequest for PlainText {
118///     async fn from_request(req: &mut Request) -> Result<Self> {
119///         // Ensure body is loaded
120///         req.load_body().await?;
121///         
122///         // Consume the body
123///         if let Some(bytes) = req.take_body() {
124///             if let Ok(text) = String::from_utf8(bytes.to_vec()) {
125///                 return Ok(PlainText(text));
126///             }
127///         }
128///         
129///         Err(ApiError::bad_request("Invalid plain text body"))
130///     }
131/// }
132/// ```
133pub trait FromRequest: Sized {
134    /// Extract from the full request
135    fn from_request(req: &mut Request) -> impl Future<Output = Result<Self>> + Send;
136}
137
138// Blanket impl: FromRequestParts -> FromRequest
139impl<T: FromRequestParts> FromRequest for T {
140    async fn from_request(req: &mut Request) -> Result<Self> {
141        T::from_request_parts(req)
142    }
143}
144
145/// JSON body extractor
146///
147/// Parses the request body as JSON and deserializes into type `T`.
148/// Also works as a response type when T: Serialize.
149///
150/// # Example
151///
152/// ```rust,ignore
153/// #[derive(Deserialize)]
154/// struct CreateUser {
155///     name: String,
156///     email: String,
157/// }
158///
159/// async fn create_user(Json(body): Json<CreateUser>) -> impl IntoResponse {
160///     // body is already deserialized
161/// }
162/// ```
163#[derive(Debug, Clone, Copy, Default)]
164pub struct Json<T>(pub T);
165
166impl<T: DeserializeOwned + Send> FromRequest for Json<T> {
167    async fn from_request(req: &mut Request) -> Result<Self> {
168        req.load_body().await?;
169        let body = req
170            .take_body()
171            .ok_or_else(|| ApiError::internal("Body already consumed"))?;
172
173        // Use simd-json accelerated parsing when available (2-4x faster)
174        let value: T = json::from_slice(&body)?;
175        Ok(Json(value))
176    }
177}
178
179impl<T> Deref for Json<T> {
180    type Target = T;
181
182    fn deref(&self) -> &Self::Target {
183        &self.0
184    }
185}
186
187impl<T> DerefMut for Json<T> {
188    fn deref_mut(&mut self) -> &mut Self::Target {
189        &mut self.0
190    }
191}
192
193impl<T> From<T> for Json<T> {
194    fn from(value: T) -> Self {
195        Json(value)
196    }
197}
198
199/// Default pre-allocation size for JSON response buffers (256 bytes)
200/// This covers most small to medium JSON responses without reallocation.
201const JSON_RESPONSE_INITIAL_CAPACITY: usize = 256;
202
203// IntoResponse for Json - allows using Json<T> as a return type
204impl<T: Serialize> IntoResponse for Json<T> {
205    fn into_response(self) -> crate::response::Response {
206        // Use pre-allocated buffer to reduce allocations
207        match json::to_vec_with_capacity(&self.0, JSON_RESPONSE_INITIAL_CAPACITY) {
208            Ok(body) => http::Response::builder()
209                .status(StatusCode::OK)
210                .header(header::CONTENT_TYPE, "application/json")
211                .body(crate::response::Body::from(body))
212                .unwrap(),
213            Err(err) => {
214                ApiError::internal(format!("Failed to serialize response: {}", err)).into_response()
215            }
216        }
217    }
218}
219
220/// Validated JSON body extractor
221///
222/// Parses the request body as JSON, deserializes into type `T`, and validates
223/// using the `Validate` trait. Returns a 422 Unprocessable Entity error with
224/// detailed field-level validation errors if validation fails.
225///
226/// # Example
227///
228/// ```rust,ignore
229/// use rustapi_rs::prelude::*;
230/// use validator::Validate;
231///
232/// #[derive(Deserialize, Validate)]
233/// struct CreateUser {
234///     #[validate(email)]
235///     email: String,
236///     #[validate(length(min = 8))]
237///     password: String,
238/// }
239///
240/// async fn register(ValidatedJson(body): ValidatedJson<CreateUser>) -> impl IntoResponse {
241///     // body is already validated!
242///     // If email is invalid or password too short, a 422 error is returned automatically
243/// }
244/// ```
245#[derive(Debug, Clone, Copy, Default)]
246pub struct ValidatedJson<T>(pub T);
247
248impl<T> ValidatedJson<T> {
249    /// Create a new ValidatedJson wrapper
250    pub fn new(value: T) -> Self {
251        Self(value)
252    }
253
254    /// Get the inner value
255    pub fn into_inner(self) -> T {
256        self.0
257    }
258}
259
260impl<T: DeserializeOwned + Validatable + Send> FromRequest for ValidatedJson<T> {
261    async fn from_request(req: &mut Request) -> Result<Self> {
262        req.load_body().await?;
263        // First, deserialize the JSON body using simd-json when available
264        let body = req
265            .take_body()
266            .ok_or_else(|| ApiError::internal("Body already consumed"))?;
267
268        let value: T = json::from_slice(&body)?;
269
270        // Then, validate it using the unified Validatable trait
271        value.do_validate()?;
272
273        Ok(ValidatedJson(value))
274    }
275}
276
277impl<T> Deref for ValidatedJson<T> {
278    type Target = T;
279
280    fn deref(&self) -> &Self::Target {
281        &self.0
282    }
283}
284
285impl<T> DerefMut for ValidatedJson<T> {
286    fn deref_mut(&mut self) -> &mut Self::Target {
287        &mut self.0
288    }
289}
290
291impl<T> From<T> for ValidatedJson<T> {
292    fn from(value: T) -> Self {
293        ValidatedJson(value)
294    }
295}
296
297impl<T: Serialize> IntoResponse for ValidatedJson<T> {
298    fn into_response(self) -> crate::response::Response {
299        Json(self.0).into_response()
300    }
301}
302
303/// Async validated JSON body extractor
304///
305/// Parses the request body as JSON, deserializes into type `T`, and validates
306/// using the `AsyncValidate` trait from `rustapi-validate`.
307///
308/// This extractor supports async validation rules, such as database uniqueness checks.
309///
310/// # Example
311///
312/// ```rust,ignore
313/// use rustapi_rs::prelude::*;
314/// use rustapi_validate::v2::prelude::*;
315///
316/// #[derive(Deserialize, Validate, AsyncValidate)]
317/// struct CreateUser {
318///     #[validate(email)]
319///     email: String,
320///     
321///     #[validate(async_unique(table = "users", column = "email"))]
322///     username: String,
323/// }
324///
325/// async fn register(AsyncValidatedJson(body): AsyncValidatedJson<CreateUser>) -> impl IntoResponse {
326///     // body is validated asynchronously (e.g. checked existing email in DB)
327/// }
328/// ```
329#[derive(Debug, Clone, Copy, Default)]
330pub struct AsyncValidatedJson<T>(pub T);
331
332impl<T> AsyncValidatedJson<T> {
333    /// Create a new AsyncValidatedJson wrapper
334    pub fn new(value: T) -> Self {
335        Self(value)
336    }
337
338    /// Get the inner value
339    pub fn into_inner(self) -> T {
340        self.0
341    }
342}
343
344impl<T> Deref for AsyncValidatedJson<T> {
345    type Target = T;
346
347    fn deref(&self) -> &Self::Target {
348        &self.0
349    }
350}
351
352impl<T> DerefMut for AsyncValidatedJson<T> {
353    fn deref_mut(&mut self) -> &mut Self::Target {
354        &mut self.0
355    }
356}
357
358impl<T> From<T> for AsyncValidatedJson<T> {
359    fn from(value: T) -> Self {
360        AsyncValidatedJson(value)
361    }
362}
363
364impl<T: Serialize> IntoResponse for AsyncValidatedJson<T> {
365    fn into_response(self) -> crate::response::Response {
366        Json(self.0).into_response()
367    }
368}
369
370impl<T: DeserializeOwned + AsyncValidate + Send + Sync> FromRequest for AsyncValidatedJson<T> {
371    async fn from_request(req: &mut Request) -> Result<Self> {
372        req.load_body().await?;
373
374        let body = req
375            .take_body()
376            .ok_or_else(|| ApiError::internal("Body already consumed"))?;
377
378        let value: T = json::from_slice(&body)?;
379
380        // Create validation context from request
381        // Check if validators are configured in App State
382        let ctx = if let Some(ctx) = req.state().get::<ValidationContext>() {
383            ctx.clone()
384        } else {
385            ValidationContext::default()
386        };
387
388        // Perform full validation (sync + async)
389        if let Err(errors) = value.validate_full(&ctx).await {
390            // Convert v2 ValidationErrors to ApiError
391            let field_errors: Vec<crate::error::FieldError> = errors
392                .fields
393                .iter()
394                .flat_map(|(field, errs)| {
395                    let field_name = field.to_string();
396                    errs.iter().map(move |e| crate::error::FieldError {
397                        field: field_name.clone(),
398                        code: e.code.to_string(),
399                        message: e.message.clone(),
400                    })
401                })
402                .collect();
403
404            return Err(ApiError::validation(field_errors));
405        }
406
407        Ok(AsyncValidatedJson(value))
408    }
409}
410
411/// Query string extractor
412///
413/// Parses the query string into type `T`.
414///
415/// # Example
416///
417/// ```rust,ignore
418/// #[derive(Deserialize)]
419/// struct Pagination {
420///     page: Option<u32>,
421///     limit: Option<u32>,
422/// }
423///
424/// async fn list_users(Query(params): Query<Pagination>) -> impl IntoResponse {
425///     // params.page, params.limit
426/// }
427/// ```
428#[derive(Debug, Clone)]
429pub struct Query<T>(pub T);
430
431impl<T: DeserializeOwned> FromRequestParts for Query<T> {
432    fn from_request_parts(req: &Request) -> Result<Self> {
433        let query = req.query_string().unwrap_or("");
434        let value: T = serde_urlencoded::from_str(query)
435            .map_err(|e| ApiError::bad_request(format!("Invalid query string: {}", e)))?;
436        Ok(Query(value))
437    }
438}
439
440impl<T> Deref for Query<T> {
441    type Target = T;
442
443    fn deref(&self) -> &Self::Target {
444        &self.0
445    }
446}
447
448/// Path parameter extractor
449///
450/// Extracts path parameters defined in the route pattern.
451///
452/// # Example
453///
454/// For route `/users/{id}`:
455///
456/// ```rust,ignore
457/// async fn get_user(Path(id): Path<i64>) -> impl IntoResponse {
458///     // id is extracted from path
459/// }
460/// ```
461///
462/// For multiple params `/users/{user_id}/posts/{post_id}`:
463///
464/// ```rust,ignore
465/// async fn get_post(Path((user_id, post_id)): Path<(i64, i64)>) -> impl IntoResponse {
466///     // Both params extracted
467/// }
468/// ```
469#[derive(Debug, Clone)]
470pub struct Path<T>(pub T);
471
472impl<T: FromStr> FromRequestParts for Path<T>
473where
474    T::Err: std::fmt::Display,
475{
476    fn from_request_parts(req: &Request) -> Result<Self> {
477        let params = req.path_params();
478
479        // For single param, get the first one
480        if let Some((_, value)) = params.iter().next() {
481            let parsed = value
482                .parse::<T>()
483                .map_err(|e| ApiError::bad_request(format!("Invalid path parameter: {}", e)))?;
484            return Ok(Path(parsed));
485        }
486
487        Err(ApiError::internal("Missing path parameter"))
488    }
489}
490
491impl<T> Deref for Path<T> {
492    type Target = T;
493
494    fn deref(&self) -> &Self::Target {
495        &self.0
496    }
497}
498
499/// Typed path extractor
500///
501/// Extracts path parameters and deserializes them into a struct implementing `Deserialize`.
502/// This is similar to `Path<T>`, but supports complex structs that can be deserialized
503/// from a map of parameter names to values (e.g. via `serde_json`).
504///
505/// # Example
506///
507/// ```rust,ignore
508/// #[derive(Deserialize)]
509/// struct UserParams {
510///     id: u64,
511///     category: String,
512/// }
513///
514/// async fn get_user(Typed(params): Typed<UserParams>) -> impl IntoResponse {
515///     // params.id, params.category
516/// }
517/// ```
518#[derive(Debug, Clone)]
519pub struct Typed<T>(pub T);
520
521impl<T: DeserializeOwned + Send> FromRequestParts for Typed<T> {
522    fn from_request_parts(req: &Request) -> Result<Self> {
523        let params = req.path_params();
524        let mut map = serde_json::Map::new();
525        for (k, v) in params.iter() {
526            map.insert(k.to_string(), serde_json::Value::String(v.to_string()));
527        }
528        let value = serde_json::Value::Object(map);
529        let parsed: T = serde_json::from_value(value)
530            .map_err(|e| ApiError::bad_request(format!("Invalid path parameters: {}", e)))?;
531        Ok(Typed(parsed))
532    }
533}
534
535impl<T> Deref for Typed<T> {
536    type Target = T;
537
538    fn deref(&self) -> &Self::Target {
539        &self.0
540    }
541}
542
543/// State extractor
544///
545/// Extracts shared application state.
546///
547/// # Example
548///
549/// ```rust,ignore
550/// #[derive(Clone)]
551/// struct AppState {
552///     db: DbPool,
553/// }
554///
555/// async fn handler(State(state): State<AppState>) -> impl IntoResponse {
556///     // Use state.db
557/// }
558/// ```
559#[derive(Debug, Clone)]
560pub struct State<T>(pub T);
561
562impl<T: Clone + Send + Sync + 'static> FromRequestParts for State<T> {
563    fn from_request_parts(req: &Request) -> Result<Self> {
564        req.state().get::<T>().cloned().map(State).ok_or_else(|| {
565            ApiError::internal(format!(
566                "State of type `{}` not found. Did you forget to call .state()?",
567                std::any::type_name::<T>()
568            ))
569        })
570    }
571}
572
573impl<T> Deref for State<T> {
574    type Target = T;
575
576    fn deref(&self) -> &Self::Target {
577        &self.0
578    }
579}
580
581/// Raw body bytes extractor
582#[derive(Debug, Clone)]
583pub struct Body(pub Bytes);
584
585impl FromRequest for Body {
586    async fn from_request(req: &mut Request) -> Result<Self> {
587        req.load_body().await?;
588        let body = req
589            .take_body()
590            .ok_or_else(|| ApiError::internal("Body already consumed"))?;
591        Ok(Body(body))
592    }
593}
594
595impl Deref for Body {
596    type Target = Bytes;
597
598    fn deref(&self) -> &Self::Target {
599        &self.0
600    }
601}
602
603/// Streaming body extractor
604pub struct BodyStream(pub StreamingBody);
605
606impl FromRequest for BodyStream {
607    async fn from_request(req: &mut Request) -> Result<Self> {
608        let config = StreamingConfig::default();
609
610        if let Some(stream) = req.take_stream() {
611            Ok(BodyStream(StreamingBody::new(stream, config.max_body_size)))
612        } else if let Some(bytes) = req.take_body() {
613            // Handle buffered body as stream
614            let stream = futures_util::stream::once(async move { Ok(bytes) });
615            Ok(BodyStream(StreamingBody::from_stream(
616                stream,
617                config.max_body_size,
618            )))
619        } else {
620            Err(ApiError::internal("Body already consumed"))
621        }
622    }
623}
624
625impl Deref for BodyStream {
626    type Target = StreamingBody;
627
628    fn deref(&self) -> &Self::Target {
629        &self.0
630    }
631}
632
633impl DerefMut for BodyStream {
634    fn deref_mut(&mut self) -> &mut Self::Target {
635        &mut self.0
636    }
637}
638
639// Forward stream implementation
640impl futures_util::Stream for BodyStream {
641    type Item = Result<Bytes, ApiError>;
642
643    fn poll_next(
644        mut self: std::pin::Pin<&mut Self>,
645        cx: &mut std::task::Context<'_>,
646    ) -> std::task::Poll<Option<Self::Item>> {
647        std::pin::Pin::new(&mut self.0).poll_next(cx)
648    }
649}
650
651/// Optional extractor wrapper
652///
653/// Makes any extractor optional - returns None instead of error on failure.
654impl<T: FromRequestParts> FromRequestParts for Option<T> {
655    fn from_request_parts(req: &Request) -> Result<Self> {
656        Ok(T::from_request_parts(req).ok())
657    }
658}
659
660/// Headers extractor
661///
662/// Provides access to all request headers as a typed map.
663///
664/// # Example
665///
666/// ```rust,ignore
667/// use rustapi_core::extract::Headers;
668///
669/// async fn handler(headers: Headers) -> impl IntoResponse {
670///     if let Some(content_type) = headers.get("content-type") {
671///         format!("Content-Type: {:?}", content_type)
672///     } else {
673///         "No Content-Type header".to_string()
674///     }
675/// }
676/// ```
677#[derive(Debug, Clone)]
678pub struct Headers(pub http::HeaderMap);
679
680impl Headers {
681    /// Get a header value by name
682    pub fn get(&self, name: &str) -> Option<&http::HeaderValue> {
683        self.0.get(name)
684    }
685
686    /// Check if a header exists
687    pub fn contains(&self, name: &str) -> bool {
688        self.0.contains_key(name)
689    }
690
691    /// Get the number of headers
692    pub fn len(&self) -> usize {
693        self.0.len()
694    }
695
696    /// Check if headers are empty
697    pub fn is_empty(&self) -> bool {
698        self.0.is_empty()
699    }
700
701    /// Iterate over all headers
702    pub fn iter(&self) -> http::header::Iter<'_, http::HeaderValue> {
703        self.0.iter()
704    }
705}
706
707impl FromRequestParts for Headers {
708    fn from_request_parts(req: &Request) -> Result<Self> {
709        Ok(Headers(req.headers().clone()))
710    }
711}
712
713impl Deref for Headers {
714    type Target = http::HeaderMap;
715
716    fn deref(&self) -> &Self::Target {
717        &self.0
718    }
719}
720
721/// Single header value extractor
722///
723/// Extracts a specific header value by name. Returns an error if the header is missing.
724///
725/// # Example
726///
727/// ```rust,ignore
728/// use rustapi_core::extract::HeaderValue;
729///
730/// async fn handler(
731///     auth: HeaderValue<{ "authorization" }>,
732/// ) -> impl IntoResponse {
733///     format!("Auth header: {}", auth.0)
734/// }
735/// ```
736///
737/// Note: Due to Rust's const generics limitations, you may need to use the
738/// `HeaderValueOf` type alias or extract headers manually using the `Headers` extractor.
739#[derive(Debug, Clone)]
740pub struct HeaderValue(pub String, pub &'static str);
741
742impl HeaderValue {
743    /// Create a new HeaderValue extractor for a specific header name
744    pub fn new(name: &'static str, value: String) -> Self {
745        Self(value, name)
746    }
747
748    /// Get the header value
749    pub fn value(&self) -> &str {
750        &self.0
751    }
752
753    /// Get the header name
754    pub fn name(&self) -> &'static str {
755        self.1
756    }
757
758    /// Extract a specific header from a request
759    pub fn extract(req: &Request, name: &'static str) -> Result<Self> {
760        req.headers()
761            .get(name)
762            .and_then(|v| v.to_str().ok())
763            .map(|s| HeaderValue(s.to_string(), name))
764            .ok_or_else(|| ApiError::bad_request(format!("Missing required header: {}", name)))
765    }
766}
767
768impl Deref for HeaderValue {
769    type Target = String;
770
771    fn deref(&self) -> &Self::Target {
772        &self.0
773    }
774}
775
776/// Extension extractor
777///
778/// Retrieves typed data from request extensions that was inserted by middleware.
779///
780/// # Example
781///
782/// ```rust,ignore
783/// use rustapi_core::extract::Extension;
784///
785/// // Middleware inserts user data
786/// #[derive(Clone)]
787/// struct CurrentUser { id: i64 }
788///
789/// async fn handler(Extension(user): Extension<CurrentUser>) -> impl IntoResponse {
790///     format!("User ID: {}", user.id)
791/// }
792/// ```
793#[derive(Debug, Clone)]
794pub struct Extension<T>(pub T);
795
796impl<T: Clone + Send + Sync + 'static> FromRequestParts for Extension<T> {
797    fn from_request_parts(req: &Request) -> Result<Self> {
798        req.extensions()
799            .get::<T>()
800            .cloned()
801            .map(Extension)
802            .ok_or_else(|| {
803                ApiError::internal(format!(
804                    "Extension of type `{}` not found. Did middleware insert it?",
805                    std::any::type_name::<T>()
806                ))
807            })
808    }
809}
810
811impl<T> Deref for Extension<T> {
812    type Target = T;
813
814    fn deref(&self) -> &Self::Target {
815        &self.0
816    }
817}
818
819impl<T> DerefMut for Extension<T> {
820    fn deref_mut(&mut self) -> &mut Self::Target {
821        &mut self.0
822    }
823}
824
825/// Client IP address extractor
826///
827/// Extracts the client IP address from the request. When `trust_proxy` is enabled,
828/// it will use the `X-Forwarded-For` header if present.
829///
830/// # Example
831///
832/// ```rust,ignore
833/// use rustapi_core::extract::ClientIp;
834///
835/// async fn handler(ClientIp(ip): ClientIp) -> impl IntoResponse {
836///     format!("Your IP: {}", ip)
837/// }
838/// ```
839#[derive(Debug, Clone)]
840pub struct ClientIp(pub std::net::IpAddr);
841
842impl ClientIp {
843    /// Extract client IP, optionally trusting X-Forwarded-For header
844    pub fn extract_with_config(req: &Request, trust_proxy: bool) -> Result<Self> {
845        if trust_proxy {
846            // Try X-Forwarded-For header first
847            if let Some(forwarded) = req.headers().get("x-forwarded-for") {
848                if let Ok(forwarded_str) = forwarded.to_str() {
849                    // X-Forwarded-For can contain multiple IPs, take the first one
850                    if let Some(first_ip) = forwarded_str.split(',').next() {
851                        if let Ok(ip) = first_ip.trim().parse() {
852                            return Ok(ClientIp(ip));
853                        }
854                    }
855                }
856            }
857        }
858
859        // Fall back to socket address from extensions (if set by server)
860        if let Some(addr) = req.extensions().get::<std::net::SocketAddr>() {
861            return Ok(ClientIp(addr.ip()));
862        }
863
864        // Default to localhost if no IP information available
865        Ok(ClientIp(std::net::IpAddr::V4(std::net::Ipv4Addr::new(
866            127, 0, 0, 1,
867        ))))
868    }
869}
870
871impl FromRequestParts for ClientIp {
872    fn from_request_parts(req: &Request) -> Result<Self> {
873        // By default, trust proxy headers
874        Self::extract_with_config(req, true)
875    }
876}
877
878/// Cookies extractor
879///
880/// Parses and provides access to request cookies from the Cookie header.
881///
882/// # Example
883///
884/// ```rust,ignore
885/// use rustapi_core::extract::Cookies;
886///
887/// async fn handler(cookies: Cookies) -> impl IntoResponse {
888///     if let Some(session) = cookies.get("session_id") {
889///         format!("Session: {}", session.value())
890///     } else {
891///         "No session cookie".to_string()
892///     }
893/// }
894/// ```
895#[cfg(feature = "cookies")]
896#[derive(Debug, Clone)]
897pub struct Cookies(pub cookie::CookieJar);
898
899#[cfg(feature = "cookies")]
900impl Cookies {
901    /// Get a cookie by name
902    pub fn get(&self, name: &str) -> Option<&cookie::Cookie<'static>> {
903        self.0.get(name)
904    }
905
906    /// Iterate over all cookies
907    pub fn iter(&self) -> impl Iterator<Item = &cookie::Cookie<'static>> {
908        self.0.iter()
909    }
910
911    /// Check if a cookie exists
912    pub fn contains(&self, name: &str) -> bool {
913        self.0.get(name).is_some()
914    }
915}
916
917#[cfg(feature = "cookies")]
918impl FromRequestParts for Cookies {
919    fn from_request_parts(req: &Request) -> Result<Self> {
920        let mut jar = cookie::CookieJar::new();
921
922        if let Some(cookie_header) = req.headers().get(header::COOKIE) {
923            if let Ok(cookie_str) = cookie_header.to_str() {
924                // Parse each cookie from the header
925                for cookie_part in cookie_str.split(';') {
926                    let trimmed = cookie_part.trim();
927                    if !trimmed.is_empty() {
928                        if let Ok(cookie) = cookie::Cookie::parse(trimmed.to_string()) {
929                            jar.add_original(cookie.into_owned());
930                        }
931                    }
932                }
933            }
934        }
935
936        Ok(Cookies(jar))
937    }
938}
939
940#[cfg(feature = "cookies")]
941impl Deref for Cookies {
942    type Target = cookie::CookieJar;
943
944    fn deref(&self) -> &Self::Target {
945        &self.0
946    }
947}
948
949// Implement FromRequestParts for common primitive types (path params)
950macro_rules! impl_from_request_parts_for_primitives {
951    ($($ty:ty),*) => {
952        $(
953            impl FromRequestParts for $ty {
954                fn from_request_parts(req: &Request) -> Result<Self> {
955                    let Path(value) = Path::<$ty>::from_request_parts(req)?;
956                    Ok(value)
957                }
958            }
959        )*
960    };
961}
962
963impl_from_request_parts_for_primitives!(
964    i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64, bool, String
965);
966
967// OperationModifier implementations for extractors
968
969use rustapi_openapi::{
970    MediaType, Operation, OperationModifier, Parameter, RequestBody, ResponseModifier, ResponseSpec,
971};
972
973// ValidatedJson - Adds request body
974impl<T: RustApiSchema> OperationModifier for ValidatedJson<T> {
975    fn update_operation(op: &mut Operation) {
976        let mut ctx = SchemaCtx::new();
977        let schema_ref = T::schema(&mut ctx);
978
979        let mut content = BTreeMap::new();
980        content.insert(
981            "application/json".to_string(),
982            MediaType {
983                schema: Some(schema_ref),
984                example: None,
985            },
986        );
987
988        op.request_body = Some(RequestBody {
989            description: None,
990            required: Some(true),
991            content,
992        });
993
994        // Add 422 Validation Error response
995        let mut responses_content = BTreeMap::new();
996        responses_content.insert(
997            "application/json".to_string(),
998            MediaType {
999                schema: Some(SchemaRef::Ref {
1000                    reference: "#/components/schemas/ValidationErrorSchema".to_string(),
1001                }),
1002                example: None,
1003            },
1004        );
1005
1006        op.responses.insert(
1007            "422".to_string(),
1008            ResponseSpec {
1009                description: "Validation Error".to_string(),
1010                content: responses_content,
1011                headers: BTreeMap::new(),
1012            },
1013        );
1014    }
1015
1016    fn register_components(spec: &mut rustapi_openapi::OpenApiSpec) {
1017        spec.register_in_place::<T>();
1018        spec.register_in_place::<rustapi_openapi::ValidationErrorSchema>();
1019        spec.register_in_place::<rustapi_openapi::ValidationErrorBodySchema>();
1020        spec.register_in_place::<rustapi_openapi::FieldErrorSchema>();
1021    }
1022}
1023
1024// AsyncValidatedJson - Adds request body + 422 response (same as ValidatedJson)
1025impl<T: RustApiSchema> OperationModifier for AsyncValidatedJson<T> {
1026    fn update_operation(op: &mut Operation) {
1027        let mut ctx = SchemaCtx::new();
1028        let schema_ref = T::schema(&mut ctx);
1029
1030        let mut content = BTreeMap::new();
1031        content.insert(
1032            "application/json".to_string(),
1033            MediaType {
1034                schema: Some(schema_ref),
1035                example: None,
1036            },
1037        );
1038
1039        op.request_body = Some(RequestBody {
1040            description: None,
1041            required: Some(true),
1042            content,
1043        });
1044
1045        // Add 422 Validation Error response
1046        let mut responses_content = BTreeMap::new();
1047        responses_content.insert(
1048            "application/json".to_string(),
1049            MediaType {
1050                schema: Some(SchemaRef::Ref {
1051                    reference: "#/components/schemas/ValidationErrorSchema".to_string(),
1052                }),
1053                example: None,
1054            },
1055        );
1056
1057        op.responses.insert(
1058            "422".to_string(),
1059            ResponseSpec {
1060                description: "Validation Error".to_string(),
1061                content: responses_content,
1062                headers: BTreeMap::new(),
1063            },
1064        );
1065    }
1066
1067    fn register_components(spec: &mut rustapi_openapi::OpenApiSpec) {
1068        spec.register_in_place::<T>();
1069        spec.register_in_place::<rustapi_openapi::ValidationErrorSchema>();
1070        spec.register_in_place::<rustapi_openapi::ValidationErrorBodySchema>();
1071        spec.register_in_place::<rustapi_openapi::FieldErrorSchema>();
1072    }
1073}
1074
1075// Json - Adds request body (Same as ValidatedJson)
1076impl<T: RustApiSchema> OperationModifier for Json<T> {
1077    fn update_operation(op: &mut Operation) {
1078        let mut ctx = SchemaCtx::new();
1079        let schema_ref = T::schema(&mut ctx);
1080
1081        let mut content = BTreeMap::new();
1082        content.insert(
1083            "application/json".to_string(),
1084            MediaType {
1085                schema: Some(schema_ref),
1086                example: None,
1087            },
1088        );
1089
1090        op.request_body = Some(RequestBody {
1091            description: None,
1092            required: Some(true),
1093            content,
1094        });
1095    }
1096
1097    fn register_components(spec: &mut rustapi_openapi::OpenApiSpec) {
1098        spec.register_in_place::<T>();
1099    }
1100}
1101
1102// Path - No op (handled by app routing)
1103impl<T> OperationModifier for Path<T> {
1104    fn update_operation(_op: &mut Operation) {}
1105}
1106
1107// Typed - No op
1108impl<T> OperationModifier for Typed<T> {
1109    fn update_operation(_op: &mut Operation) {}
1110}
1111
1112// Query - Extracts query params using field_schemas
1113impl<T: RustApiSchema> OperationModifier for Query<T> {
1114    fn update_operation(op: &mut Operation) {
1115        let mut ctx = SchemaCtx::new();
1116        if let Some(fields) = T::field_schemas(&mut ctx) {
1117            let new_params: Vec<Parameter> = fields
1118                .into_iter()
1119                .map(|(name, schema)| {
1120                    Parameter {
1121                        name,
1122                        location: "query".to_string(),
1123                        required: false, // Assume optional
1124                        deprecated: None,
1125                        description: None,
1126                        schema: Some(schema),
1127                    }
1128                })
1129                .collect();
1130
1131            op.parameters.extend(new_params);
1132        }
1133    }
1134
1135    fn register_components(spec: &mut rustapi_openapi::OpenApiSpec) {
1136        spec.register_in_place::<T>();
1137    }
1138}
1139
1140// State - No op
1141impl<T> OperationModifier for State<T> {
1142    fn update_operation(_op: &mut Operation) {}
1143}
1144
1145// Body - Generic binary body
1146impl OperationModifier for Body {
1147    fn update_operation(op: &mut Operation) {
1148        let mut content = BTreeMap::new();
1149        content.insert(
1150            "application/octet-stream".to_string(),
1151            MediaType {
1152                schema: Some(SchemaRef::Inline(
1153                    serde_json::json!({ "type": "string", "format": "binary" }),
1154                )),
1155                example: None,
1156            },
1157        );
1158
1159        op.request_body = Some(RequestBody {
1160            description: None,
1161            required: Some(true),
1162            content,
1163        });
1164    }
1165}
1166
1167// BodyStream - Generic binary stream
1168impl OperationModifier for BodyStream {
1169    fn update_operation(op: &mut Operation) {
1170        let mut content = BTreeMap::new();
1171        content.insert(
1172            "application/octet-stream".to_string(),
1173            MediaType {
1174                schema: Some(SchemaRef::Inline(
1175                    serde_json::json!({ "type": "string", "format": "binary" }),
1176                )),
1177                example: None,
1178            },
1179        );
1180
1181        op.request_body = Some(RequestBody {
1182            description: None,
1183            required: Some(true),
1184            content,
1185        });
1186    }
1187}
1188
1189// ResponseModifier implementations for extractors
1190
1191// Json<T> - 200 OK with schema T
1192impl<T: RustApiSchema> ResponseModifier for Json<T> {
1193    fn update_response(op: &mut Operation) {
1194        let mut ctx = SchemaCtx::new();
1195        let schema_ref = T::schema(&mut ctx);
1196
1197        let mut content = BTreeMap::new();
1198        content.insert(
1199            "application/json".to_string(),
1200            MediaType {
1201                schema: Some(schema_ref),
1202                example: None,
1203            },
1204        );
1205
1206        op.responses.insert(
1207            "200".to_string(),
1208            ResponseSpec {
1209                description: "Successful response".to_string(),
1210                content,
1211                headers: BTreeMap::new(),
1212            },
1213        );
1214    }
1215
1216    fn register_components(spec: &mut rustapi_openapi::OpenApiSpec) {
1217        spec.register_in_place::<T>();
1218    }
1219}
1220
1221// RustApiSchema implementations
1222
1223impl<T: RustApiSchema> RustApiSchema for Json<T> {
1224    fn schema(ctx: &mut SchemaCtx) -> SchemaRef {
1225        T::schema(ctx)
1226    }
1227}
1228
1229impl<T: RustApiSchema> RustApiSchema for ValidatedJson<T> {
1230    fn schema(ctx: &mut SchemaCtx) -> SchemaRef {
1231        T::schema(ctx)
1232    }
1233}
1234
1235impl<T: RustApiSchema> RustApiSchema for AsyncValidatedJson<T> {
1236    fn schema(ctx: &mut SchemaCtx) -> SchemaRef {
1237        T::schema(ctx)
1238    }
1239}
1240
1241impl<T: RustApiSchema> RustApiSchema for Query<T> {
1242    fn schema(ctx: &mut SchemaCtx) -> SchemaRef {
1243        T::schema(ctx)
1244    }
1245    fn field_schemas(ctx: &mut SchemaCtx) -> Option<BTreeMap<String, SchemaRef>> {
1246        T::field_schemas(ctx)
1247    }
1248}
1249
1250// ─── Pagination Extractors ──────────────────────────────────────────────────
1251
1252/// Default page number (1-indexed)
1253const DEFAULT_PAGE: u64 = 1;
1254/// Default items per page
1255const DEFAULT_PER_PAGE: u64 = 20;
1256/// Maximum items per page (prevents abuse)
1257const MAX_PER_PAGE: u64 = 100;
1258
1259/// Offset-based pagination extractor
1260///
1261/// Extracts pagination parameters from the query string.
1262/// Supports `?page=1&per_page=20` (defaults: page=1, per_page=20, max=100).
1263///
1264/// # Example
1265///
1266/// ```rust,ignore
1267/// use rustapi_core::Paginate;
1268///
1269/// async fn list_users(paginate: Paginate) -> impl IntoResponse {
1270///     let offset = paginate.offset();
1271///     let limit = paginate.limit();
1272///     // SELECT * FROM users LIMIT $limit OFFSET $offset
1273/// }
1274/// ```
1275#[derive(Debug, Clone, Copy)]
1276pub struct Paginate {
1277    /// Current page (1-indexed)
1278    pub page: u64,
1279    /// Items per page (capped at MAX_PER_PAGE)
1280    pub per_page: u64,
1281}
1282
1283impl Paginate {
1284    /// Create a new Paginate with given page and per_page
1285    pub fn new(page: u64, per_page: u64) -> Self {
1286        Self {
1287            page: page.max(1),
1288            per_page: per_page.clamp(1, MAX_PER_PAGE),
1289        }
1290    }
1291
1292    /// Calculate the SQL OFFSET value
1293    pub fn offset(&self) -> u64 {
1294        (self.page - 1) * self.per_page
1295    }
1296
1297    /// Get the LIMIT value (alias for per_page)
1298    pub fn limit(&self) -> u64 {
1299        self.per_page
1300    }
1301
1302    /// Build a `Paginated<T>` response from this pagination and results
1303    pub fn paginate<T>(self, items: Vec<T>, total: u64) -> crate::hateoas::Paginated<T> {
1304        crate::hateoas::Paginated {
1305            items,
1306            page: self.page,
1307            per_page: self.per_page,
1308            total,
1309        }
1310    }
1311}
1312
1313impl Default for Paginate {
1314    fn default() -> Self {
1315        Self {
1316            page: DEFAULT_PAGE,
1317            per_page: DEFAULT_PER_PAGE,
1318        }
1319    }
1320}
1321
1322impl FromRequestParts for Paginate {
1323    fn from_request_parts(req: &Request) -> Result<Self> {
1324        let query = req.query_string().unwrap_or("");
1325
1326        #[derive(serde::Deserialize)]
1327        struct PaginateQuery {
1328            page: Option<u64>,
1329            per_page: Option<u64>,
1330        }
1331
1332        let params: PaginateQuery = serde_urlencoded::from_str(query).unwrap_or(PaginateQuery {
1333            page: None,
1334            per_page: None,
1335        });
1336
1337        Ok(Paginate::new(
1338            params.page.unwrap_or(DEFAULT_PAGE),
1339            params.per_page.unwrap_or(DEFAULT_PER_PAGE),
1340        ))
1341    }
1342}
1343
1344/// Cursor-based pagination extractor
1345///
1346/// Extracts cursor pagination parameters from the query string.
1347/// Supports `?cursor=abc123&limit=20` (defaults: cursor=None, limit=20, max=100).
1348///
1349/// Cursor-based pagination is preferred for large datasets or real-time data
1350/// where offset-based pagination would skip or duplicate items.
1351///
1352/// # Example
1353///
1354/// ```rust,ignore
1355/// use rustapi_core::CursorPaginate;
1356///
1357/// async fn list_events(cursor: CursorPaginate) -> impl IntoResponse {
1358///     let limit = cursor.limit();
1359///     if let Some(after) = cursor.after() {
1360///         // SELECT * FROM events WHERE id > $after ORDER BY id LIMIT $limit
1361///     } else {
1362///         // SELECT * FROM events ORDER BY id LIMIT $limit
1363///     }
1364/// }
1365/// ```
1366#[derive(Debug, Clone)]
1367pub struct CursorPaginate {
1368    /// Opaque cursor token (None = start from beginning)
1369    pub cursor: Option<String>,
1370    /// Items per page (capped at MAX_PER_PAGE)
1371    pub per_page: u64,
1372}
1373
1374impl CursorPaginate {
1375    /// Create a new CursorPaginate
1376    pub fn new(cursor: Option<String>, per_page: u64) -> Self {
1377        Self {
1378            cursor,
1379            per_page: per_page.clamp(1, MAX_PER_PAGE),
1380        }
1381    }
1382
1383    /// Get the cursor value (if any)
1384    pub fn after(&self) -> Option<&str> {
1385        self.cursor.as_deref()
1386    }
1387
1388    /// Get the LIMIT value
1389    pub fn limit(&self) -> u64 {
1390        self.per_page
1391    }
1392
1393    /// Check if this is the first page (no cursor)
1394    pub fn is_first_page(&self) -> bool {
1395        self.cursor.is_none()
1396    }
1397}
1398
1399impl Default for CursorPaginate {
1400    fn default() -> Self {
1401        Self {
1402            cursor: None,
1403            per_page: DEFAULT_PER_PAGE,
1404        }
1405    }
1406}
1407
1408impl FromRequestParts for CursorPaginate {
1409    fn from_request_parts(req: &Request) -> Result<Self> {
1410        let query = req.query_string().unwrap_or("");
1411
1412        #[derive(serde::Deserialize)]
1413        struct CursorQuery {
1414            cursor: Option<String>,
1415            limit: Option<u64>,
1416        }
1417
1418        let params: CursorQuery = serde_urlencoded::from_str(query).unwrap_or(CursorQuery {
1419            cursor: None,
1420            limit: None,
1421        });
1422
1423        Ok(CursorPaginate::new(
1424            params.cursor,
1425            params.limit.unwrap_or(DEFAULT_PER_PAGE),
1426        ))
1427    }
1428}
1429
1430#[cfg(test)]
1431mod tests {
1432    use super::*;
1433    use crate::path_params::PathParams;
1434    use bytes::Bytes;
1435    use http::{Extensions, Method};
1436    use proptest::prelude::*;
1437    use proptest::test_runner::TestCaseError;
1438    use std::sync::Arc;
1439
1440    /// Create a test request with the given method, path, and headers
1441    fn create_test_request_with_headers(
1442        method: Method,
1443        path: &str,
1444        headers: Vec<(&str, &str)>,
1445    ) -> Request {
1446        let uri: http::Uri = path.parse().unwrap();
1447        let mut builder = http::Request::builder().method(method).uri(uri);
1448
1449        for (name, value) in headers {
1450            builder = builder.header(name, value);
1451        }
1452
1453        let req = builder.body(()).unwrap();
1454        let (parts, _) = req.into_parts();
1455
1456        Request::new(
1457            parts,
1458            crate::request::BodyVariant::Buffered(Bytes::new()),
1459            Arc::new(Extensions::new()),
1460            PathParams::new(),
1461        )
1462    }
1463
1464    /// Create a test request with extensions
1465    fn create_test_request_with_extensions<T: Clone + Send + Sync + 'static>(
1466        method: Method,
1467        path: &str,
1468        extension: T,
1469    ) -> Request {
1470        let uri: http::Uri = path.parse().unwrap();
1471        let builder = http::Request::builder().method(method).uri(uri);
1472
1473        let req = builder.body(()).unwrap();
1474        let (mut parts, _) = req.into_parts();
1475        parts.extensions.insert(extension);
1476
1477        Request::new(
1478            parts,
1479            crate::request::BodyVariant::Buffered(Bytes::new()),
1480            Arc::new(Extensions::new()),
1481            PathParams::new(),
1482        )
1483    }
1484
1485    // **Feature: phase3-batteries-included, Property 14: Headers extractor completeness**
1486    //
1487    // For any request with headers H, the `Headers` extractor SHALL return a map
1488    // containing all key-value pairs in H.
1489    //
1490    // **Validates: Requirements 5.1**
1491    proptest! {
1492        #![proptest_config(ProptestConfig::with_cases(100))]
1493
1494        #[test]
1495        fn prop_headers_extractor_completeness(
1496            // Generate random header names and values
1497            // Using alphanumeric strings to ensure valid header names/values
1498            headers in prop::collection::vec(
1499                (
1500                    "[a-z][a-z0-9-]{0,20}",  // Valid header name pattern
1501                    "[a-zA-Z0-9 ]{1,50}"     // Valid header value pattern
1502                ),
1503                0..10
1504            )
1505        ) {
1506            let result: Result<(), TestCaseError> = (|| {
1507                // Convert to header tuples
1508                let header_tuples: Vec<(&str, &str)> = headers
1509                    .iter()
1510                    .map(|(k, v)| (k.as_str(), v.as_str()))
1511                    .collect();
1512
1513                // Create request with headers
1514                let request = create_test_request_with_headers(
1515                    Method::GET,
1516                    "/test",
1517                    header_tuples.clone(),
1518                );
1519
1520                // Extract headers
1521                let extracted = Headers::from_request_parts(&request)
1522                    .map_err(|e| TestCaseError::fail(format!("Failed to extract headers: {}", e)))?;
1523
1524                // Verify all original headers are present
1525                // HTTP allows duplicate headers - get_all() returns all values for a header name
1526                for (name, value) in &headers {
1527                    // Check that the header name exists
1528                    let all_values: Vec<_> = extracted.get_all(name.as_str()).iter().collect();
1529                    prop_assert!(
1530                        !all_values.is_empty(),
1531                        "Header '{}' not found",
1532                        name
1533                    );
1534
1535                    // Check that the value is among the extracted values
1536                    let value_found = all_values.iter().any(|v| {
1537                        v.to_str().map(|s| s == value.as_str()).unwrap_or(false)
1538                    });
1539
1540                    prop_assert!(
1541                        value_found,
1542                        "Header '{}' value '{}' not found in extracted values",
1543                        name,
1544                        value
1545                    );
1546                }
1547
1548                Ok(())
1549            })();
1550            result?;
1551        }
1552    }
1553
1554    // **Feature: phase3-batteries-included, Property 15: HeaderValue extractor correctness**
1555    //
1556    // For any request with header "X" having value V, `HeaderValue::extract(req, "X")` SHALL return V;
1557    // for requests without header "X", it SHALL return an error.
1558    //
1559    // **Validates: Requirements 5.2**
1560    proptest! {
1561        #![proptest_config(ProptestConfig::with_cases(100))]
1562
1563        #[test]
1564        fn prop_header_value_extractor_correctness(
1565            header_name in "[a-z][a-z0-9-]{0,20}",
1566            header_value in "[a-zA-Z0-9 ]{1,50}",
1567            has_header in prop::bool::ANY,
1568        ) {
1569            let result: Result<(), TestCaseError> = (|| {
1570                let headers = if has_header {
1571                    vec![(header_name.as_str(), header_value.as_str())]
1572                } else {
1573                    vec![]
1574                };
1575
1576                let _request = create_test_request_with_headers(Method::GET, "/test", headers);
1577
1578                // We need to use a static string for the header name in the extractor
1579                // So we'll test with a known header name
1580                let test_header = "x-test-header";
1581                let request_with_known_header = if has_header {
1582                    create_test_request_with_headers(
1583                        Method::GET,
1584                        "/test",
1585                        vec![(test_header, header_value.as_str())],
1586                    )
1587                } else {
1588                    create_test_request_with_headers(Method::GET, "/test", vec![])
1589                };
1590
1591                let result = HeaderValue::extract(&request_with_known_header, test_header);
1592
1593                if has_header {
1594                    let extracted = result
1595                        .map_err(|e| TestCaseError::fail(format!("Expected header to be found: {}", e)))?;
1596                    prop_assert_eq!(
1597                        extracted.value(),
1598                        header_value.as_str(),
1599                        "Header value mismatch"
1600                    );
1601                } else {
1602                    prop_assert!(
1603                        result.is_err(),
1604                        "Expected error when header is missing"
1605                    );
1606                }
1607
1608                Ok(())
1609            })();
1610            result?;
1611        }
1612    }
1613
1614    // **Feature: phase3-batteries-included, Property 17: ClientIp extractor with forwarding**
1615    //
1616    // For any request with socket IP S and X-Forwarded-For header F, when forwarding is enabled,
1617    // `ClientIp` SHALL return the first IP in F; when disabled, it SHALL return S.
1618    //
1619    // **Validates: Requirements 5.4**
1620    proptest! {
1621        #![proptest_config(ProptestConfig::with_cases(100))]
1622
1623        #[test]
1624        fn prop_client_ip_extractor_with_forwarding(
1625            // Generate valid IPv4 addresses
1626            forwarded_ip in (0u8..=255, 0u8..=255, 0u8..=255, 0u8..=255)
1627                .prop_map(|(a, b, c, d)| format!("{}.{}.{}.{}", a, b, c, d)),
1628            socket_ip in (0u8..=255, 0u8..=255, 0u8..=255, 0u8..=255)
1629                .prop_map(|(a, b, c, d)| std::net::IpAddr::V4(std::net::Ipv4Addr::new(a, b, c, d))),
1630            has_forwarded_header in prop::bool::ANY,
1631            trust_proxy in prop::bool::ANY,
1632        ) {
1633            let result: Result<(), TestCaseError> = (|| {
1634                let headers = if has_forwarded_header {
1635                    vec![("x-forwarded-for", forwarded_ip.as_str())]
1636                } else {
1637                    vec![]
1638                };
1639
1640                // Create request with headers
1641                let uri: http::Uri = "/test".parse().unwrap();
1642                let mut builder = http::Request::builder().method(Method::GET).uri(uri);
1643                for (name, value) in &headers {
1644                    builder = builder.header(*name, *value);
1645                }
1646                let req = builder.body(()).unwrap();
1647                let (mut parts, _) = req.into_parts();
1648
1649                // Add socket address to extensions
1650                let socket_addr = std::net::SocketAddr::new(socket_ip, 8080);
1651                parts.extensions.insert(socket_addr);
1652
1653                let request = Request::new(
1654                    parts,
1655                    crate::request::BodyVariant::Buffered(Bytes::new()),
1656                    Arc::new(Extensions::new()),
1657                    PathParams::new(),
1658                );
1659
1660                let extracted = ClientIp::extract_with_config(&request, trust_proxy)
1661                    .map_err(|e| TestCaseError::fail(format!("Failed to extract ClientIp: {}", e)))?;
1662
1663                if trust_proxy && has_forwarded_header {
1664                    // Should use X-Forwarded-For
1665                    let expected_ip: std::net::IpAddr = forwarded_ip.parse()
1666                        .map_err(|e| TestCaseError::fail(format!("Invalid IP: {}", e)))?;
1667                    prop_assert_eq!(
1668                        extracted.0,
1669                        expected_ip,
1670                        "Should use X-Forwarded-For IP when trust_proxy is enabled"
1671                    );
1672                } else {
1673                    // Should use socket IP
1674                    prop_assert_eq!(
1675                        extracted.0,
1676                        socket_ip,
1677                        "Should use socket IP when trust_proxy is disabled or no X-Forwarded-For"
1678                    );
1679                }
1680
1681                Ok(())
1682            })();
1683            result?;
1684        }
1685    }
1686
1687    // **Feature: phase3-batteries-included, Property 18: Extension extractor retrieval**
1688    //
1689    // For any type T and value V inserted into request extensions by middleware,
1690    // `Extension<T>` SHALL return V.
1691    //
1692    // **Validates: Requirements 5.5**
1693    proptest! {
1694        #![proptest_config(ProptestConfig::with_cases(100))]
1695
1696        #[test]
1697        fn prop_extension_extractor_retrieval(
1698            value in any::<i64>(),
1699            has_extension in prop::bool::ANY,
1700        ) {
1701            let result: Result<(), TestCaseError> = (|| {
1702                // Create a simple wrapper type for testing
1703                #[derive(Clone, Debug, PartialEq)]
1704                struct TestExtension(i64);
1705
1706                let uri: http::Uri = "/test".parse().unwrap();
1707                let builder = http::Request::builder().method(Method::GET).uri(uri);
1708                let req = builder.body(()).unwrap();
1709                let (mut parts, _) = req.into_parts();
1710
1711                if has_extension {
1712                    parts.extensions.insert(TestExtension(value));
1713                }
1714
1715                let request = Request::new(
1716                    parts,
1717                    crate::request::BodyVariant::Buffered(Bytes::new()),
1718                    Arc::new(Extensions::new()),
1719                    PathParams::new(),
1720                );
1721
1722                let result = Extension::<TestExtension>::from_request_parts(&request);
1723
1724                if has_extension {
1725                    let extracted = result
1726                        .map_err(|e| TestCaseError::fail(format!("Expected extension to be found: {}", e)))?;
1727                    prop_assert_eq!(
1728                        extracted.0,
1729                        TestExtension(value),
1730                        "Extension value mismatch"
1731                    );
1732                } else {
1733                    prop_assert!(
1734                        result.is_err(),
1735                        "Expected error when extension is missing"
1736                    );
1737                }
1738
1739                Ok(())
1740            })();
1741            result?;
1742        }
1743    }
1744
1745    // Unit tests for basic functionality
1746
1747    #[test]
1748    fn test_headers_extractor_basic() {
1749        let request = create_test_request_with_headers(
1750            Method::GET,
1751            "/test",
1752            vec![
1753                ("content-type", "application/json"),
1754                ("accept", "text/html"),
1755            ],
1756        );
1757
1758        let headers = Headers::from_request_parts(&request).unwrap();
1759
1760        assert!(headers.contains("content-type"));
1761        assert!(headers.contains("accept"));
1762        assert!(!headers.contains("x-custom"));
1763        assert_eq!(headers.len(), 2);
1764    }
1765
1766    #[test]
1767    fn test_header_value_extractor_present() {
1768        let request = create_test_request_with_headers(
1769            Method::GET,
1770            "/test",
1771            vec![("authorization", "Bearer token123")],
1772        );
1773
1774        let result = HeaderValue::extract(&request, "authorization");
1775        assert!(result.is_ok());
1776        assert_eq!(result.unwrap().value(), "Bearer token123");
1777    }
1778
1779    #[test]
1780    fn test_header_value_extractor_missing() {
1781        let request = create_test_request_with_headers(Method::GET, "/test", vec![]);
1782
1783        let result = HeaderValue::extract(&request, "authorization");
1784        assert!(result.is_err());
1785    }
1786
1787    #[test]
1788    fn test_client_ip_from_forwarded_header() {
1789        let request = create_test_request_with_headers(
1790            Method::GET,
1791            "/test",
1792            vec![("x-forwarded-for", "192.168.1.100, 10.0.0.1")],
1793        );
1794
1795        let ip = ClientIp::extract_with_config(&request, true).unwrap();
1796        assert_eq!(ip.0, "192.168.1.100".parse::<std::net::IpAddr>().unwrap());
1797    }
1798
1799    #[test]
1800    fn test_client_ip_ignores_forwarded_when_not_trusted() {
1801        let uri: http::Uri = "/test".parse().unwrap();
1802        let builder = http::Request::builder()
1803            .method(Method::GET)
1804            .uri(uri)
1805            .header("x-forwarded-for", "192.168.1.100");
1806        let req = builder.body(()).unwrap();
1807        let (mut parts, _) = req.into_parts();
1808
1809        let socket_addr = std::net::SocketAddr::new(
1810            std::net::IpAddr::V4(std::net::Ipv4Addr::new(10, 0, 0, 1)),
1811            8080,
1812        );
1813        parts.extensions.insert(socket_addr);
1814
1815        let request = Request::new(
1816            parts,
1817            crate::request::BodyVariant::Buffered(Bytes::new()),
1818            Arc::new(Extensions::new()),
1819            PathParams::new(),
1820        );
1821
1822        let ip = ClientIp::extract_with_config(&request, false).unwrap();
1823        assert_eq!(ip.0, "10.0.0.1".parse::<std::net::IpAddr>().unwrap());
1824    }
1825
1826    #[test]
1827    fn test_extension_extractor_present() {
1828        #[derive(Clone, Debug, PartialEq)]
1829        struct MyData(String);
1830
1831        let request =
1832            create_test_request_with_extensions(Method::GET, "/test", MyData("hello".to_string()));
1833
1834        let result = Extension::<MyData>::from_request_parts(&request);
1835        assert!(result.is_ok());
1836        assert_eq!(result.unwrap().0, MyData("hello".to_string()));
1837    }
1838
1839    #[test]
1840    fn test_extension_extractor_missing() {
1841        #[derive(Clone, Debug)]
1842        #[allow(dead_code)]
1843        struct MyData(String);
1844
1845        let request = create_test_request_with_headers(Method::GET, "/test", vec![]);
1846
1847        let result = Extension::<MyData>::from_request_parts(&request);
1848        assert!(result.is_err());
1849    }
1850
1851    // Cookies tests (feature-gated)
1852    #[cfg(feature = "cookies")]
1853    mod cookies_tests {
1854        use super::*;
1855
1856        // **Feature: phase3-batteries-included, Property 16: Cookies extractor parsing**
1857        //
1858        // For any request with Cookie header containing cookies C, the `Cookies` extractor
1859        // SHALL return a CookieJar containing exactly the cookies in C.
1860        // Note: Duplicate cookie names result in only the last value being kept.
1861        //
1862        // **Validates: Requirements 5.3**
1863        proptest! {
1864            #![proptest_config(ProptestConfig::with_cases(100))]
1865
1866            #[test]
1867            fn prop_cookies_extractor_parsing(
1868                // Generate random cookie names and values
1869                // Using alphanumeric strings to ensure valid cookie names/values
1870                cookies in prop::collection::vec(
1871                    (
1872                        "[a-zA-Z][a-zA-Z0-9_]{0,15}",  // Valid cookie name pattern
1873                        "[a-zA-Z0-9]{1,30}"            // Valid cookie value pattern (no special chars)
1874                    ),
1875                    0..5
1876                )
1877            ) {
1878                let result: Result<(), TestCaseError> = (|| {
1879                    // Build cookie header string
1880                    let cookie_header = cookies
1881                        .iter()
1882                        .map(|(name, value)| format!("{}={}", name, value))
1883                        .collect::<Vec<_>>()
1884                        .join("; ");
1885
1886                    let headers = if !cookies.is_empty() {
1887                        vec![("cookie", cookie_header.as_str())]
1888                    } else {
1889                        vec![]
1890                    };
1891
1892                    let request = create_test_request_with_headers(Method::GET, "/test", headers);
1893
1894                    // Extract cookies
1895                    let extracted = Cookies::from_request_parts(&request)
1896                        .map_err(|e| TestCaseError::fail(format!("Failed to extract cookies: {}", e)))?;
1897
1898                    // Build expected cookies map - last value wins for duplicate names
1899                    let mut expected_cookies: std::collections::HashMap<&str, &str> = std::collections::HashMap::new();
1900                    for (name, value) in &cookies {
1901                        expected_cookies.insert(name.as_str(), value.as_str());
1902                    }
1903
1904                    // Verify all expected cookies are present with correct values
1905                    for (name, expected_value) in &expected_cookies {
1906                        let cookie = extracted.get(name)
1907                            .ok_or_else(|| TestCaseError::fail(format!("Cookie '{}' not found", name)))?;
1908
1909                        prop_assert_eq!(
1910                            cookie.value(),
1911                            *expected_value,
1912                            "Cookie '{}' value mismatch",
1913                            name
1914                        );
1915                    }
1916
1917                    // Count cookies in jar should match unique cookie names
1918                    let extracted_count = extracted.iter().count();
1919                    prop_assert_eq!(
1920                        extracted_count,
1921                        expected_cookies.len(),
1922                        "Expected {} unique cookies, got {}",
1923                        expected_cookies.len(),
1924                        extracted_count
1925                    );
1926
1927                    Ok(())
1928                })();
1929                result?;
1930            }
1931        }
1932
1933        #[test]
1934        fn test_cookies_extractor_basic() {
1935            let request = create_test_request_with_headers(
1936                Method::GET,
1937                "/test",
1938                vec![("cookie", "session=abc123; user=john")],
1939            );
1940
1941            let cookies = Cookies::from_request_parts(&request).unwrap();
1942
1943            assert!(cookies.contains("session"));
1944            assert!(cookies.contains("user"));
1945            assert!(!cookies.contains("other"));
1946
1947            assert_eq!(cookies.get("session").unwrap().value(), "abc123");
1948            assert_eq!(cookies.get("user").unwrap().value(), "john");
1949        }
1950
1951        #[test]
1952        fn test_cookies_extractor_empty() {
1953            let request = create_test_request_with_headers(Method::GET, "/test", vec![]);
1954
1955            let cookies = Cookies::from_request_parts(&request).unwrap();
1956            assert_eq!(cookies.iter().count(), 0);
1957        }
1958
1959        #[test]
1960        fn test_cookies_extractor_single() {
1961            let request = create_test_request_with_headers(
1962                Method::GET,
1963                "/test",
1964                vec![("cookie", "token=xyz789")],
1965            );
1966
1967            let cookies = Cookies::from_request_parts(&request).unwrap();
1968            assert_eq!(cookies.iter().count(), 1);
1969            assert_eq!(cookies.get("token").unwrap().value(), "xyz789");
1970        }
1971    }
1972
1973    #[tokio::test]
1974    async fn test_async_validated_json_with_state_context() {
1975        use async_trait::async_trait;
1976        use rustapi_validate::prelude::*;
1977        use rustapi_validate::v2::{
1978            AsyncValidationRule, DatabaseValidator, ValidationContextBuilder,
1979        };
1980        use serde::{Deserialize, Serialize};
1981
1982        struct MockDbValidator {
1983            unique_values: Vec<String>,
1984        }
1985
1986        #[async_trait]
1987        impl DatabaseValidator for MockDbValidator {
1988            async fn exists(
1989                &self,
1990                _table: &str,
1991                _column: &str,
1992                _value: &str,
1993            ) -> Result<bool, String> {
1994                Ok(true)
1995            }
1996            async fn is_unique(
1997                &self,
1998                _table: &str,
1999                _column: &str,
2000                value: &str,
2001            ) -> Result<bool, String> {
2002                Ok(!self.unique_values.contains(&value.to_string()))
2003            }
2004            async fn is_unique_except(
2005                &self,
2006                _table: &str,
2007                _column: &str,
2008                value: &str,
2009                _except_id: &str,
2010            ) -> Result<bool, String> {
2011                Ok(!self.unique_values.contains(&value.to_string()))
2012            }
2013        }
2014
2015        #[derive(Debug, Deserialize, Serialize)]
2016        struct TestUser {
2017            email: String,
2018        }
2019
2020        impl Validate for TestUser {
2021            fn validate_with_group(
2022                &self,
2023                _group: rustapi_validate::v2::ValidationGroup,
2024            ) -> Result<(), rustapi_validate::v2::ValidationErrors> {
2025                Ok(())
2026            }
2027        }
2028
2029        #[async_trait]
2030        impl AsyncValidate for TestUser {
2031            async fn validate_async_with_group(
2032                &self,
2033                ctx: &ValidationContext,
2034                _group: rustapi_validate::v2::ValidationGroup,
2035            ) -> Result<(), rustapi_validate::v2::ValidationErrors> {
2036                let mut errors = rustapi_validate::v2::ValidationErrors::new();
2037
2038                let rule = AsyncUniqueRule::new("users", "email");
2039                if let Err(e) = rule.validate_async(&self.email, ctx).await {
2040                    errors.add("email", e);
2041                }
2042
2043                errors.into_result()
2044            }
2045        }
2046
2047        // Test 1: Without context in state (should fail due to missing validator)
2048        let uri: http::Uri = "/test".parse().unwrap();
2049        let user = TestUser {
2050            email: "new@example.com".to_string(),
2051        };
2052        let body_bytes = serde_json::to_vec(&user).unwrap();
2053
2054        let builder = http::Request::builder()
2055            .method(Method::POST)
2056            .uri(uri.clone())
2057            .header("content-type", "application/json");
2058        let req = builder.body(()).unwrap();
2059        let (parts, _) = req.into_parts();
2060
2061        // Construct Request with BodyVariant::Buffered
2062        let mut request = Request::new(
2063            parts,
2064            crate::request::BodyVariant::Buffered(Bytes::from(body_bytes.clone())),
2065            Arc::new(Extensions::new()),
2066            PathParams::new(),
2067        );
2068
2069        let result = AsyncValidatedJson::<TestUser>::from_request(&mut request).await;
2070
2071        assert!(result.is_err(), "Expected error when validator is missing");
2072        let err = result.unwrap_err();
2073        let err_str = format!("{:?}", err);
2074        assert!(
2075            err_str.contains("Database validator not configured")
2076                || err_str.contains("async_unique"),
2077            "Error should mention missing configuration or rule: {:?}",
2078            err_str
2079        );
2080
2081        // Test 2: With context in state (should succeed)
2082        let db_validator = MockDbValidator {
2083            unique_values: vec!["taken@example.com".to_string()],
2084        };
2085        let ctx = ValidationContextBuilder::new()
2086            .database(db_validator)
2087            .build();
2088
2089        let mut extensions = Extensions::new();
2090        extensions.insert(ctx);
2091
2092        let builder = http::Request::builder()
2093            .method(Method::POST)
2094            .uri(uri.clone())
2095            .header("content-type", "application/json");
2096        let req = builder.body(()).unwrap();
2097        let (parts, _) = req.into_parts();
2098
2099        let mut request = Request::new(
2100            parts,
2101            crate::request::BodyVariant::Buffered(Bytes::from(body_bytes.clone())),
2102            Arc::new(extensions),
2103            PathParams::new(),
2104        );
2105
2106        let result = AsyncValidatedJson::<TestUser>::from_request(&mut request).await;
2107        assert!(
2108            result.is_ok(),
2109            "Expected success when validator is present and value is unique. Error: {:?}",
2110            result.err()
2111        );
2112
2113        // Test 3: With context in state (should fail validation logic)
2114        let user_taken = TestUser {
2115            email: "taken@example.com".to_string(),
2116        };
2117        let body_taken = serde_json::to_vec(&user_taken).unwrap();
2118
2119        let db_validator = MockDbValidator {
2120            unique_values: vec!["taken@example.com".to_string()],
2121        };
2122        let ctx = ValidationContextBuilder::new()
2123            .database(db_validator)
2124            .build();
2125
2126        let mut extensions = Extensions::new();
2127        extensions.insert(ctx);
2128
2129        let builder = http::Request::builder()
2130            .method(Method::POST)
2131            .uri("/test")
2132            .header("content-type", "application/json");
2133        let req = builder.body(()).unwrap();
2134        let (parts, _) = req.into_parts();
2135
2136        let mut request = Request::new(
2137            parts,
2138            crate::request::BodyVariant::Buffered(Bytes::from(body_taken)),
2139            Arc::new(extensions),
2140            PathParams::new(),
2141        );
2142
2143        let result = AsyncValidatedJson::<TestUser>::from_request(&mut request).await;
2144        assert!(result.is_err(), "Expected validation error for taken email");
2145    }
2146}