rustapi_core/
request.rs

1//! Request types for RustAPI
2//!
3//! This module provides the [`Request`] type which wraps an incoming HTTP request
4//! and provides access to all its components.
5//!
6//! # Accessing Request Data
7//!
8//! While extractors are the preferred way to access request data in handlers,
9//! the `Request` type provides direct access when needed:
10//!
11//! ```rust,ignore
12//! // In middleware or custom extractors
13//! fn process_request(req: &Request) {
14//!     let method = req.method();
15//!     let path = req.path();
16//!     let headers = req.headers();
17//!     let query = req.query_string();
18//! }
19//! ```
20//!
21//! # Path Parameters
22//!
23//! Path parameters extracted from the URL pattern are available via:
24//!
25//! ```rust,ignore
26//! // For route "/users/{id}"
27//! let id = req.path_param("id");
28//! let all_params = req.path_params();
29//! ```
30//!
31//! # Request Body
32//!
33//! The body can only be consumed once:
34//!
35//! ```rust,ignore
36//! if let Some(body) = req.take_body() {
37//!     // Process body bytes
38//! }
39//! // Subsequent calls return None
40//! ```
41
42use crate::path_params::PathParams;
43use bytes::Bytes;
44use http::{request::Parts, Extensions, HeaderMap, Method, Uri, Version};
45use http_body_util::BodyExt;
46use hyper::body::Incoming;
47use std::sync::Arc;
48
49/// Internal representation of the request body state
50pub(crate) enum BodyVariant {
51    Buffered(Bytes),
52    Streaming(Incoming),
53    Consumed,
54}
55
56/// HTTP Request wrapper
57///
58/// Provides access to all parts of an incoming HTTP request.
59pub struct Request {
60    pub(crate) parts: Parts,
61    pub(crate) body: BodyVariant,
62    pub(crate) state: Arc<Extensions>,
63    pub(crate) path_params: PathParams,
64}
65
66impl Request {
67    /// Create a new request from parts
68    pub(crate) fn new(
69        parts: Parts,
70        body: BodyVariant,
71        state: Arc<Extensions>,
72        path_params: PathParams,
73    ) -> Self {
74        Self {
75            parts,
76            body,
77            state,
78            path_params,
79        }
80    }
81
82    /// Get the HTTP method
83    pub fn method(&self) -> &Method {
84        &self.parts.method
85    }
86
87    /// Get the URI
88    pub fn uri(&self) -> &Uri {
89        &self.parts.uri
90    }
91
92    /// Get the HTTP version
93    pub fn version(&self) -> Version {
94        self.parts.version
95    }
96
97    /// Get the headers
98    pub fn headers(&self) -> &HeaderMap {
99        &self.parts.headers
100    }
101
102    /// Get request extensions
103    pub fn extensions(&self) -> &Extensions {
104        &self.parts.extensions
105    }
106
107    /// Get mutable extensions
108    pub fn extensions_mut(&mut self) -> &mut Extensions {
109        &mut self.parts.extensions
110    }
111
112    /// Get the request path
113    pub fn path(&self) -> &str {
114        self.parts.uri.path()
115    }
116
117    /// Get the query string
118    pub fn query_string(&self) -> Option<&str> {
119        self.parts.uri.query()
120    }
121
122    /// Take the body bytes (can only be called once)
123    ///
124    /// Returns None if the body is streaming or already consumed.
125    /// Use `load_body().await` first if you need to ensure the body is available as bytes.
126    pub fn take_body(&mut self) -> Option<Bytes> {
127        match std::mem::replace(&mut self.body, BodyVariant::Consumed) {
128            BodyVariant::Buffered(bytes) => Some(bytes),
129            other => {
130                self.body = other;
131                None
132            }
133        }
134    }
135
136    /// Take the body as a stream (can only be called once)
137    pub fn take_stream(&mut self) -> Option<Incoming> {
138        match std::mem::replace(&mut self.body, BodyVariant::Consumed) {
139            BodyVariant::Streaming(stream) => Some(stream),
140            other => {
141                self.body = other;
142                None
143            }
144        }
145    }
146
147    /// Ensure the body is loaded into memory.
148    ///
149    /// If the body is streaming, this collects it into Bytes.
150    /// If already buffered, does nothing.
151    /// Returns error if collection fails.
152    pub async fn load_body(&mut self) -> Result<(), crate::error::ApiError> {
153        // We moved the body out to check, put it back if buffered or new buffer
154        let new_body = match std::mem::replace(&mut self.body, BodyVariant::Consumed) {
155            BodyVariant::Streaming(incoming) => {
156                let collected = incoming
157                    .collect()
158                    .await
159                    .map_err(|e| crate::error::ApiError::bad_request(e.to_string()))?;
160                BodyVariant::Buffered(collected.to_bytes())
161            }
162            BodyVariant::Buffered(b) => BodyVariant::Buffered(b),
163            BodyVariant::Consumed => BodyVariant::Consumed,
164        };
165        self.body = new_body;
166        Ok(())
167    }
168
169    /// Get path parameters
170    pub fn path_params(&self) -> &PathParams {
171        &self.path_params
172    }
173
174    /// Get a specific path parameter
175    pub fn path_param(&self, name: &str) -> Option<&String> {
176        self.path_params.get(name)
177    }
178
179    /// Get shared state
180    pub fn state(&self) -> &Arc<Extensions> {
181        &self.state
182    }
183
184    /// Create a test request from an http::Request
185    ///
186    /// This is useful for testing middleware and extractors.
187    #[cfg(any(test, feature = "test-utils"))]
188    pub fn from_http_request<B>(req: http::Request<B>, body: Bytes) -> Self {
189        let (parts, _) = req.into_parts();
190        Self {
191            parts,
192            body: BodyVariant::Buffered(body),
193            state: Arc::new(Extensions::new()),
194            path_params: PathParams::new(),
195        }
196    }
197    /// Try to clone the request.
198    ///
199    /// This creates a deep copy of the request, including headers, body (if present),
200    /// path params, and shared state.
201    ///
202    /// Returns None if the body is streaming (cannot be cloned) or already consumed.
203    pub fn try_clone(&self) -> Option<Self> {
204        let mut builder = http::Request::builder()
205            .method(self.method().clone())
206            .uri(self.uri().clone())
207            .version(self.version());
208
209        if let Some(headers) = builder.headers_mut() {
210            *headers = self.headers().clone();
211        }
212
213        let req = builder.body(()).ok()?;
214        let (parts, _) = req.into_parts();
215
216        let new_body = match &self.body {
217            BodyVariant::Buffered(b) => BodyVariant::Buffered(b.clone()),
218            BodyVariant::Streaming(_) => return None, // Cannot clone stream
219            BodyVariant::Consumed => return None,
220        };
221
222        Some(Self {
223            parts,
224            body: new_body,
225            state: self.state.clone(),
226            path_params: self.path_params.clone(),
227        })
228    }
229}
230
231impl std::fmt::Debug for Request {
232    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233        f.debug_struct("Request")
234            .field("method", &self.parts.method)
235            .field("uri", &self.parts.uri)
236            .field("version", &self.parts.version)
237            .finish()
238    }
239}