1use crate::FromMultipart;
2use axum::extract::FromRequest;
3use axum::http::Request;
4use modo::app::AppState;
5use modo::error::{Error, HttpError};
6use modo::validate::Validate;
7use std::ops::Deref;
8
9pub struct MultipartForm<T>(pub T);
21
22impl<T> Deref for MultipartForm<T> {
23 type Target = T;
24 fn deref(&self) -> &Self::Target {
25 &self.0
26 }
27}
28
29impl<T> MultipartForm<T> {
30 pub fn into_inner(self) -> T {
32 self.0
33 }
34}
35
36impl<T: Validate> MultipartForm<T> {
37 pub fn validate(&self) -> Result<(), Error> {
42 self.0.validate()
43 }
44}
45
46impl<T> FromRequest<AppState> for MultipartForm<T>
47where
48 T: FromMultipart + 'static,
49{
50 type Rejection = Error;
51
52 async fn from_request(
53 req: Request<axum::body::Body>,
54 state: &AppState,
55 ) -> Result<Self, Self::Rejection> {
56 let mut multipart = axum::extract::Multipart::from_request(req, state)
57 .await
58 .map_err(|e| HttpError::BadRequest.with_message(format!("{e}")))?;
59 let registered_config = state.services.get::<crate::config::UploadConfig>();
60 let config = match registered_config.as_deref() {
61 Some(cfg) => cfg,
62 None => {
63 return Err(Error::internal(
64 "UploadConfig not configured — register it via .service(upload_config)",
65 ));
66 }
67 };
68 let max_file_size = config.max_file_size.as_ref().and_then(|s| {
69 modo::config::parse_size(s)
70 .inspect_err(|e| {
71 tracing::warn!(
72 size = %s,
73 error = %e,
74 "failed to parse max_file_size from UploadConfig, ignoring limit"
75 );
76 })
77 .ok()
78 });
79 let mut value = T::from_multipart(&mut multipart, max_file_size).await?;
80 modo::sanitize::auto_sanitize(&mut value);
81 Ok(MultipartForm(value))
82 }
83}