server_fn/codec/
multipart.rs

1use super::{Encoding, FromReq};
2use crate::{
3    error::{FromServerFnError, ServerFnErrorWrapper},
4    request::{browser::BrowserFormData, ClientReq, Req},
5    ContentType, IntoReq,
6};
7use futures::StreamExt;
8use http::Method;
9use multer::Multipart;
10use web_sys::FormData;
11
12/// Encodes multipart form data.
13///
14/// You should primarily use this if you are trying to handle file uploads.
15pub struct MultipartFormData;
16
17impl ContentType for MultipartFormData {
18    const CONTENT_TYPE: &'static str = "multipart/form-data";
19}
20
21impl Encoding for MultipartFormData {
22    const METHOD: Method = Method::POST;
23}
24
25/// Describes whether the multipart data is on the client side or the server side.
26#[derive(Debug)]
27pub enum MultipartData {
28    /// `FormData` from the browser.
29    Client(BrowserFormData),
30    /// Generic multipart form using [`multer`]. This implements [`Stream`](futures::Stream).
31    Server(multer::Multipart<'static>),
32}
33
34impl MultipartData {
35    /// Extracts the inner data to handle as a stream.
36    ///
37    /// On the server side, this always returns `Some(_)`. On the client side, always returns `None`.
38    pub fn into_inner(self) -> Option<Multipart<'static>> {
39        match self {
40            MultipartData::Client(_) => None,
41            MultipartData::Server(data) => Some(data),
42        }
43    }
44
45    /// Extracts the inner form data on the client side.
46    ///
47    /// On the server side, this always returns `None`. On the client side, always returns `Some(_)`.
48    pub fn into_client_data(self) -> Option<BrowserFormData> {
49        match self {
50            MultipartData::Client(data) => Some(data),
51            MultipartData::Server(_) => None,
52        }
53    }
54}
55
56impl From<FormData> for MultipartData {
57    fn from(value: FormData) -> Self {
58        MultipartData::Client(value.into())
59    }
60}
61
62impl<E: FromServerFnError, T, Request> IntoReq<MultipartFormData, Request, E>
63    for T
64where
65    Request: ClientReq<E, FormData = BrowserFormData>,
66    T: Into<MultipartData>,
67{
68    fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
69        let multi = self.into();
70        Request::try_new_post_multipart(
71            path,
72            accepts,
73            multi.into_client_data().unwrap(),
74        )
75    }
76}
77
78impl<E, T, Request> FromReq<MultipartFormData, Request, E> for T
79where
80    Request: Req<E> + Send + 'static,
81    T: From<MultipartData>,
82    E: FromServerFnError + Send + Sync,
83{
84    async fn from_req(req: Request) -> Result<Self, E> {
85        let boundary = req
86            .to_content_type()
87            .and_then(|ct| multer::parse_boundary(ct).ok())
88            .expect("couldn't parse boundary");
89        let stream = req.try_into_stream()?;
90        let data = multer::Multipart::new(
91            stream.map(|data| data.map_err(|e| ServerFnErrorWrapper(E::de(e)))),
92            boundary,
93        );
94        Ok(MultipartData::Server(data).into())
95    }
96}