serde_querystring_actix/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::future::{ready, Ready};
4use std::sync::Arc;
5use std::{fmt, ops};
6
7use actix_web::dev::Payload;
8use actix_web::http::StatusCode;
9use actix_web::{Error, FromRequest, HttpRequest, ResponseError};
10use derive_more::{Display, From};
11use serde::de;
12
13pub use serde_querystring::de::ParseMode;
14
15/// Actix-web's web::Query modified to work with serde-querystring
16///
17/// [**QueryStringConfig**](struct.QueryStringConfig.html) allows to configure extraction process.
18///
19/// # Example
20///
21/// ```rust
22/// use actix_web::{web, App};
23/// use serde::Deserialize;
24/// use serde_querystring_actix::QueryString;
25///
26/// #[derive(Debug, Deserialize)]
27/// pub enum ResponseType {
28///    Token,
29///    Code
30/// }
31///
32/// #[derive(Deserialize)]
33/// pub struct AuthRequest {
34///    id: u64,
35///    response_type: ResponseType,
36/// }
37///
38/// // Use `QueryString` extractor for query information (and destructure it within the signature).
39/// // This handler gets called only if the request's query string contains a `username` field.
40/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`.
41/// // For more example visit the serde-querystring crate itself.
42/// async fn index(QueryString(info): QueryString<AuthRequest>) -> String {
43///     format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type)
44/// }
45///
46/// fn main() {
47///     let app = App::new().service(
48///        web::resource("/index.html").route(web::get().to(index))); // <- use `Query` extractor
49/// }
50/// ```
51#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
52pub struct QueryString<T>(pub T);
53
54impl<T> QueryString<T> {
55    /// Deconstruct to a inner value
56    pub fn into_inner(self) -> T {
57        self.0
58    }
59
60    /// Get query parameters from the path
61    pub fn from_query(
62        query_str: &str,
63        parse_mode: serde_querystring::de::ParseMode,
64    ) -> Result<Self, QueryStringPayloadError>
65    where
66        T: de::DeserializeOwned,
67    {
68        serde_querystring::de::from_str::<T>(query_str, parse_mode)
69            .map(Self)
70            .map_err(QueryStringPayloadError::Deserialize)
71    }
72}
73
74impl<T> ops::Deref for QueryString<T> {
75    type Target = T;
76
77    fn deref(&self) -> &T {
78        &self.0
79    }
80}
81
82impl<T> ops::DerefMut for QueryString<T> {
83    fn deref_mut(&mut self) -> &mut T {
84        &mut self.0
85    }
86}
87
88impl<T: fmt::Display> fmt::Display for QueryString<T> {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        self.0.fmt(f)
91    }
92}
93
94impl<T> FromRequest for QueryString<T>
95where
96    T: de::DeserializeOwned,
97{
98    type Error = Error;
99    type Future = Ready<Result<Self, Error>>;
100
101    #[inline]
102    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
103        let config = req
104            .app_data::<QueryStringConfig>()
105            .cloned()
106            .unwrap_or_default();
107
108        serde_querystring::de::from_str::<T>(req.query_string(), config.mode)
109            .map(|val| ready(Ok(QueryString(val))))
110            .unwrap_or_else(move |e| {
111                let e = QueryStringPayloadError::Deserialize(e);
112
113                log::debug!(
114                    "Failed during QueryString extractor deserialization. \
115                     Request path: {:?}",
116                    req.path()
117                );
118
119                let e = if let Some(error_handler) = config.ehandler {
120                    (error_handler)(e, req)
121                } else {
122                    e.into()
123                };
124
125                ready(Err(e))
126            })
127    }
128}
129
130/// QueryString extractor configuration
131///
132/// # Example
133///
134/// ```rust
135/// use actix_web::{error, web, App, FromRequest, HttpResponse};
136/// use serde::Deserialize;
137/// use serde_querystring_actix::{QueryString, QueryStringConfig, ParseMode};
138///
139/// #[derive(Deserialize)]
140/// struct Info {
141///     username: String,
142/// }
143///
144/// /// deserialize `Info` from request's querystring
145/// async fn index(info: QueryString<Info>) -> String {
146///     format!("Welcome {}!", info.username)
147/// }
148///
149/// fn main() {
150///     let app = App::new().service(
151///         web::resource("/index.html").app_data(
152///             // change query extractor configuration
153///             QueryStringConfig::default()
154///                 .parse_mode(ParseMode::Brackets) // <- choose the parsing mode
155///                 .error_handler(|err, req| {  // <- create custom error response
156///                     error::InternalError::from_response(
157///                         err, HttpResponse::Conflict().finish()).into()
158///                 })
159///             )
160///             .route(web::post().to(index))
161///     );
162/// }
163/// ```
164#[derive(Clone)]
165pub struct QueryStringConfig {
166    mode: serde_querystring::de::ParseMode,
167    ehandler: Option<Arc<dyn Fn(QueryStringPayloadError, &HttpRequest) -> Error + Send + Sync>>,
168}
169
170impl QueryStringConfig {
171    /// Set custom error handler
172    pub fn error_handler<F>(mut self, f: F) -> Self
173    where
174        F: Fn(QueryStringPayloadError, &HttpRequest) -> Error + Send + Sync + 'static,
175    {
176        self.ehandler = Some(Arc::new(f));
177        self
178    }
179
180    pub fn parse_mode(mut self, mode: serde_querystring::de::ParseMode) -> Self {
181        self.mode = mode;
182        self
183    }
184}
185
186impl Default for QueryStringConfig {
187    fn default() -> Self {
188        QueryStringConfig {
189            mode: serde_querystring::de::ParseMode::Duplicate,
190            ehandler: None,
191        }
192    }
193}
194
195/// A set of errors that can occur during parsing query strings
196#[derive(Debug, Display, From)]
197pub enum QueryStringPayloadError {
198    /// Deserialize error
199    #[display(fmt = "Query deserialize error: {}", _0)]
200    Deserialize(serde_querystring::de::Error),
201}
202
203impl std::error::Error for QueryStringPayloadError {}
204
205/// Return `BadRequest` for `QueryStringPayloadError`
206impl ResponseError for QueryStringPayloadError {
207    fn status_code(&self) -> StatusCode {
208        StatusCode::BAD_REQUEST
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use actix_web::error::InternalError;
215    use actix_web::http::StatusCode;
216    use actix_web::test::TestRequest;
217    use actix_web::HttpResponse;
218    use derive_more::Display;
219    use serde::Deserialize;
220
221    use super::*;
222
223    #[derive(Deserialize, Debug, Display)]
224    struct Id {
225        id: String,
226    }
227
228    #[actix_rt::test]
229    async fn test_service_request_extract() {
230        let req = TestRequest::with_uri("/name/user1/").to_srv_request();
231        assert!(QueryString::<Id>::from_query(
232            &req.query_string(),
233            serde_querystring::de::ParseMode::UrlEncoded
234        )
235        .is_err());
236
237        let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
238        let mut s = QueryString::<Id>::from_query(
239            &req.query_string(),
240            serde_querystring::de::ParseMode::UrlEncoded,
241        )
242        .unwrap();
243
244        assert_eq!(s.id, "test");
245        assert_eq!(
246            format!("{}, {:?}", s, s),
247            "test, QueryString(Id { id: \"test\" })"
248        );
249
250        s.id = "test1".to_string();
251        let s = s.into_inner();
252        assert_eq!(s.id, "test1");
253    }
254
255    #[actix_rt::test]
256    async fn test_request_extract() {
257        let req = TestRequest::with_uri("/name/user1/").to_srv_request();
258        let (req, mut pl) = req.into_parts();
259        assert!(QueryString::<Id>::from_request(&req, &mut pl)
260            .await
261            .is_err());
262
263        let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
264        let (req, mut pl) = req.into_parts();
265
266        let mut s = QueryString::<Id>::from_request(&req, &mut pl)
267            .await
268            .unwrap();
269        assert_eq!(s.id, "test");
270        assert_eq!(
271            format!("{}, {:?}", s, s),
272            "test, QueryString(Id { id: \"test\" })"
273        );
274
275        s.id = "test1".to_string();
276        let s = s.into_inner();
277        assert_eq!(s.id, "test1");
278    }
279
280    #[actix_rt::test]
281    async fn test_custom_error_responder() {
282        let req = TestRequest::with_uri("/name/user1/")
283            .app_data(QueryStringConfig::default().error_handler(|e, _| {
284                let resp = HttpResponse::UnprocessableEntity().finish();
285                InternalError::from_response(e, resp).into()
286            }))
287            .to_srv_request();
288
289        let (req, mut pl) = req.into_parts();
290        let query = QueryString::<Id>::from_request(&req, &mut pl).await;
291
292        assert!(query.is_err());
293        assert_eq!(
294            query
295                .unwrap_err()
296                .as_response_error()
297                .error_response()
298                .status(),
299            StatusCode::UNPROCESSABLE_ENTITY
300        );
301    }
302}