diff --git a/axum/CHANGELOG.md b/axum/CHANGELOG.md index 9d5388d198..b0b9c68fe2 100644 --- a/axum/CHANGELOG.md +++ b/axum/CHANGELOG.md @@ -53,6 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **breaking:** Only inherit fallbacks for routers nested with `Router::nest`. Routers nested with `Router::nest_service` will no longer inherit fallbacks ([#1956]) - **fixed:** Don't remove the `Sec-WebSocket-Key` header in `WebSocketUpgrade` ([#1972]) +- **added:** Add `axum::extract::Query::try_from_uri` ([#2058]) - **added:** Implement `IntoResponse` for `Box` and `Box<[u8]>` ([#2035]) [#1664]: https://github.com/tokio-rs/axum/pull/1664 @@ -64,6 +65,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#1868]: https://github.com/tokio-rs/axum/pull/1868 [#1956]: https://github.com/tokio-rs/axum/pull/1956 [#1972]: https://github.com/tokio-rs/axum/pull/1972 +[#2058]: https://github.com/tokio-rs/axum/pull/2058 # 0.6.17 (25. April, 2023) diff --git a/axum/src/docs/error_handling.md b/axum/src/docs/error_handling.md index 305821b9ec..02f6f74025 100644 --- a/axum/src/docs/error_handling.md +++ b/axum/src/docs/error_handling.md @@ -39,7 +39,7 @@ It doesn't matter whether you return `Err(StatusCode::NOT_FOUND)` or axum. Instead of a direct `StatusCode`, it makes sense to use intermediate error type -that can ultimately be converted to `Reponse`. This allows using `?` operator +that can ultimately be converted to `Response`. This allows using `?` operator in handlers. See those examples: * [`anyhow-error-response`][anyhow] for generic boxed errors diff --git a/axum/src/extract/query.rs b/axum/src/extract/query.rs index 81b28b61cd..37a40771c2 100644 --- a/axum/src/extract/query.rs +++ b/axum/src/extract/query.rs @@ -1,6 +1,6 @@ use super::{rejection::*, FromRequestParts}; use async_trait::async_trait; -use http::request::Parts; +use http::{request::Parts, Uri}; use serde::de::DeserializeOwned; /// Extractor that deserializes query strings into some type. @@ -55,10 +55,38 @@ where type Rejection = QueryRejection; async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { - let query = parts.uri.query().unwrap_or_default(); - let value = + Self::try_from_uri(&parts.uri) + } +} + +impl Query +where + T: DeserializeOwned, +{ + /// Attempts to construct a [`Query`] from a reference to a [`Uri`]. + /// + /// # Example + /// ``` + /// use axum::extract::Query; + /// use http::Uri; + /// use serde::Deserialize; + /// + /// #[derive(Deserialize)] + /// struct ExampleParams { + /// foo: String, + /// bar: u32, + /// } + /// + /// let uri: Uri = "http://example.com/path?foo=hello&bar=42".parse().unwrap(); + /// let result: Query = Query::try_from_uri(&uri).unwrap(); + /// assert_eq!(result.foo, String::from("hello")); + /// assert_eq!(result.bar, 42); + /// ``` + pub fn try_from_uri(value: &Uri) -> Result { + let query = value.query().unwrap_or_default(); + let params = serde_urlencoded::from_str(query).map_err(FailedToDeserializeQueryString::from_err)?; - Ok(Query(value)) + Ok(Query(params)) } } @@ -137,4 +165,32 @@ mod tests { let res = client.get("/?n=hi").send().await; assert_eq!(res.status(), StatusCode::BAD_REQUEST); } + + #[test] + fn test_try_from_uri() { + #[derive(Deserialize)] + struct TestQueryParams { + foo: String, + bar: u32, + } + let uri: Uri = "http://example.com/path?foo=hello&bar=42".parse().unwrap(); + let result: Query = Query::try_from_uri(&uri).unwrap(); + assert_eq!(result.foo, String::from("hello")); + assert_eq!(result.bar, 42); + } + + #[test] + fn test_try_from_uri_with_invalid_query() { + #[derive(Deserialize)] + struct TestQueryParams { + _foo: String, + _bar: u32, + } + let uri: Uri = "http://example.com/path?foo=hello&bar=invalid" + .parse() + .unwrap(); + let result: Result, _> = Query::try_from_uri(&uri); + + assert!(result.is_err()); + } } diff --git a/axum/src/handler/mod.rs b/axum/src/handler/mod.rs index 8f1271793a..69ff8c8025 100644 --- a/axum/src/handler/mod.rs +++ b/axum/src/handler/mod.rs @@ -33,7 +33,7 @@ //! ``` //! //! Instead of a direct `StatusCode`, it makes sense to use intermediate error type -//! that can ultimately be converted to `Reponse`. This allows using `?` operator +//! that can ultimately be converted to `Response`. This allows using `?` operator //! in handlers. See those examples: //! //! * [`anyhow-error-response`][anyhow] for generic boxed errors