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 OperationModifier for Headers {
714    fn update_operation(_op: &mut Operation) {}
715}
716
717impl Deref for Headers {
718    type Target = http::HeaderMap;
719
720    fn deref(&self) -> &Self::Target {
721        &self.0
722    }
723}
724
725/// Single header value extractor
726///
727/// Extracts a specific header value by name. Returns an error if the header is missing.
728///
729/// # Example
730///
731/// ```rust,ignore
732/// use rustapi_core::extract::HeaderValue;
733///
734/// async fn handler(
735///     auth: HeaderValue<{ "authorization" }>,
736/// ) -> impl IntoResponse {
737///     format!("Auth header: {}", auth.0)
738/// }
739/// ```
740///
741/// Note: Due to Rust's const generics limitations, you may need to use the
742/// `HeaderValueOf` type alias or extract headers manually using the `Headers` extractor.
743#[derive(Debug, Clone)]
744pub struct HeaderValue(pub String, pub &'static str);
745
746impl HeaderValue {
747    /// Create a new HeaderValue extractor for a specific header name
748    pub fn new(name: &'static str, value: String) -> Self {
749        Self(value, name)
750    }
751
752    /// Get the header value
753    pub fn value(&self) -> &str {
754        &self.0
755    }
756
757    /// Get the header name
758    pub fn name(&self) -> &'static str {
759        self.1
760    }
761
762    /// Extract a specific header from a request
763    pub fn extract(req: &Request, name: &'static str) -> Result<Self> {
764        req.headers()
765            .get(name)
766            .and_then(|v| v.to_str().ok())
767            .map(|s| HeaderValue(s.to_string(), name))
768            .ok_or_else(|| ApiError::bad_request(format!("Missing required header: {}", name)))
769    }
770}
771
772impl Deref for HeaderValue {
773    type Target = String;
774
775    fn deref(&self) -> &Self::Target {
776        &self.0
777    }
778}
779
780/// Extension extractor
781///
782/// Retrieves typed data from request extensions that was inserted by middleware.
783///
784/// # Example
785///
786/// ```rust,ignore
787/// use rustapi_core::extract::Extension;
788///
789/// // Middleware inserts user data
790/// #[derive(Clone)]
791/// struct CurrentUser { id: i64 }
792///
793/// async fn handler(Extension(user): Extension<CurrentUser>) -> impl IntoResponse {
794///     format!("User ID: {}", user.id)
795/// }
796/// ```
797#[derive(Debug, Clone)]
798pub struct Extension<T>(pub T);
799
800impl<T: Clone + Send + Sync + 'static> FromRequestParts for Extension<T> {
801    fn from_request_parts(req: &Request) -> Result<Self> {
802        req.extensions()
803            .get::<T>()
804            .cloned()
805            .map(Extension)
806            .ok_or_else(|| {
807                ApiError::internal(format!(
808                    "Extension of type `{}` not found. Did middleware insert it?",
809                    std::any::type_name::<T>()
810                ))
811            })
812    }
813}
814
815impl<T> Deref for Extension<T> {
816    type Target = T;
817
818    fn deref(&self) -> &Self::Target {
819        &self.0
820    }
821}
822
823impl<T> DerefMut for Extension<T> {
824    fn deref_mut(&mut self) -> &mut Self::Target {
825        &mut self.0
826    }
827}
828
829/// Client IP address extractor
830///
831/// Extracts the client IP address from the request. When `trust_proxy` is enabled,
832/// it will use the `X-Forwarded-For` header if present.
833///
834/// # Example
835///
836/// ```rust,ignore
837/// use rustapi_core::extract::ClientIp;
838///
839/// async fn handler(ClientIp(ip): ClientIp) -> impl IntoResponse {
840///     format!("Your IP: {}", ip)
841/// }
842/// ```
843#[derive(Debug, Clone)]
844pub struct ClientIp(pub std::net::IpAddr);
845
846impl ClientIp {
847    /// Extract client IP, optionally trusting X-Forwarded-For header
848    pub fn extract_with_config(req: &Request, trust_proxy: bool) -> Result<Self> {
849        if trust_proxy {
850            // Try X-Forwarded-For header first
851            if let Some(forwarded) = req.headers().get("x-forwarded-for") {
852                if let Ok(forwarded_str) = forwarded.to_str() {
853                    // X-Forwarded-For can contain multiple IPs, take the first one
854                    if let Some(first_ip) = forwarded_str.split(',').next() {
855                        if let Ok(ip) = first_ip.trim().parse() {
856                            return Ok(ClientIp(ip));
857                        }
858                    }
859                }
860            }
861        }
862
863        // Fall back to socket address from extensions (if set by server)
864        if let Some(addr) = req.extensions().get::<std::net::SocketAddr>() {
865            return Ok(ClientIp(addr.ip()));
866        }
867
868        // Default to localhost if no IP information available
869        Ok(ClientIp(std::net::IpAddr::V4(std::net::Ipv4Addr::new(
870            127, 0, 0, 1,
871        ))))
872    }
873}
874
875impl FromRequestParts for ClientIp {
876    fn from_request_parts(req: &Request) -> Result<Self> {
877        // By default, trust proxy headers
878        Self::extract_with_config(req, true)
879    }
880}
881
882/// Cookies extractor
883///
884/// Parses and provides access to request cookies from the Cookie header.
885///
886/// # Example
887///
888/// ```rust,ignore
889/// use rustapi_core::extract::Cookies;
890///
891/// async fn handler(cookies: Cookies) -> impl IntoResponse {
892///     if let Some(session) = cookies.get("session_id") {
893///         format!("Session: {}", session.value())
894///     } else {
895///         "No session cookie".to_string()
896///     }
897/// }
898/// ```
899#[cfg(feature = "cookies")]
900#[derive(Debug, Clone)]
901pub struct Cookies(pub cookie::CookieJar);
902
903#[cfg(feature = "cookies")]
904impl Cookies {
905    /// Get a cookie by name
906    pub fn get(&self, name: &str) -> Option<&cookie::Cookie<'static>> {
907        self.0.get(name)
908    }
909
910    /// Iterate over all cookies
911    pub fn iter(&self) -> impl Iterator<Item = &cookie::Cookie<'static>> {
912        self.0.iter()
913    }
914
915    /// Check if a cookie exists
916    pub fn contains(&self, name: &str) -> bool {
917        self.0.get(name).is_some()
918    }
919}
920
921#[cfg(feature = "cookies")]
922impl FromRequestParts for Cookies {
923    fn from_request_parts(req: &Request) -> Result<Self> {
924        let mut jar = cookie::CookieJar::new();
925
926        if let Some(cookie_header) = req.headers().get(header::COOKIE) {
927            if let Ok(cookie_str) = cookie_header.to_str() {
928                // Parse each cookie from the header
929                for cookie_part in cookie_str.split(';') {
930                    let trimmed = cookie_part.trim();
931                    if !trimmed.is_empty() {
932                        if let Ok(cookie) = cookie::Cookie::parse(trimmed.to_string()) {
933                            jar.add_original(cookie.into_owned());
934                        }
935                    }
936                }
937            }
938        }
939
940        Ok(Cookies(jar))
941    }
942}
943
944#[cfg(feature = "cookies")]
945impl Deref for Cookies {
946    type Target = cookie::CookieJar;
947
948    fn deref(&self) -> &Self::Target {
949        &self.0
950    }
951}
952
953// Implement FromRequestParts for common primitive types (path params)
954macro_rules! impl_from_request_parts_for_primitives {
955    ($($ty:ty),*) => {
956        $(
957            impl FromRequestParts for $ty {
958                fn from_request_parts(req: &Request) -> Result<Self> {
959                    let Path(value) = Path::<$ty>::from_request_parts(req)?;
960                    Ok(value)
961                }
962            }
963        )*
964    };
965}
966
967impl_from_request_parts_for_primitives!(
968    i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64, bool, String
969);
970
971// OperationModifier implementations for extractors
972
973use rustapi_openapi::{
974    MediaType, Operation, OperationModifier, Parameter, RequestBody, ResponseModifier, ResponseSpec,
975};
976
977// ValidatedJson - Adds request body
978impl<T: RustApiSchema> OperationModifier for ValidatedJson<T> {
979    fn update_operation(op: &mut Operation) {
980        let mut ctx = SchemaCtx::new();
981        let schema_ref = T::schema(&mut ctx);
982
983        let mut content = BTreeMap::new();
984        content.insert(
985            "application/json".to_string(),
986            MediaType {
987                schema: Some(schema_ref),
988                example: None,
989            },
990        );
991
992        op.request_body = Some(RequestBody {
993            description: None,
994            required: Some(true),
995            content,
996        });
997
998        // Add 422 Validation Error response
999        let mut responses_content = BTreeMap::new();
1000        responses_content.insert(
1001            "application/json".to_string(),
1002            MediaType {
1003                schema: Some(SchemaRef::Ref {
1004                    reference: "#/components/schemas/ValidationErrorSchema".to_string(),
1005                }),
1006                example: None,
1007            },
1008        );
1009
1010        op.responses.insert(
1011            "422".to_string(),
1012            ResponseSpec {
1013                description: "Validation Error".to_string(),
1014                content: responses_content,
1015                headers: BTreeMap::new(),
1016            },
1017        );
1018    }
1019
1020    fn register_components(spec: &mut rustapi_openapi::OpenApiSpec) {
1021        spec.register_in_place::<T>();
1022        spec.register_in_place::<rustapi_openapi::ValidationErrorSchema>();
1023        spec.register_in_place::<rustapi_openapi::ValidationErrorBodySchema>();
1024        spec.register_in_place::<rustapi_openapi::FieldErrorSchema>();
1025    }
1026}
1027
1028// AsyncValidatedJson - Adds request body + 422 response (same as ValidatedJson)
1029impl<T: RustApiSchema> OperationModifier for AsyncValidatedJson<T> {
1030    fn update_operation(op: &mut Operation) {
1031        let mut ctx = SchemaCtx::new();
1032        let schema_ref = T::schema(&mut ctx);
1033
1034        let mut content = BTreeMap::new();
1035        content.insert(
1036            "application/json".to_string(),
1037            MediaType {
1038                schema: Some(schema_ref),
1039                example: None,
1040            },
1041        );
1042
1043        op.request_body = Some(RequestBody {
1044            description: None,
1045            required: Some(true),
1046            content,
1047        });
1048
1049        // Add 422 Validation Error response
1050        let mut responses_content = BTreeMap::new();
1051        responses_content.insert(
1052            "application/json".to_string(),
1053            MediaType {
1054                schema: Some(SchemaRef::Ref {
1055                    reference: "#/components/schemas/ValidationErrorSchema".to_string(),
1056                }),
1057                example: None,
1058            },
1059        );
1060
1061        op.responses.insert(
1062            "422".to_string(),
1063            ResponseSpec {
1064                description: "Validation Error".to_string(),
1065                content: responses_content,
1066                headers: BTreeMap::new(),
1067            },
1068        );
1069    }
1070
1071    fn register_components(spec: &mut rustapi_openapi::OpenApiSpec) {
1072        spec.register_in_place::<T>();
1073        spec.register_in_place::<rustapi_openapi::ValidationErrorSchema>();
1074        spec.register_in_place::<rustapi_openapi::ValidationErrorBodySchema>();
1075        spec.register_in_place::<rustapi_openapi::FieldErrorSchema>();
1076    }
1077}
1078
1079// Json - Adds request body (Same as ValidatedJson)
1080impl<T: RustApiSchema> OperationModifier for Json<T> {
1081    fn update_operation(op: &mut Operation) {
1082        let mut ctx = SchemaCtx::new();
1083        let schema_ref = T::schema(&mut ctx);
1084
1085        let mut content = BTreeMap::new();
1086        content.insert(
1087            "application/json".to_string(),
1088            MediaType {
1089                schema: Some(schema_ref),
1090                example: None,
1091            },
1092        );
1093
1094        op.request_body = Some(RequestBody {
1095            description: None,
1096            required: Some(true),
1097            content,
1098        });
1099    }
1100
1101    fn register_components(spec: &mut rustapi_openapi::OpenApiSpec) {
1102        spec.register_in_place::<T>();
1103    }
1104}
1105
1106// Path - No op (handled by app routing)
1107impl<T> OperationModifier for Path<T> {
1108    fn update_operation(_op: &mut Operation) {}
1109}
1110
1111// Typed - No op
1112impl<T> OperationModifier for Typed<T> {
1113    fn update_operation(_op: &mut Operation) {}
1114}
1115
1116// Query - Extracts query params using field_schemas
1117impl<T: RustApiSchema> OperationModifier for Query<T> {
1118    fn update_operation(op: &mut Operation) {
1119        let mut ctx = SchemaCtx::new();
1120        if let Some(fields) = T::field_schemas(&mut ctx) {
1121            let new_params: Vec<Parameter> = fields
1122                .into_iter()
1123                .map(|(name, schema)| {
1124                    Parameter {
1125                        name,
1126                        location: "query".to_string(),
1127                        required: false, // Assume optional
1128                        deprecated: None,
1129                        description: None,
1130                        schema: Some(schema),
1131                    }
1132                })
1133                .collect();
1134
1135            op.parameters.extend(new_params);
1136        }
1137    }
1138
1139    fn register_components(spec: &mut rustapi_openapi::OpenApiSpec) {
1140        spec.register_in_place::<T>();
1141    }
1142}
1143
1144// State - No op
1145impl<T> OperationModifier for State<T> {
1146    fn update_operation(_op: &mut Operation) {}
1147}
1148
1149// Body - Generic binary body
1150impl OperationModifier for Body {
1151    fn update_operation(op: &mut Operation) {
1152        let mut content = BTreeMap::new();
1153        content.insert(
1154            "application/octet-stream".to_string(),
1155            MediaType {
1156                schema: Some(SchemaRef::Inline(
1157                    serde_json::json!({ "type": "string", "format": "binary" }),
1158                )),
1159                example: None,
1160            },
1161        );
1162
1163        op.request_body = Some(RequestBody {
1164            description: None,
1165            required: Some(true),
1166            content,
1167        });
1168    }
1169}
1170
1171// BodyStream - Generic binary stream
1172impl OperationModifier for BodyStream {
1173    fn update_operation(op: &mut Operation) {
1174        let mut content = BTreeMap::new();
1175        content.insert(
1176            "application/octet-stream".to_string(),
1177            MediaType {
1178                schema: Some(SchemaRef::Inline(
1179                    serde_json::json!({ "type": "string", "format": "binary" }),
1180                )),
1181                example: None,
1182            },
1183        );
1184
1185        op.request_body = Some(RequestBody {
1186            description: None,
1187            required: Some(true),
1188            content,
1189        });
1190    }
1191}
1192
1193// ResponseModifier implementations for extractors
1194
1195// Json<T> - 200 OK with schema T
1196impl<T: RustApiSchema> ResponseModifier for Json<T> {
1197    fn update_response(op: &mut Operation) {
1198        let mut ctx = SchemaCtx::new();
1199        let schema_ref = T::schema(&mut ctx);
1200
1201        let mut content = BTreeMap::new();
1202        content.insert(
1203            "application/json".to_string(),
1204            MediaType {
1205                schema: Some(schema_ref),
1206                example: None,
1207            },
1208        );
1209
1210        op.responses.insert(
1211            "200".to_string(),
1212            ResponseSpec {
1213                description: "Successful response".to_string(),
1214                content,
1215                headers: BTreeMap::new(),
1216            },
1217        );
1218    }
1219
1220    fn register_components(spec: &mut rustapi_openapi::OpenApiSpec) {
1221        spec.register_in_place::<T>();
1222    }
1223}
1224
1225// RustApiSchema implementations
1226
1227impl<T: RustApiSchema> RustApiSchema for Json<T> {
1228    fn schema(ctx: &mut SchemaCtx) -> SchemaRef {
1229        T::schema(ctx)
1230    }
1231}
1232
1233impl<T: RustApiSchema> RustApiSchema for ValidatedJson<T> {
1234    fn schema(ctx: &mut SchemaCtx) -> SchemaRef {
1235        T::schema(ctx)
1236    }
1237}
1238
1239impl<T: RustApiSchema> RustApiSchema for AsyncValidatedJson<T> {
1240    fn schema(ctx: &mut SchemaCtx) -> SchemaRef {
1241        T::schema(ctx)
1242    }
1243}
1244
1245impl<T: RustApiSchema> RustApiSchema for Query<T> {
1246    fn schema(ctx: &mut SchemaCtx) -> SchemaRef {
1247        T::schema(ctx)
1248    }
1249    fn field_schemas(ctx: &mut SchemaCtx) -> Option<BTreeMap<String, SchemaRef>> {
1250        T::field_schemas(ctx)
1251    }
1252}
1253
1254// ─── Pagination Extractors ──────────────────────────────────────────────────
1255
1256/// Default page number (1-indexed)
1257const DEFAULT_PAGE: u64 = 1;
1258/// Default items per page
1259const DEFAULT_PER_PAGE: u64 = 20;
1260/// Maximum items per page (prevents abuse)
1261const MAX_PER_PAGE: u64 = 100;
1262
1263/// Offset-based pagination extractor
1264///
1265/// Extracts pagination parameters from the query string.
1266/// Supports `?page=1&per_page=20` (defaults: page=1, per_page=20, max=100).
1267///
1268/// # Example
1269///
1270/// ```rust,ignore
1271/// use rustapi_core::Paginate;
1272///
1273/// async fn list_users(paginate: Paginate) -> impl IntoResponse {
1274///     let offset = paginate.offset();
1275///     let limit = paginate.limit();
1276///     // SELECT * FROM users LIMIT $limit OFFSET $offset
1277/// }
1278/// ```
1279#[derive(Debug, Clone, Copy)]
1280pub struct Paginate {
1281    /// Current page (1-indexed)
1282    pub page: u64,
1283    /// Items per page (capped at MAX_PER_PAGE)
1284    pub per_page: u64,
1285}
1286
1287impl Paginate {
1288    /// Create a new Paginate with given page and per_page
1289    pub fn new(page: u64, per_page: u64) -> Self {
1290        Self {
1291            page: page.max(1),
1292            per_page: per_page.clamp(1, MAX_PER_PAGE),
1293        }
1294    }
1295
1296    /// Calculate the SQL OFFSET value
1297    pub fn offset(&self) -> u64 {
1298        (self.page - 1) * self.per_page
1299    }
1300
1301    /// Get the LIMIT value (alias for per_page)
1302    pub fn limit(&self) -> u64 {
1303        self.per_page
1304    }
1305
1306    /// Build a `Paginated<T>` response from this pagination and results
1307    pub fn paginate<T>(self, items: Vec<T>, total: u64) -> crate::hateoas::Paginated<T> {
1308        crate::hateoas::Paginated {
1309            items,
1310            page: self.page,
1311            per_page: self.per_page,
1312            total,
1313        }
1314    }
1315}
1316
1317impl Default for Paginate {
1318    fn default() -> Self {
1319        Self {
1320            page: DEFAULT_PAGE,
1321            per_page: DEFAULT_PER_PAGE,
1322        }
1323    }
1324}
1325
1326impl FromRequestParts for Paginate {
1327    fn from_request_parts(req: &Request) -> Result<Self> {
1328        let query = req.query_string().unwrap_or("");
1329
1330        #[derive(serde::Deserialize)]
1331        struct PaginateQuery {
1332            page: Option<u64>,
1333            per_page: Option<u64>,
1334        }
1335
1336        let params: PaginateQuery = serde_urlencoded::from_str(query).unwrap_or(PaginateQuery {
1337            page: None,
1338            per_page: None,
1339        });
1340
1341        Ok(Paginate::new(
1342            params.page.unwrap_or(DEFAULT_PAGE),
1343            params.per_page.unwrap_or(DEFAULT_PER_PAGE),
1344        ))
1345    }
1346}
1347
1348/// Cursor-based pagination extractor
1349///
1350/// Extracts cursor pagination parameters from the query string.
1351/// Supports `?cursor=abc123&limit=20` (defaults: cursor=None, limit=20, max=100).
1352///
1353/// Cursor-based pagination is preferred for large datasets or real-time data
1354/// where offset-based pagination would skip or duplicate items.
1355///
1356/// # Example
1357///
1358/// ```rust,ignore
1359/// use rustapi_core::CursorPaginate;
1360///
1361/// async fn list_events(cursor: CursorPaginate) -> impl IntoResponse {
1362///     let limit = cursor.limit();
1363///     if let Some(after) = cursor.after() {
1364///         // SELECT * FROM events WHERE id > $after ORDER BY id LIMIT $limit
1365///     } else {
1366///         // SELECT * FROM events ORDER BY id LIMIT $limit
1367///     }
1368/// }
1369/// ```
1370#[derive(Debug, Clone)]
1371pub struct CursorPaginate {
1372    /// Opaque cursor token (None = start from beginning)
1373    pub cursor: Option<String>,
1374    /// Items per page (capped at MAX_PER_PAGE)
1375    pub per_page: u64,
1376}
1377
1378impl CursorPaginate {
1379    /// Create a new CursorPaginate
1380    pub fn new(cursor: Option<String>, per_page: u64) -> Self {
1381        Self {
1382            cursor,
1383            per_page: per_page.clamp(1, MAX_PER_PAGE),
1384        }
1385    }
1386
1387    /// Get the cursor value (if any)
1388    pub fn after(&self) -> Option<&str> {
1389        self.cursor.as_deref()
1390    }
1391
1392    /// Get the LIMIT value
1393    pub fn limit(&self) -> u64 {
1394        self.per_page
1395    }
1396
1397    /// Check if this is the first page (no cursor)
1398    pub fn is_first_page(&self) -> bool {
1399        self.cursor.is_none()
1400    }
1401}
1402
1403impl Default for CursorPaginate {
1404    fn default() -> Self {
1405        Self {
1406            cursor: None,
1407            per_page: DEFAULT_PER_PAGE,
1408        }
1409    }
1410}
1411
1412impl FromRequestParts for CursorPaginate {
1413    fn from_request_parts(req: &Request) -> Result<Self> {
1414        let query = req.query_string().unwrap_or("");
1415
1416        #[derive(serde::Deserialize)]
1417        struct CursorQuery {
1418            cursor: Option<String>,
1419            limit: Option<u64>,
1420        }
1421
1422        let params: CursorQuery = serde_urlencoded::from_str(query).unwrap_or(CursorQuery {
1423            cursor: None,
1424            limit: None,
1425        });
1426
1427        Ok(CursorPaginate::new(
1428            params.cursor,
1429            params.limit.unwrap_or(DEFAULT_PER_PAGE),
1430        ))
1431    }
1432}
1433
1434#[cfg(test)]
1435mod tests {
1436    include!(concat!(
1437        env!("CARGO_MANIFEST_DIR"),
1438        "/tests/support/extract_lib.rs"
1439    ));
1440}