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}