axum/extract/
raw_form.rs

1use axum_core::extract::{FromRequest, Request};
2use bytes::Bytes;
3use http::Method;
4
5use super::{
6    has_content_type,
7    rejection::{InvalidFormContentType, RawFormRejection},
8};
9
10/// Extractor that extracts raw form requests.
11///
12/// For `GET` requests it will extract the raw query. For other methods it extracts the raw
13/// `application/x-www-form-urlencoded` encoded request body.
14///
15/// # Example
16///
17/// ```rust,no_run
18/// use axum::{
19///     extract::RawForm,
20///     routing::get,
21///     Router
22/// };
23///
24/// async fn handler(RawForm(form): RawForm) {}
25///
26/// let app = Router::new().route("/", get(handler));
27/// # let _: Router = app;
28/// ```
29#[derive(Debug)]
30pub struct RawForm(pub Bytes);
31
32impl<S> FromRequest<S> for RawForm
33where
34    S: Send + Sync,
35{
36    type Rejection = RawFormRejection;
37
38    async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
39        if req.method() == Method::GET {
40            if let Some(query) = req.uri().query() {
41                return Ok(Self(Bytes::copy_from_slice(query.as_bytes())));
42            }
43
44            Ok(Self(Bytes::new()))
45        } else {
46            if !has_content_type(req.headers(), &mime::APPLICATION_WWW_FORM_URLENCODED) {
47                return Err(InvalidFormContentType.into());
48            }
49
50            Ok(Self(Bytes::from_request(req, state).await?))
51        }
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use axum_core::body::Body;
58    use http::{header::CONTENT_TYPE, Request};
59
60    use super::{InvalidFormContentType, RawForm, RawFormRejection};
61
62    use crate::extract::FromRequest;
63
64    async fn check_query(uri: &str, value: &[u8]) {
65        let req = Request::builder().uri(uri).body(Body::empty()).unwrap();
66
67        assert_eq!(RawForm::from_request(req, &()).await.unwrap().0, value);
68    }
69
70    async fn check_body(body: &'static [u8]) {
71        let req = Request::post("http://example.com/test")
72            .header(CONTENT_TYPE, mime::APPLICATION_WWW_FORM_URLENCODED.as_ref())
73            .body(Body::from(body))
74            .unwrap();
75
76        assert_eq!(RawForm::from_request(req, &()).await.unwrap().0, body);
77    }
78
79    #[crate::test]
80    async fn test_from_query() {
81        check_query("http://example.com/test", b"").await;
82
83        check_query("http://example.com/test?page=0&size=10", b"page=0&size=10").await;
84    }
85
86    #[crate::test]
87    async fn test_from_body() {
88        check_body(b"").await;
89
90        check_body(b"username=user&password=secure%20password").await;
91    }
92
93    #[crate::test]
94    async fn test_incorrect_content_type() {
95        let req = Request::post("http://example.com/test")
96            .body(Body::from("page=0&size=10"))
97            .unwrap();
98
99        assert!(matches!(
100            RawForm::from_request(req, &()).await.unwrap_err(),
101            RawFormRejection::InvalidFormContentType(InvalidFormContentType)
102        ))
103    }
104}