Skip to main content

serde_qs/
axum.rs

1//! Functionality for using `serde_qs` with `axum`.
2//!
3//! Enable with the `axum` feature.
4
5use axum_framework as axum;
6
7use std::sync::Arc;
8
9use crate::error::Error as QsError;
10
11use axum::{
12    BoxError, Error,
13    body::Body,
14    extract::{Extension, FromRequest, FromRequestParts, RawForm, Request},
15    http::StatusCode,
16    response::{IntoResponse, Response},
17};
18
19/// Extract typed information from the request's query.
20///
21/// ## Example
22///
23/// ```rust
24/// # use axum_framework as axum;
25/// use serde_qs::axum::QsQuery;
26/// use serde_qs::Config;
27/// use axum::{response::IntoResponse, routing::get, Router, body::Body};
28///
29/// #[derive(serde::Deserialize)]
30/// pub struct UsersFilter {
31///    id: Vec<u64>,
32/// }
33///
34/// async fn filter_users(
35///     QsQuery(info): QsQuery<UsersFilter>
36/// ) -> impl IntoResponse {
37///     info.id
38///         .iter()
39///         .map(|i| i.to_string())
40///         .collect::<Vec<String>>()
41///         .join(", ")
42/// }
43///
44/// fn main() {
45///     let app = Router::<()>::new()
46///         .route("/users", get(filter_users));
47/// }
48pub use crate::web::QsQuery;
49
50impl<T, S> FromRequestParts<S> for QsQuery<T>
51where
52    T: serde::de::DeserializeOwned,
53    S: Send + Sync,
54{
55    type Rejection = QsQueryRejection;
56
57    async fn from_request_parts(
58        parts: &mut axum::http::request::Parts,
59        state: &S,
60    ) -> Result<Self, Self::Rejection> {
61        let qs_config = Extension::<QsQueryConfig>::from_request_parts(parts, state)
62            .await
63            .map_or_else(|_| DEFAULT_QUERY_CONFIG.clone(), |ext| ext.0);
64        let error_handler = qs_config.error_handler.clone();
65        let query = parts.uri.query().unwrap_or_default();
66        match qs_config.config.deserialize_str::<T>(query) {
67            Ok(value) => Ok(QsQuery(value)),
68            Err(err) => match error_handler {
69                Some(handler) => Err((handler)(err)),
70                None => Err(QsQueryRejection::new(err, StatusCode::BAD_REQUEST)),
71            },
72        }
73    }
74}
75
76/// Extract typed information from the request's formdata.
77///
78/// For a `GET` request, this will extract the query string as form data.
79/// Otherwise, it will extract the body of the request as form data.
80///
81/// By default this will use the `use_form_encoding` option from `crate::Config`.
82/// If you want to use a different configuration, you can set it using
83/// `QsQueryConfig` in your router.
84///
85/// ## Example
86///
87/// ```rust
88/// # use axum_framework as axum;
89/// use serde_qs::axum::QsQuery;
90/// use serde_qs::Config;
91/// use axum::{response::IntoResponse, routing::get, Router, body::Body};
92///
93/// #[derive(serde::Deserialize)]
94/// pub struct UsersFilter {
95///    id: Vec<u64>,
96/// }
97///
98/// async fn filter_users(
99///     QsQuery(info): QsQuery<UsersFilter>
100/// ) -> impl IntoResponse {
101///     info.id
102///         .iter()
103///         .map(|i| i.to_string())
104///         .collect::<Vec<String>>()
105///         .join(", ")
106/// }
107///
108/// fn main() {
109///     let app = Router::<()>::new()
110///         .route("/users", get(filter_users));
111/// }
112pub use crate::web::QsForm;
113
114impl<T, S> FromRequest<S> for QsForm<T>
115where
116    T: serde::de::DeserializeOwned,
117    S: Send + Sync,
118{
119    type Rejection = QsQueryRejection;
120
121    async fn from_request(request: Request<Body>, state: &S) -> Result<Self, Self::Rejection> {
122        let (mut parts, body) = request.into_parts();
123        let qs_config = Extension::<QsQueryConfig>::from_request_parts(&mut parts, state)
124            .await
125            .map_or_else(|_| DEFAULT_FORM_CONFIG.clone(), |ext| ext.0);
126        let error_handler = qs_config.error_handler.clone();
127        // extract the form data from the request
128        let request = Request::from_parts(parts, body);
129        let RawForm(form_data) = RawForm::from_request(request, state)
130            .await
131            .map_err(|err| QsQueryRejection::new(err, StatusCode::BAD_REQUEST))?;
132        match qs_config.config.deserialize_bytes::<T>(&form_data) {
133            Ok(value) => Ok(QsForm(value)),
134            Err(err) => match error_handler {
135                Some(handler) => Err((handler)(err)),
136                None => Err(QsQueryRejection::new(err, StatusCode::BAD_REQUEST)),
137            },
138        }
139    }
140}
141
142#[derive(Debug)]
143/// Rejection type for extractors that deserialize query strings
144pub struct QsQueryRejection {
145    error: axum::Error,
146    status: StatusCode,
147}
148
149impl std::fmt::Display for QsQueryRejection {
150    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151        write!(
152            f,
153            "Failed to deserialize query string. Error: {}",
154            self.error,
155        )
156    }
157}
158
159impl QsQueryRejection {
160    /// Create new rejection
161    pub fn new<E>(error: E, status: StatusCode) -> Self
162    where
163        E: Into<BoxError>,
164    {
165        QsQueryRejection {
166            error: Error::new(error),
167            status,
168        }
169    }
170}
171
172impl IntoResponse for QsQueryRejection {
173    fn into_response(self) -> Response {
174        let mut res = self.to_string().into_response();
175        *res.status_mut() = self.status;
176        res
177    }
178}
179
180impl std::error::Error for QsQueryRejection {
181    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
182        Some(&self.error)
183    }
184}
185
186#[derive(Clone)]
187/// Query extractor configuration
188///
189/// QsQueryConfig wraps [`Config`](crate::de::Config) and implement [`Clone`]
190/// for [`FromRequest`](https://docs.rs/axum/0.5/axum/extract/trait.FromRequest.html)
191///
192/// ## Example
193///
194/// ```rust
195/// # use axum_framework as axum;
196/// use serde_qs::axum::{QsQuery, QsQueryConfig, QsQueryRejection};
197/// use serde_qs::Config;
198/// use axum::{
199///     response::IntoResponse,
200///     routing::get,
201///     Router,
202///     body::Body,
203///     extract::Extension,
204///     http::StatusCode,
205/// };
206/// use std::sync::Arc;
207///
208/// #[derive(serde::Deserialize)]
209/// pub struct UsersFilter {
210///    id: Vec<u64>,
211/// }
212///
213/// async fn filter_users(
214///     QsQuery(info): QsQuery<UsersFilter>
215/// ) -> impl IntoResponse {
216///     info.id
217///         .iter()
218///         .map(|i| i.to_string())
219///         .collect::<Vec<String>>()
220///         .join(", ")
221/// }
222///
223/// fn main() {
224///     let app = Router::<()>::new()
225///         .route("/users", get(filter_users))
226///         .layer(Extension(QsQueryConfig::new().config(Config::default())
227///             .error_handler(|err| {
228///                 QsQueryRejection::new(err, StatusCode::UNPROCESSABLE_ENTITY)
229///         })));
230/// }
231pub struct QsQueryConfig {
232    config: crate::Config,
233    error_handler: Option<Arc<dyn Fn(QsError) -> QsQueryRejection + Send + Sync>>,
234}
235
236static DEFAULT_QUERY_CONFIG: QsQueryConfig = QsQueryConfig {
237    error_handler: None,
238    config: crate::Config::new(),
239};
240
241static DEFAULT_FORM_CONFIG: QsQueryConfig = QsQueryConfig {
242    error_handler: None,
243    config: crate::Config::new().use_form_encoding(true),
244};
245
246impl QsQueryConfig {
247    /// Create new config wrapper
248    pub const fn new() -> Self {
249        Self {
250            config: crate::Config::new(),
251            error_handler: None,
252        }
253    }
254
255    pub fn config(mut self, config: crate::Config) -> Self {
256        self.config = config;
257        self
258    }
259
260    /// Set custom error handler
261    pub fn error_handler<F>(mut self, f: F) -> Self
262    where
263        F: Fn(QsError) -> QsQueryRejection + Send + Sync + 'static,
264    {
265        self.error_handler = Some(Arc::new(f));
266        self
267    }
268}
269
270impl Default for QsQueryConfig {
271    fn default() -> Self {
272        Self::new()
273    }
274}