dioxus_fullstack/payloads/
multipart.rs

1#![allow(unreachable_code)]
2
3use crate::{ClientRequest, ClientResponse, IntoRequest};
4use axum::{
5    extract::{FromRequest, Request},
6    response::{IntoResponse, Response},
7};
8use dioxus_fullstack_core::RequestError;
9use dioxus_html::{FormData, FormEvent};
10use std::{prelude::rust_2024::Future, rc::Rc};
11
12#[cfg(feature = "server")]
13use axum::extract::multipart::{Field, MultipartError};
14
15/// A streaming multipart form data handler.
16///
17/// This type makes it easy to send and receive multipart form data in a streaming fashion by directly
18/// leveraging the corresponding `dioxus_html::FormData` and `axum::extract::Multipart` types.
19///
20/// On the client, you can create a `MultipartFormData` instance by using `.into()` on a `FormData` instance.
21/// This is typically done by using the `FormEvent`'s `.data()` method.
22///
23/// On the server, you can extract a `MultipartFormData` instance by using it as an extractor in your handler function.
24/// This gives you access to axum's `Multipart` extractor, which allows you to handle the various fields
25/// and files in the multipart form data.
26///
27/// ## Axum Usage
28///
29/// Extractor that parses `multipart/form-data` requests (commonly used with file uploads).
30///
31/// ⚠️ Since extracting multipart form data from the request requires consuming the body, the
32/// `Multipart` extractor must be *last* if there are multiple extractors in a handler.
33/// See ["the order of extractors"][order-of-extractors]
34///
35/// [order-of-extractors]: mod@crate::extract#the-order-of-extractors
36///
37/// # Large Files
38///
39/// For security reasons, by default, `Multipart` limits the request body size to 2MB.
40/// See [`DefaultBodyLimit`][default-body-limit] for how to configure this limit.
41///
42/// [default-body-limit]: crate::extract::DefaultBodyLimit
43pub struct MultipartFormData<T = ()> {
44    #[cfg(feature = "server")]
45    form: Option<axum::extract::Multipart>,
46
47    _client: Option<Rc<FormData>>,
48
49    _phantom: std::marker::PhantomData<T>,
50}
51
52impl MultipartFormData {
53    #[cfg(feature = "server")]
54    pub async fn next_field(&mut self) -> Result<Option<Field<'_>>, MultipartError> {
55        if let Some(form) = &mut self.form {
56            form.next_field().await
57        } else {
58            Ok(None)
59        }
60    }
61}
62
63impl<S> IntoRequest for MultipartFormData<S> {
64    fn into_request(
65        self,
66        _req: ClientRequest,
67    ) -> impl Future<Output = Result<ClientResponse, RequestError>> + 'static {
68        async move {
69            // On the web, it's just easier to convert the form data into a blob and then send that
70            // blob as the body of the request. This handles setting the correct headers, wiring
71            // up file uploads as streams, and encoding the request.
72            #[cfg(feature = "web")]
73            if cfg!(target_arch = "wasm32") {
74                let data = self._client.clone().ok_or_else(|| {
75                    RequestError::Builder("Failed to get FormData from event".into())
76                })?;
77
78                fn get_form_data(data: Rc<FormData>) -> Option<wasm_bindgen::JsValue> {
79                    use wasm_bindgen::JsCast;
80                    let event: &web_sys::Event = data.downcast()?;
81                    let target = event.target()?;
82                    let form: &web_sys::HtmlFormElement = target.dyn_ref()?;
83                    let data: web_sys::FormData = web_sys::FormData::new_with_form(form).ok()?;
84                    Some(data.into())
85                }
86
87                let js_form_data = get_form_data(data).ok_or_else(|| {
88                    RequestError::Builder("Failed to get FormData from event".into())
89                })?;
90
91                return _req.send_js_value(js_form_data).await;
92            }
93
94            // On non-web platforms, we actually need to read the values out of the FormData
95            // and construct a multipart form body manually.
96            #[cfg(not(target_arch = "wasm32"))]
97            {
98                let data = self._client.clone().ok_or_else(|| {
99                    RequestError::Builder("Failed to get FormData from event".into())
100                })?;
101
102                return _req.send_multipart(&data).await;
103            }
104
105            unimplemented!("Non web wasm32 clients are not supported yet")
106        }
107    }
108}
109impl<S: Send + Sync + 'static, D> FromRequest<S> for MultipartFormData<D> {
110    type Rejection = Response;
111
112    #[doc = " Perform the extraction."]
113    fn from_request(
114        req: Request,
115        state: &S,
116    ) -> impl Future<Output = Result<Self, Self::Rejection>> + Send {
117        #[cfg(feature = "server")]
118        return async move {
119            let form = axum::extract::multipart::Multipart::from_request(req, state)
120                .await
121                .map_err(|err| err.into_response())?;
122
123            Ok(MultipartFormData {
124                form: Some(form),
125                _client: None,
126                _phantom: std::marker::PhantomData,
127            })
128        };
129
130        #[cfg(not(feature = "server"))]
131        async {
132            use dioxus_fullstack_core::HttpError;
133
134            let _ = req;
135            let _ = state;
136            Err(HttpError::new(
137                http::StatusCode::INTERNAL_SERVER_ERROR,
138                "MultipartFormData extractor is not supported on non-server builds",
139            )
140            .into_response())
141        }
142    }
143}
144
145impl<T> From<Rc<FormData>> for MultipartFormData<T> {
146    fn from(_value: Rc<FormData>) -> Self {
147        MultipartFormData {
148            #[cfg(feature = "server")]
149            form: None,
150            _client: Some(_value),
151            _phantom: std::marker::PhantomData,
152        }
153    }
154}
155
156impl<T> From<FormEvent> for MultipartFormData<T> {
157    fn from(event: FormEvent) -> Self {
158        let data = event.data();
159        MultipartFormData {
160            #[cfg(feature = "server")]
161            form: None,
162            _client: Some(data),
163            _phantom: std::marker::PhantomData,
164        }
165    }
166}
167
168unsafe impl Send for MultipartFormData {}
169unsafe impl Sync for MultipartFormData {}