Skip to main content

rust_webx_core/route/
bind.rs

1//! Binding a request struct from a `multipart/form-data` body.
2
3use crate::error::{Error, Result};
4use crate::form::{ActiveForm, FormDeserializer, MultipartForm};
5use crate::http::IHttpContext;
6use serde::de::DeserializeOwned;
7use std::sync::Arc;
8
9/// Bind `T` from a `multipart/form-data` body.
10///
11/// Returns `Ok(None)` when the request does not declare
12/// `Content-Type: multipart/form-data`, so the caller can fall back to JSON or
13/// parameter binding.
14///
15/// Route parameters are merged into the form as text values, so
16/// `POST /api/users/{id}/avatar` binds `{id}` alongside the uploaded file.
17/// Binding failures are reported as [`Error::Validation`] (HTTP 400) with the
18/// offending field named.
19pub async fn bind_form_request<T>(ctx: &mut dyn IHttpContext) -> Result<Option<T>>
20where
21    T: DeserializeOwned + Send + 'static,
22{
23    if !ctx.request().is_multipart() {
24        return Ok(None);
25    }
26
27    let route_params = ctx.request().route_params().clone();
28    let form: Arc<MultipartForm> = ctx.request_mut().multipart().await?;
29
30    let bound = ActiveForm::run(Arc::clone(&form), || {
31        T::deserialize(FormDeserializer::new(&form, &route_params))
32    })
33    .map_err(|err| Error::Validation(format!("invalid multipart form data: {err}")))?;
34
35    Ok(Some(bound))
36}