axum/extract/
raw_form.rs

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