axum_bindform/
lib.rs

1#![warn(missing_docs)]
2//! Bind XML, JSON, URL-encoded or query-string form data in Axum.
3//!
4//! # Example
5//!
6//! ```rust
7//! use axum::http::StatusCode;
8//! use axum_bindform::{BindForm, TryBindForm};
9//! use serde::{Deserialize, Serialize};
10//!
11//! #[derive(Deserialize, Serialize)]
12//! struct Human {
13//!     name: String,
14//!     age: u8,
15//! }
16//!
17//! async fn greet_human(BindForm(form): BindForm<Human>) -> String {
18//!     format!("Hello {} year old named {}!", form.age, form.name)
19//! }
20//!
21//! async fn try_greet_human(
22//!     TryBindForm(form): TryBindForm<Human>,
23//! ) -> Result<String, (StatusCode, String)> {
24//!     let form = form.map_err(|e| {
25//!         (
26//!             StatusCode::BAD_REQUEST,
27//!             format!("Error parsing form: {}", e),
28//!         )
29//!     })?;
30//!     Ok(format!("Hello {} year old named {}!", form.age, form.name))
31//! }
32//! ```
33use std::convert::Infallible;
34
35use axum::{
36    async_trait,
37    body::HttpBody,
38    extract::{rejection::BytesRejection, FromRequest},
39    http::Request,
40    response::IntoResponse,
41    BoxError,
42};
43use serde::de::DeserializeOwned;
44use thiserror::Error;
45
46mod bind;
47
48/// Errors that can occur when binding.
49#[derive(Debug, Error)]
50#[non_exhaustive]
51pub enum BindError {
52    /// Invalid mime type.
53    #[error("invalid mime type")]
54    InvalidMimeType,
55    /// Body read error.
56    #[error("body read error: {0}")]
57    BodyReadError(BytesRejection),
58    /// JSON deserialization error.
59    #[cfg(feature = "json")]
60    #[error("json error: {0}")]
61    JsonError(serde_json::Error),
62    /// URL-encoded deserialization error.
63    #[cfg(feature = "urlencoded")]
64    #[error("urlencoded error: {0}")]
65    UrlEncodedError(serde_urlencoded::de::Error),
66    /// XML deserialization error.
67    #[cfg(feature = "xml")]
68    #[error("xml error: {0}")]
69    XmlError(serde_xml_rs::Error),
70}
71
72/// Result of binding.
73pub type BindResult<T> = Result<T, BindError>;
74
75/// Try to bind form data in Axum and return the result, does not reject.
76pub struct TryBindForm<T: DeserializeOwned>(pub BindResult<T>);
77
78#[async_trait]
79impl<S, B, T> FromRequest<S, B> for TryBindForm<T>
80where
81    T: DeserializeOwned,
82    B: HttpBody + Send + 'static,
83    B::Data: Send,
84    B::Error: Into<BoxError>,
85    S: Send + Sync,
86{
87    type Rejection = Infallible;
88
89    async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
90        let deserialized = bind::bind_request(req, state).await;
91
92        Ok(TryBindForm(deserialized))
93    }
94}
95
96/// Bind form data in Axum, rejects on error.
97pub struct BindForm<T: DeserializeOwned>(pub T);
98
99/// Rejection for [`BindForm`].
100pub struct BindFormRejection(BindError);
101
102impl IntoResponse for BindFormRejection {
103    fn into_response(self) -> axum::response::Response {
104        let body = format!("{}", self.0);
105        (
106            axum::http::StatusCode::BAD_REQUEST,
107            [(axum::http::header::CONTENT_TYPE, "text/plain")],
108            body,
109        )
110            .into_response()
111    }
112}
113
114#[async_trait]
115impl<S, B, T> FromRequest<S, B> for BindForm<T>
116where
117    T: DeserializeOwned,
118    B: HttpBody + Send + 'static,
119    B::Data: Send,
120    B::Error: Into<BoxError>,
121    S: Send + Sync,
122{
123    type Rejection = BindFormRejection;
124
125    async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
126        let deserialized = bind::bind_request(req, state).await;
127
128        match deserialized {
129            Ok(deserialized) => Ok(BindForm(deserialized)),
130            Err(err) => Err(BindFormRejection(err)),
131        }
132    }
133}