modo/extractor/query.rs
1use axum::extract::FromRequestParts;
2use http::request::Parts;
3use serde::de::DeserializeOwned;
4
5use crate::sanitize::Sanitize;
6
7/// Axum extractor that deserializes URL query parameters into `T` and then sanitizes it.
8///
9/// Returns a 400 Bad Request error if the query string cannot be deserialized into `T`.
10/// `T` must implement both [`serde::de::DeserializeOwned`] and [`crate::sanitize::Sanitize`].
11///
12/// # Example
13///
14/// ```
15/// use modo::extractor::Query;
16/// use modo::Sanitize;
17/// use serde::Deserialize;
18///
19/// #[derive(Deserialize)]
20/// struct SearchParams { q: String, page: Option<u32> }
21///
22/// impl Sanitize for SearchParams {
23/// fn sanitize(&mut self) { self.q = self.q.trim().to_lowercase(); }
24/// }
25///
26/// async fn search(Query(params): Query<SearchParams>) {
27/// // params.q is already trimmed and lowercased
28/// }
29/// ```
30pub struct Query<T>(pub T);
31
32impl<S, T> FromRequestParts<S> for Query<T>
33where
34 S: Send + Sync,
35 T: DeserializeOwned + Sanitize,
36{
37 type Rejection = crate::error::Error;
38
39 async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
40 let axum::extract::Query(mut value) =
41 axum::extract::Query::<T>::from_request_parts(parts, state)
42 .await
43 .map_err(|e| crate::error::Error::bad_request(format!("invalid query: {e}")))?;
44 value.sanitize();
45 Ok(Query(value))
46 }
47}