ntex/web/extract.rs
1//! Request extractors
2use std::convert::Infallible;
3
4use super::{HttpRequest, State, WebResponseError};
5use crate::http::Payload;
6
7/// Turns request data into a value that a handler can use.
8///
9/// Each argument accepted by a handler registered with
10/// [`Route::to()`](super::Route::to) is an extractor. Before calling the
11/// handler, ntex asks each argument type to create its value from the incoming
12/// request.
13///
14/// An extractor can look at the application state, request headers, path, and
15/// other request information. It can also read the request body through the
16/// payload. Extractors run in the same order as the handler arguments and
17/// share that payload, so an extractor that reads the body may leave nothing
18/// for the next one. For that reason, a handler should normally have only one
19/// body-reading extractor.
20///
21/// When an extractor returns an error, ntex turns it into an HTTP response and
22/// skips the handler. The error must implement [`WebResponseError`] for the
23/// application. If the handler should still run, use `Option<T>` to receive
24/// `None`, or `Result<T, T::Error>` to receive the original error.
25///
26/// # Example
27///
28/// A custom extractor can turn a request header into a handler argument:
29///
30/// ```rust
31/// use ntex::http::Payload;
32/// use ntex::web::{self, FromRequest, HttpRequest, InternalError};
33///
34/// struct ClientName(String);
35///
36/// impl<St: web::State> FromRequest<St> for ClientName {
37/// type Error = InternalError<&'static str>;
38///
39/// async fn from_request(_: &St, req: &HttpRequest, _: &mut Payload) -> Result<Self, Self::Error> {
40/// req.headers()
41/// .get("x-client-name")
42/// .and_then(|value| value.to_str().ok())
43/// .map(|value| ClientName(value.to_owned()))
44/// .ok_or_else(|| web::error::ErrorBadRequest("Missing client name"))
45/// }
46/// }
47///
48/// async fn hello(client: ClientName) -> String {
49/// format!("Hello, {}!", client.0)
50/// }
51///
52/// let app = web::App::default().route("/", web::get().to(hello));
53/// ```
54pub trait FromRequest<St>: Sized {
55 /// The error returned when extraction fails.
56 ///
57 /// For a route handler, ntex must be able to turn this error into an HTTP
58 /// response through [`WebResponseError`].
59 type Error;
60
61 /// Creates the extractor value for this request.
62 async fn from_request(
63 st: &St,
64 req: &HttpRequest,
65 payload: &mut Payload,
66 ) -> Result<Self, Self::Error>;
67}
68
69/// Optionally extract a field from the request
70///
71/// If the `FromRequest` for T fails, return None rather than returning an error response
72///
73/// ## Example
74///
75/// ```rust
76/// use ntex::http;
77/// use ntex::web::{self, error, App, HttpRequest, FromRequest, InternalError};
78/// use rand;
79///
80/// #[derive(Debug, serde::Deserialize)]
81/// struct Thing {
82/// name: String
83/// }
84///
85/// impl<St> FromRequest<St> for Thing {
86/// type Error = InternalError<&'static str>;
87///
88/// async fn from_request(st: &St, req: &HttpRequest, payload: &mut http::Payload) -> Result<Self, Self::Error> {
89/// if rand::random() {
90/// Ok(Thing { name: "thingy".into() })
91/// } else {
92/// Err(error::ErrorBadRequest("no luck"))
93/// }
94/// }
95/// }
96///
97/// /// extract `Thing` from request
98/// async fn index(supplied_thing: Option<Thing>) -> String {
99/// match supplied_thing {
100/// // Puns not intended
101/// Some(thing) => format!("Got something: {:?}", thing),
102/// None => format!("No thing!")
103/// }
104/// }
105///
106/// fn main() {
107/// let app = App::default().service(
108/// web::resource("/users/{first}").route(
109/// web::post().to(index))
110/// );
111/// }
112/// ```
113impl<St, T> FromRequest<St> for Option<T>
114where
115 St: State,
116 T: FromRequest<St>,
117 <T as FromRequest<St>>::Error: WebResponseError<St, St::Error>,
118{
119 type Error = Infallible;
120
121 #[inline]
122 async fn from_request(
123 st: &St,
124 req: &HttpRequest,
125 payload: &mut Payload,
126 ) -> Result<Option<T>, Self::Error> {
127 match T::from_request(st, req, payload).await {
128 Ok(v) => Ok(Some(v)),
129 Err(e) => {
130 log::debug!("Error for Option<T> extractor: {e}");
131 Ok(None)
132 }
133 }
134 }
135}
136
137/// Optionally extract a field from the request or extract the Error if unsuccessful
138///
139/// If the `FromRequest` for T fails, inject Err into handler rather than returning an error response
140///
141/// ## Example
142///
143/// ```rust
144/// use ntex::http;
145/// use ntex::web::{self, error, App, State, HttpRequest, FromRequest, InternalError};
146/// use rand;
147///
148/// #[derive(Debug, serde::Deserialize)]
149/// struct Thing {
150/// name: String
151/// }
152///
153/// impl<St: State> FromRequest<St> for Thing {
154/// type Error = InternalError<&'static str>;
155///
156/// async fn from_request(st: &St, req: &HttpRequest, payload: &mut http::Payload) -> Result<Thing, Self::Error> {
157/// if rand::random() {
158/// Ok(Thing { name: "thingy".into() })
159/// } else {
160/// Err(error::ErrorBadRequest("no luck"))
161/// }
162/// }
163/// }
164///
165/// /// extract `Thing` from request
166/// async fn index(supplied_thing: Result<Thing, InternalError<&'static str>>) -> String {
167/// match supplied_thing {
168/// Ok(thing) => format!("Got thing: {:?}", thing),
169/// Err(e) => format!("Error extracting thing: {}", e)
170/// }
171/// }
172///
173/// fn main() {
174/// let app = App::default().service(
175/// web::resource("/users/{first}").route(web::post().to(index))
176/// );
177/// }
178/// ```
179impl<St, T> FromRequest<St> for Result<T, T::Error>
180where
181 St: State,
182 T: FromRequest<St>,
183{
184 type Error = T::Error;
185
186 #[inline]
187 async fn from_request(
188 st: &St,
189 req: &HttpRequest,
190 payload: &mut Payload,
191 ) -> Result<Self, Self::Error> {
192 match T::from_request(st, req, payload).await {
193 Ok(v) => Ok(Ok(v)),
194 Err(e) => Ok(Err(e)),
195 }
196 }
197}
198
199#[doc(hidden)]
200impl<St: State> FromRequest<St> for () {
201 type Error = Infallible;
202
203 #[inline]
204 async fn from_request(_: &St, _: &HttpRequest, _: &mut Payload) -> Result<(), Self::Error> {
205 Ok(())
206 }
207}
208
209macro_rules! tuple_from_req {
210 ($(#[$meta:meta])* $(($T:ident, $t:ident)),*) => {
211 $(#[$meta])*
212 impl<St, $($T,)+> FromRequest<St> for ($($T,)+)
213 where
214 St: State,
215 $($T: FromRequest<St> + 'static,)+
216 $(<$T as $crate::web::FromRequest<St>>::Error: WebResponseError<St, St::Error>),+
217 {
218 type Error = $crate::web::InternalError<&'static str>;
219
220 async fn from_request(st: &St, req: &HttpRequest, payload: &mut Payload) -> Result<($($T,)+), Self::Error> {
221 Ok((
222 $($T::from_request(st, req, payload).await.map_err(
223 |e|
224 $crate::web::InternalError::from_response("Error", e.error_response(st)))?,)+
225 ))
226 }
227 }
228 }
229}
230
231#[allow(non_snake_case, clippy::wildcard_imports)]
232#[rustfmt::skip]
233mod m {
234 use super::*;
235 use variadics_please::all_tuples;
236
237 all_tuples!(#[doc(fake_variadic)] tuple_from_req, 1, 12, T, t);
238}
239
240#[cfg(test)]
241mod tests {
242 use crate::http::header;
243 use crate::util::Bytes;
244 use crate::web::error::UrlencodedError;
245 use crate::web::test::{TestRequest, from_request};
246 use crate::web::types::{Form, FormConfig};
247
248 #[derive(serde::Deserialize, Debug, PartialEq)]
249 struct Info {
250 hello: String,
251 }
252
253 #[crate::rt_test]
254 async fn test_option() {
255 let (req, mut pl, ()) =
256 TestRequest::with_header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
257 .app_state(FormConfig::default().limit(4096))
258 .to_http_parts();
259
260 let r = from_request::<_, Option<Form<Info>>>(&(), &req, &mut pl)
261 .await
262 .unwrap();
263 assert_eq!(r, None);
264
265 let (req, mut pl, ()) =
266 TestRequest::with_header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
267 .header(header::CONTENT_LENGTH, "9")
268 .payload(Bytes::from_static(b"hello=world"))
269 .to_http_parts();
270
271 let r = from_request::<_, Option<Form<Info>>>(&(), &req, &mut pl)
272 .await
273 .unwrap();
274 assert_eq!(
275 r,
276 Some(Form(Info {
277 hello: "world".into()
278 }))
279 );
280
281 let (req, mut pl, ()) =
282 TestRequest::with_header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
283 .header(header::CONTENT_LENGTH, "9")
284 .payload(Bytes::from_static(b"bye=world"))
285 .to_http_parts();
286
287 let r = from_request::<_, Option<Form<Info>>>(&(), &req, &mut pl)
288 .await
289 .unwrap();
290 assert_eq!(r, None);
291 }
292
293 #[crate::rt_test]
294 async fn test_result() {
295 let (req, mut pl, ()) =
296 TestRequest::with_header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
297 .header(header::CONTENT_LENGTH, "11")
298 .payload(Bytes::from_static(b"hello=world"))
299 .to_http_parts();
300
301 let r = from_request::<_, Result<Form<Info>, UrlencodedError>>(&(), &req, &mut pl)
302 .await
303 .unwrap();
304 assert_eq!(
305 r.unwrap(),
306 Form(Info {
307 hello: "world".into()
308 })
309 );
310
311 let (req, mut pl, ()) =
312 TestRequest::with_header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
313 .header(header::CONTENT_LENGTH, "9")
314 .payload(Bytes::from_static(b"bye=world"))
315 .to_http_parts();
316
317 let r = from_request::<_, Result<Form<Info>, UrlencodedError>>(&(), &req, &mut pl)
318 .await
319 .unwrap();
320 assert!(r.is_err());
321 }
322}