Skip to main content

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}