From 4ce5a324f0536addb1a0ebe5a83468c1fe6005c4 Mon Sep 17 00:00:00 2001 From: Jerry Wang Date: Fri, 19 May 2023 01:37:21 -0700 Subject: [PATCH 01/21] Fix a typo in `axum::extract::rejection::FailedToDeserializeQueryString` documentation (#2009) --- axum/src/extract/rejection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axum/src/extract/rejection.rs b/axum/src/extract/rejection.rs index caaab06d86..07c322e94d 100644 --- a/axum/src/extract/rejection.rs +++ b/axum/src/extract/rejection.rs @@ -93,7 +93,7 @@ define_rejection! { #[status = BAD_REQUEST] #[body = "Failed to deserialize query string"] /// Rejection type used if the [`Query`](super::Query) extractor is unable to - /// deserialize the form into the target type. + /// deserialize the query string into the target type. pub struct FailedToDeserializeQueryString(Error); } From 31229cf7c30150b52b5b921e8e505ad075851d23 Mon Sep 17 00:00:00 2001 From: "Azzam S.A" <17734314+azzamsa@users.noreply.github.com> Date: Mon, 5 Jun 2023 17:05:53 +0700 Subject: [PATCH 02/21] docs: add warning icon for extractor order (#2027) --- axum-extra/src/extract/multipart.rs | 2 +- axum/src/extract/multipart.rs | 2 +- axum/src/form.rs | 2 +- axum/src/json.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/axum-extra/src/extract/multipart.rs b/axum-extra/src/extract/multipart.rs index 83c4934fab..5ce730714e 100644 --- a/axum-extra/src/extract/multipart.rs +++ b/axum-extra/src/extract/multipart.rs @@ -23,7 +23,7 @@ use std::{ /// Extractor that parses `multipart/form-data` requests (commonly used with file uploads). /// -/// Since extracting multipart form data from the request requires consuming the body, the +/// ⚠️ Since extracting multipart form data from the request requires consuming the body, the /// `Multipart` extractor must be *last* if there are multiple extractors in a handler. /// See ["the order of extractors"][order-of-extractors] /// diff --git a/axum/src/extract/multipart.rs b/axum/src/extract/multipart.rs index b7f2c10322..3827734f3e 100644 --- a/axum/src/extract/multipart.rs +++ b/axum/src/extract/multipart.rs @@ -22,7 +22,7 @@ use std::{ /// Extractor that parses `multipart/form-data` requests (commonly used with file uploads). /// -/// Since extracting multipart form data from the request requires consuming the body, the +/// ⚠️ Since extracting multipart form data from the request requires consuming the body, the /// `Multipart` extractor must be *last* if there are multiple extractors in a handler. /// See ["the order of extractors"][order-of-extractors] /// diff --git a/axum/src/form.rs b/axum/src/form.rs index 208049130f..c690d48ef3 100644 --- a/axum/src/form.rs +++ b/axum/src/form.rs @@ -17,7 +17,7 @@ use serde::Serialize; /// requests and `application/x-www-form-urlencoded` encoded request bodies for other methods. It /// supports any type that implements [`serde::Deserialize`]. /// -/// Since parsing form data might require consuming the request body, the `Form` extractor must be +/// ⚠️ Since parsing form data might require consuming the request body, the `Form` extractor must be /// *last* if there are multiple extractors in a handler. See ["the order of /// extractors"][order-of-extractors] /// diff --git a/axum/src/json.rs b/axum/src/json.rs index 0f1775c8a9..39fd7f4280 100644 --- a/axum/src/json.rs +++ b/axum/src/json.rs @@ -24,7 +24,7 @@ use serde::{de::DeserializeOwned, Serialize}; /// type. /// - Buffering the request body fails. /// -/// Since parsing JSON requires consuming the request body, the `Json` extractor must be +/// ⚠️ Since parsing JSON requires consuming the request body, the `Json` extractor must be /// *last* if there are multiple extractors in a handler. /// See ["the order of extractors"][order-of-extractors] /// From 1a9fc5f5133ac85fec04de26ef262cba169b337d Mon Sep 17 00:00:00 2001 From: Cyril Marpaud <9333398+cyril-marpaud@users.noreply.github.com> Date: Thu, 22 Jun 2023 23:34:38 +0200 Subject: [PATCH 03/21] Fix a typo in `axum::middleware` (#2056) Co-authored-by: David Pedersen --- axum/src/docs/middleware.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axum/src/docs/middleware.md b/axum/src/docs/middleware.md index 90fa2cac48..fd601fcfe8 100644 --- a/axum/src/docs/middleware.md +++ b/axum/src/docs/middleware.md @@ -127,7 +127,7 @@ That is: - It then does its thing and passes the request onto `layer_two` - Which passes the request onto `layer_one` - Which passes the request onto `handler` where a response is produced -- That response is then passes to `layer_one` +- That response is then passed to `layer_one` - Then to `layer_two` - And finally to `layer_three` where it's returned out of your app From 3bd32bd8c7a2a85d05f74f3e9b40083e5d45690e Mon Sep 17 00:00:00 2001 From: mohad12211 <51754973+mohad12211@users.noreply.github.com> Date: Fri, 23 Jun 2023 00:36:37 +0300 Subject: [PATCH 04/21] docs: update router::route multiple methods docs (#2051) Co-authored-by: David Pedersen --- axum/src/docs/routing/route.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/axum/src/docs/routing/route.md b/axum/src/docs/routing/route.md index ae425142fc..ac5ed9406b 100644 --- a/axum/src/docs/routing/route.md +++ b/axum/src/docs/routing/route.md @@ -61,7 +61,7 @@ the path `/foo/bar/baz` the value of `rest` will be `bar/baz`. # Accepting multiple methods -To accept multiple methods for the same route you must add all handlers at the +To accept multiple methods for the same route you can add all handlers at the same time: ```rust @@ -82,6 +82,23 @@ async fn delete_root() {} # }; ``` +Or you can add them one by one: + +```rust +# use axum::Router; +# use axum::routing::{get, post, delete}; +# +let app = Router::new() + .route("/", get(get_root)) + .route("/", post(post_root)) + .route("/", delete(delete_root)); +# +# let _: Router = app; +# async fn get_root() {} +# async fn post_root() {} +# async fn delete_root() {} +``` + # More examples ```rust From 095bf2ff611b29998d7bb0b26f0d141d63d8c697 Mon Sep 17 00:00:00 2001 From: Vitaly Shukela Date: Fri, 23 Jun 2023 00:41:13 +0300 Subject: [PATCH 05/21] docs: Add links to more examples of Result-returning handlers. (#2049) Co-authored-by: David Pedersen --- axum/src/docs/error_handling.md | 10 ++++++++++ axum/src/handler/mod.rs | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/axum/src/docs/error_handling.md b/axum/src/docs/error_handling.md index 45c768c69a..d1b172b974 100644 --- a/axum/src/docs/error_handling.md +++ b/axum/src/docs/error_handling.md @@ -38,6 +38,16 @@ It doesn't matter whether you return `Err(StatusCode::NOT_FOUND)` or `Err(StatusCode::INTERNAL_SERVER_ERROR)`. These are not considered errors in 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 +in handlers. See those examples: + +* [`anyhow-error-response`][anyhow] for generic boxed errors +* [`error-handling-and-dependency-injection`][ehdi] for application-specific detailed errors + +[anyhow]:https://github.com/tokio-rs/axum/blob/main/examples/anyhow-error-response/src/main.rs +[ehdi]:https://github.com/tokio-rs/axum/blob/main/examples/error-handling-and-dependency-injection/src/main.rs + This also applies to extractors. If an extractor doesn't match the request the request will be rejected and a response will be returned without calling your handler. See [`extract`](crate::extract) to learn more about handling extractor diff --git a/axum/src/handler/mod.rs b/axum/src/handler/mod.rs index 338eea623c..d8546a1b71 100644 --- a/axum/src/handler/mod.rs +++ b/axum/src/handler/mod.rs @@ -32,6 +32,16 @@ //! } //! ``` //! +//! Instead of a direct `StatusCode`, it makes sense to use intermediate error type +//! that can ultimately be converted to `Reponse`. This allows using `?` operator +//! in handlers. See those examples: +//! +//! * [`anyhow-error-response`][anyhow] for generic boxed errors +//! * [`error-handling-and-dependency-injection`][ehdi] for application-specific detailed errors +//! +//! [anyhow]:https://github.com/tokio-rs/axum/blob/main/examples/anyhow-error-response/src/main.rs +//! [ehdi]:https://github.com/tokio-rs/axum/blob/main/examples/error-handling-and-dependency-injection/src/main.rs +//! #![doc = include_str!("../docs/debugging_handler_type_errors.md")] #[cfg(feature = "tokio")] From 16b6388ee2cd8980534ad9dbd3d64d4b0d785d27 Mon Sep 17 00:00:00 2001 From: Sabrina Jewson Date: Thu, 22 Jun 2023 22:46:39 +0100 Subject: [PATCH 06/21] Fix `.source()` of composite rejections (#2030) Co-authored-by: David Pedersen --- axum-core/src/macros.rs | 42 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/axum-core/src/macros.rs b/axum-core/src/macros.rs index 10365e1556..e0a4e8683c 100644 --- a/axum-core/src/macros.rs +++ b/axum-core/src/macros.rs @@ -203,7 +203,7 @@ macro_rules! __composite_rejection { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { $( - Self::$variant(inner) => Some(inner), + Self::$variant(inner) => inner.source(), )+ } } @@ -211,6 +211,46 @@ macro_rules! __composite_rejection { }; } +#[cfg(test)] +mod composite_rejection_tests { + use self::defs::*; + use crate::Error; + use std::error::Error as _; + + #[allow(dead_code, unreachable_pub)] + mod defs { + use crate::{__composite_rejection, __define_rejection}; + + __define_rejection! { + #[status = BAD_REQUEST] + #[body = "error message 1"] + pub struct Inner1; + } + __define_rejection! { + #[status = BAD_REQUEST] + #[body = "error message 2"] + pub struct Inner2(Error); + } + __composite_rejection! { + pub enum Outer { Inner1, Inner2 } + } + } + + /// The implementation of `.source()` on `Outer` should defer straight to the implementation + /// on its inner type instead of returning the inner type itself, because the `Display` + /// implementation on `Outer` already forwards to the inner type and so it would result in two + /// errors in the chain `Display`ing the same thing. + #[test] + fn source_gives_inner_source() { + let rejection = Outer::Inner1(Inner1); + assert!(rejection.source().is_none()); + + let msg = "hello world"; + let rejection = Outer::Inner2(Inner2(Error::new(msg))); + assert_eq!(rejection.source().unwrap().to_string(), msg); + } +} + #[rustfmt::skip] macro_rules! all_the_tuples { ($name:ident) => { From cf8302869572e9517519389f22edd97780a78d39 Mon Sep 17 00:00:00 2001 From: Asger Hautop Drewsen Date: Thu, 22 Jun 2023 23:50:45 +0200 Subject: [PATCH 07/21] docs: Remove explicit auto deref from PrivateCookieJar example (#2028) Co-authored-by: David Pedersen --- axum-extra/src/extract/cookie/private.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axum-extra/src/extract/cookie/private.rs b/axum-extra/src/extract/cookie/private.rs index c1b55dbdcd..985898c289 100644 --- a/axum-extra/src/extract/cookie/private.rs +++ b/axum-extra/src/extract/cookie/private.rs @@ -88,7 +88,7 @@ use std::{convert::Infallible, fmt, marker::PhantomData}; /// type Target = InnerState; /// /// fn deref(&self) -> &Self::Target { -/// &*self.0 +/// &self.0 /// } /// } /// From 1fb9e0ebaaf93bbc905fcb1e8e228ff83f433ed4 Mon Sep 17 00:00:00 2001 From: Alexander Jackson Date: Thu, 22 Jun 2023 22:51:52 +0100 Subject: [PATCH 08/21] Allow unreachable code in `#[debug_handler]` (#2014) Co-authored-by: David Pedersen --- axum-macros/src/debug_handler.rs | 8 ++++++++ .../tests/debug_handler/pass/deny_unreachable_code.rs | 8 ++++++++ 2 files changed, 16 insertions(+) create mode 100644 axum-macros/tests/debug_handler/pass/deny_unreachable_code.rs diff --git a/axum-macros/src/debug_handler.rs b/axum-macros/src/debug_handler.rs index 9b42a1af90..dcd292b1e1 100644 --- a/axum-macros/src/debug_handler.rs +++ b/axum-macros/src/debug_handler.rs @@ -276,6 +276,7 @@ fn check_inputs_impls_from_request( quote_spanned! {span=> #[allow(warnings)] + #[allow(unreachable_code)] #[doc(hidden)] fn #check_fn #check_fn_generics() where @@ -285,6 +286,7 @@ fn check_inputs_impls_from_request( // we have to call the function to actually trigger a compile error // since the function is generic, just defining it is not enough #[allow(warnings)] + #[allow(unreachable_code)] #[doc(hidden)] fn #call_check_fn() { @@ -429,6 +431,7 @@ fn check_output_impls_into_response(item_fn: &ItemFn) -> TokenStream { let make = if item_fn.sig.asyncness.is_some() { quote_spanned! {span=> #[allow(warnings)] + #[allow(unreachable_code)] #[doc(hidden)] async fn #make_value_name() -> #ty { #declare_inputs @@ -438,6 +441,7 @@ fn check_output_impls_into_response(item_fn: &ItemFn) -> TokenStream { } else { quote_spanned! {span=> #[allow(warnings)] + #[allow(unreachable_code)] #[doc(hidden)] fn #make_value_name() -> #ty { #declare_inputs @@ -453,6 +457,7 @@ fn check_output_impls_into_response(item_fn: &ItemFn) -> TokenStream { #make #[allow(warnings)] + #[allow(unreachable_code)] #[doc(hidden)] async fn #name() { let value = #receiver #make_value_name().await; @@ -465,6 +470,7 @@ fn check_output_impls_into_response(item_fn: &ItemFn) -> TokenStream { } else { quote_spanned! {span=> #[allow(warnings)] + #[allow(unreachable_code)] #[doc(hidden)] async fn #name() { #make @@ -515,6 +521,7 @@ fn check_future_send(item_fn: &ItemFn) -> TokenStream { if let Some(receiver) = self_receiver(item_fn) { quote! { #[allow(warnings)] + #[allow(unreachable_code)] #[doc(hidden)] fn #name() { let future = #receiver #handler_name(#(#args),*); @@ -524,6 +531,7 @@ fn check_future_send(item_fn: &ItemFn) -> TokenStream { } else { quote! { #[allow(warnings)] + #[allow(unreachable_code)] #[doc(hidden)] fn #name() { #item_fn diff --git a/axum-macros/tests/debug_handler/pass/deny_unreachable_code.rs b/axum-macros/tests/debug_handler/pass/deny_unreachable_code.rs new file mode 100644 index 0000000000..1ccbc6e01e --- /dev/null +++ b/axum-macros/tests/debug_handler/pass/deny_unreachable_code.rs @@ -0,0 +1,8 @@ +#![deny(unreachable_code)] + +use axum::extract::Path; + +#[axum_macros::debug_handler] +async fn handler(Path(_): Path) {} + +fn main() {} From 357adb0a7def39299648efa157be8f760c3e76e6 Mon Sep 17 00:00:00 2001 From: Alex Butler Date: Thu, 22 Jun 2023 23:03:30 +0100 Subject: [PATCH 09/21] Update tokio-tungstenite to 0.19 (#2021) Co-authored-by: David Pedersen --- .github/workflows/CI.yml | 2 +- axum-extra/Cargo.toml | 2 +- axum-extra/README.md | 2 +- axum-macros/README.md | 2 +- axum/Cargo.toml | 4 ++-- axum/README.md | 2 +- examples/testing-websockets/Cargo.toml | 2 +- examples/websockets/Cargo.toml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 62d73cf36e..1903b9c4f7 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -2,7 +2,7 @@ name: CI env: CARGO_TERM_COLOR: always - MSRV: '1.60' + MSRV: '1.63' on: push: diff --git a/axum-extra/Cargo.toml b/axum-extra/Cargo.toml index dca9f07d66..6ccbc46e20 100644 --- a/axum-extra/Cargo.toml +++ b/axum-extra/Cargo.toml @@ -2,7 +2,7 @@ categories = ["asynchronous", "network-programming", "web-programming"] description = "Extra utilities for axum" edition = "2021" -rust-version = "1.60" +rust-version = "1.63" homepage = "https://github.com/tokio-rs/axum" keywords = ["http", "web", "framework"] license = "MIT" diff --git a/axum-extra/README.md b/axum-extra/README.md index 25fa2e8d74..d2c5646b2e 100644 --- a/axum-extra/README.md +++ b/axum-extra/README.md @@ -14,7 +14,7 @@ This crate uses `#![forbid(unsafe_code)]` to ensure everything is implemented in ## Minimum supported Rust version -axum-extra's MSRV is 1.60. +axum-extra's MSRV is 1.63. ## Getting Help diff --git a/axum-macros/README.md b/axum-macros/README.md index acaf06122a..7fa1bd2dea 100644 --- a/axum-macros/README.md +++ b/axum-macros/README.md @@ -14,7 +14,7 @@ This crate uses `#![forbid(unsafe_code)]` to ensure everything is implemented in ## Minimum supported Rust version -axum-macros's MSRV is 1.60. +axum-macros's MSRV is 1.63. ## Getting Help diff --git a/axum/Cargo.toml b/axum/Cargo.toml index 087bd72082..0979f1278c 100644 --- a/axum/Cargo.toml +++ b/axum/Cargo.toml @@ -4,7 +4,7 @@ version = "0.6.18" categories = ["asynchronous", "network-programming", "web-programming::http-server"] description = "Web framework that focuses on ergonomics and modularity" edition = "2021" -rust-version = "1.60" +rust-version = "1.63" homepage = "https://github.com/tokio-rs/axum" keywords = ["http", "web", "framework"] license = "MIT" @@ -61,7 +61,7 @@ serde_path_to_error = { version = "0.1.8", optional = true } serde_urlencoded = { version = "0.7", optional = true } sha1 = { version = "0.10", optional = true } tokio = { package = "tokio", version = "1.25.0", features = ["time"], optional = true } -tokio-tungstenite = { version = "0.18.0", optional = true } +tokio-tungstenite = { version = "0.19", optional = true } tracing = { version = "0.1", default-features = false, optional = true } [dependencies.tower-http] diff --git a/axum/README.md b/axum/README.md index 08487807c5..407d931b4d 100644 --- a/axum/README.md +++ b/axum/README.md @@ -111,7 +111,7 @@ This crate uses `#![forbid(unsafe_code)]` to ensure everything is implemented in ## Minimum supported Rust version -axum's MSRV is 1.60. +axum's MSRV is 1.63. ## Examples diff --git a/examples/testing-websockets/Cargo.toml b/examples/testing-websockets/Cargo.toml index 26c4f8d40b..7fc9a26a16 100644 --- a/examples/testing-websockets/Cargo.toml +++ b/examples/testing-websockets/Cargo.toml @@ -9,4 +9,4 @@ axum = { path = "../../axum", features = ["ws"] } futures = "0.3" hyper = { version = "0.14", features = ["full"] } tokio = { version = "1.0", features = ["full"] } -tokio-tungstenite = "0.17" +tokio-tungstenite = "0.19" diff --git a/examples/websockets/Cargo.toml b/examples/websockets/Cargo.toml index 30e6c3c0f1..d79c29b736 100644 --- a/examples/websockets/Cargo.toml +++ b/examples/websockets/Cargo.toml @@ -10,7 +10,7 @@ futures = "0.3" futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] } headers = "0.3" tokio = { version = "1.0", features = ["full"] } -tokio-tungstenite = "0.18.0" +tokio-tungstenite = "0.19" tower = { version = "0.4", features = ["util"] } tower-http = { version = "0.4.0", features = ["fs", "trace"] } tracing = "0.1" From 7dec5c064b73fce712b0810c261d99f6cbb01ef4 Mon Sep 17 00:00:00 2001 From: Alexander van Ratingen <470642+alvra@users.noreply.github.com> Date: Mon, 26 Jun 2023 13:14:28 +0200 Subject: [PATCH 10/21] Implement `IntoResponse` for boxed slices (#2035) --- axum-core/src/response/into_response.rs | 12 ++++++++++++ axum/CHANGELOG.md | 1 + 2 files changed, 13 insertions(+) diff --git a/axum-core/src/response/into_response.rs b/axum-core/src/response/into_response.rs index f19974cfb7..898252a2e3 100644 --- a/axum-core/src/response/into_response.rs +++ b/axum-core/src/response/into_response.rs @@ -241,6 +241,12 @@ impl IntoResponse for String { } } +impl IntoResponse for Box { + fn into_response(self) -> Response { + String::from(self).into_response() + } +} + impl IntoResponse for Cow<'static, str> { fn into_response(self) -> Response { let mut res = Full::from(self).into_response(); @@ -366,6 +372,12 @@ impl IntoResponse for Vec { } } +impl IntoResponse for Box<[u8]> { + fn into_response(self) -> Response { + Vec::from(self).into_response() + } +} + impl IntoResponse for Cow<'static, [u8]> { fn into_response(self) -> Response { let mut res = Full::from(self).into_response(); diff --git a/axum/CHANGELOG.md b/axum/CHANGELOG.md index 9eec180cf4..33f45d4727 100644 --- a/axum/CHANGELOG.md +++ b/axum/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # 0.6.18 (30. April, 2023) - **fixed:** Don't remove the `Sec-WebSocket-Key` header in `WebSocketUpgrade` ([#1972]) +- **added:** Implement `IntoResponse` for `Box` and `Box<[u8]>` ([#2035]) [#1972]: https://github.com/tokio-rs/axum/pull/1972 From cf0ccec8c2a9ccde3d25ab24b44534d90486ea55 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Mon, 3 Jul 2023 09:23:37 +0200 Subject: [PATCH 11/21] Fix some typos in the docs --- axum/src/docs/error_handling.md | 2 +- axum/src/handler/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/axum/src/docs/error_handling.md b/axum/src/docs/error_handling.md index d1b172b974..c797b1d787 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/handler/mod.rs b/axum/src/handler/mod.rs index d8546a1b71..9aa88b0896 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 From ed2578e84123d53faf23d4aafa03e8cb4ca8700d Mon Sep 17 00:00:00 2001 From: jachii <70832612+dobecad@users.noreply.github.com> Date: Mon, 3 Jul 2023 03:35:59 -0400 Subject: [PATCH 12/21] Add `axum::extract::Query::try_from_uri` (#2058) Co-authored-by: David Pedersen --- axum/CHANGELOG.md | 2 ++ axum/src/extract/query.rs | 64 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/axum/CHANGELOG.md b/axum/CHANGELOG.md index 33f45d4727..1bce75c71c 100644 --- a/axum/CHANGELOG.md +++ b/axum/CHANGELOG.md @@ -12,9 +12,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # 0.6.18 (30. April, 2023) - **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]) [#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/extract/query.rs b/axum/src/extract/query.rs index f9551e076d..6f8cb89dc1 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. @@ -57,10 +57,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)) } } @@ -136,4 +164,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()); + } } From 98eec13a848a342e3d4a4af3454f2c6db456a243 Mon Sep 17 00:00:00 2001 From: z-o-n-n-e <139063539+z-o-n-n-e@users.noreply.github.com> Date: Mon, 10 Jul 2023 04:00:01 +0800 Subject: [PATCH 13/21] Fix typo in docs (#2080) --- axum/src/docs/routing/merge.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axum/src/docs/routing/merge.md b/axum/src/docs/routing/merge.md index 0e103c83ca..b88175130b 100644 --- a/axum/src/docs/routing/merge.md +++ b/axum/src/docs/routing/merge.md @@ -31,7 +31,7 @@ let app = Router::new() // Our app now accepts // - GET /users // - GET /users/:id -// - POST /teams +// - GET /teams # async { # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); # }; From 959f4d085b6973dd3b08754823eebb77f7b99dd5 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Sun, 16 Jul 2023 01:40:31 +0200 Subject: [PATCH 14/21] Fix example for accessing inner extrator errors (#2095) --- axum/src/docs/extract.md | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/axum/src/docs/extract.md b/axum/src/docs/extract.md index ed350e5bd8..1e78d5719b 100644 --- a/axum/src/docs/extract.md +++ b/axum/src/docs/extract.md @@ -366,13 +366,16 @@ async fn handler( } } -// attempt to extract the inner `serde_json::Error`, if that succeeds we can -// provide a more specific error +// attempt to extract the inner `serde_path_to_error::Error`, +// if that succeeds we can provide a more specific error. +// +// `Json` uses `serde_path_to_error` so the error will be wrapped in `serde_path_to_error::Error`. fn serde_json_error_response(err: E) -> (StatusCode, String) where E: Error + 'static, { - if let Some(serde_json_err) = find_error_source::(&err) { + if let Some(err) = find_error_source::>(&err) { + let serde_json_err = err.inner(); ( StatusCode::BAD_REQUEST, format!( @@ -400,6 +403,24 @@ where None } } +# +# #[tokio::main] +# async fn main() { +# use axum::extract::FromRequest; +# +# let req = axum::http::Request::builder() +# .header("content-type", "application/json") +# .body(axum::body::Body::from("{")) +# .unwrap(); +# +# let err = match Json::::from_request(req, &()).await.unwrap_err() { +# JsonRejection::JsonSyntaxError(err) => err, +# _ => panic!(), +# }; +# +# let (_, body) = serde_json_error_response(err); +# assert_eq!(body, "Invalid JSON at line 1 column 1"); +# } ``` Note that while this approach works it might break in the future if axum changes From bb406b196a843064ce60885be8e88c6c43ad8e7d Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Sun, 16 Jul 2023 12:06:58 +0200 Subject: [PATCH 15/21] Fix bugs around merging routers with nested fallbacks (#2096) --- axum/CHANGELOG.md | 4 +- axum/src/routing/mod.rs | 17 +++-- axum/src/routing/path_router.rs | 28 +++++-- axum/src/routing/tests/fallback.rs | 119 +++++++++++++++++++++++++++++ 4 files changed, 156 insertions(+), 12 deletions(-) diff --git a/axum/CHANGELOG.md b/axum/CHANGELOG.md index 1bce75c71c..24d100e9ed 100644 --- a/axum/CHANGELOG.md +++ b/axum/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # Unreleased -- None. +- **fixed:** Fix bugs around merging routers with nested fallbacks ([#2096]) + +[#2096]: https://github.com/tokio-rs/axum/pull/2096 # 0.6.18 (30. April, 2023) diff --git a/axum/src/routing/mod.rs b/axum/src/routing/mod.rs index 54dc772e6c..1760157cbc 100644 --- a/axum/src/routing/mod.rs +++ b/axum/src/routing/mod.rs @@ -98,6 +98,7 @@ impl fmt::Debug for Router { pub(crate) const NEST_TAIL_PARAM: &str = "__private__axum_nest_tail_param"; pub(crate) const NEST_TAIL_PARAM_CAPTURE: &str = "/*__private__axum_nest_tail_param"; pub(crate) const FALLBACK_PARAM: &str = "__private__axum_fallback"; +pub(crate) const FALLBACK_PARAM_PATH: &str = "/*__private__axum_fallback"; impl Router where @@ -185,9 +186,12 @@ where where R: Into>, { + const PANIC_MSG: &str = + "Failed to merge fallbacks. This is a bug in axum. Please file an issue"; + let Router { path_router, - fallback_router: other_fallback, + fallback_router: mut other_fallback, default_fallback, catch_all_fallback, } = other.into(); @@ -198,16 +202,19 @@ where // both have the default fallback // use the one from other (true, true) => { - self.fallback_router = other_fallback; + self.fallback_router.merge(other_fallback).expect(PANIC_MSG); } // self has default fallback, other has a custom fallback (true, false) => { - self.fallback_router = other_fallback; + self.fallback_router.merge(other_fallback).expect(PANIC_MSG); self.default_fallback = false; } // self has a custom fallback, other has a default - // nothing to do - (false, true) => {} + (false, true) => { + let fallback_router = std::mem::take(&mut self.fallback_router); + other_fallback.merge(fallback_router).expect(PANIC_MSG); + self.fallback_router = other_fallback; + } // both have a custom fallback, not allowed (false, false) => { panic!("Cannot merge two `Router`s that both have a fallback") diff --git a/axum/src/routing/path_router.rs b/axum/src/routing/path_router.rs index e05a799754..b415f4f7b2 100644 --- a/axum/src/routing/path_router.rs +++ b/axum/src/routing/path_router.rs @@ -8,7 +8,7 @@ use tower_service::Service; use super::{ future::RouteFuture, not_found::NotFound, strip_prefix::StripPrefix, url_params, Endpoint, - MethodRouter, Route, RouteId, FALLBACK_PARAM, NEST_TAIL_PARAM, + MethodRouter, Route, RouteId, FALLBACK_PARAM_PATH, NEST_TAIL_PARAM, }; pub(super) struct PathRouter { @@ -30,7 +30,7 @@ where pub(super) fn set_fallback(&mut self, endpoint: Endpoint) { self.replace_endpoint("/", endpoint.clone()); - self.replace_endpoint(&format!("/*{FALLBACK_PARAM}"), endpoint); + self.replace_endpoint(FALLBACK_PARAM_PATH, endpoint); } } @@ -139,10 +139,26 @@ where .route_id_to_path .get(&id) .expect("no path for route id. This is a bug in axum. Please file an issue"); - match route { - Endpoint::MethodRouter(method_router) => self.route(path, method_router)?, - Endpoint::Route(route) => self.route_service(path, route)?, - }; + + if IS_FALLBACK && (&**path == "/" || &**path == FALLBACK_PARAM_PATH) { + // when merging two routers it doesn't matter if you do `a.merge(b)` or + // `b.merge(a)`. This must also be true for fallbacks. + // + // However all fallback routers will have routes for `/` and `/*` so when merging + // we have to ignore the top level fallbacks on one side otherwise we get + // conflicts. + // + // `Router::merge` makes sure that when merging fallbacks `other` always has the + // fallback we want to keep. It panics if both routers have a custom fallback. Thus + // it is always okay to ignore one fallback and `Router::merge` also makes sure the + // one we can ignore is that of `self`. + self.replace_endpoint(path, route); + } else { + match route { + Endpoint::MethodRouter(method_router) => self.route(path, method_router)?, + Endpoint::Route(route) => self.route_service(path, route)?, + } + } } Ok(()) diff --git a/axum/src/routing/tests/fallback.rs b/axum/src/routing/tests/fallback.rs index 9aa9fbe6aa..869b7329cf 100644 --- a/axum/src/routing/tests/fallback.rs +++ b/axum/src/routing/tests/fallback.rs @@ -241,3 +241,122 @@ async fn doesnt_panic_if_used_with_nested_router() { let res = client.get("/foobar").send().await; assert_eq!(res.status(), StatusCode::OK); } + +#[crate::test] +async fn issue_2072() { + let nested_routes = Router::new().fallback(inner_fallback); + + let app = Router::new() + .nest("/nested", nested_routes) + .merge(Router::new()); + + let client = TestClient::new(app); + + let res = client.get("/nested/does-not-exist").send().await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + assert_eq!(res.text().await, "inner"); + + let res = client.get("/does-not-exist").send().await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + assert_eq!(res.text().await, ""); +} + +#[crate::test] +async fn issue_2072_outer_fallback_before_merge() { + let nested_routes = Router::new().fallback(inner_fallback); + + let app = Router::new() + .nest("/nested", nested_routes) + .fallback(outer_fallback) + .merge(Router::new()); + + let client = TestClient::new(app); + + let res = client.get("/nested/does-not-exist").send().await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + assert_eq!(res.text().await, "inner"); + + let res = client.get("/does-not-exist").send().await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + assert_eq!(res.text().await, "outer"); +} + +#[crate::test] +async fn issue_2072_outer_fallback_after_merge() { + let nested_routes = Router::new().fallback(inner_fallback); + + let app = Router::new() + .nest("/nested", nested_routes) + .merge(Router::new()) + .fallback(outer_fallback); + + let client = TestClient::new(app); + + let res = client.get("/nested/does-not-exist").send().await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + assert_eq!(res.text().await, "inner"); + + let res = client.get("/does-not-exist").send().await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + assert_eq!(res.text().await, "outer"); +} + +#[crate::test] +async fn merge_router_with_fallback_into_nested_router_with_fallback() { + let nested_routes = Router::new().fallback(inner_fallback); + + let app = Router::new() + .nest("/nested", nested_routes) + .merge(Router::new().fallback(outer_fallback)); + + let client = TestClient::new(app); + + let res = client.get("/nested/does-not-exist").send().await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + assert_eq!(res.text().await, "inner"); + + let res = client.get("/does-not-exist").send().await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + assert_eq!(res.text().await, "outer"); +} + +#[crate::test] +async fn merging_nested_router_with_fallback_into_router_with_fallback() { + let nested_routes = Router::new().fallback(inner_fallback); + + let app = Router::new() + .fallback(outer_fallback) + .merge(Router::new().nest("/nested", nested_routes)); + + let client = TestClient::new(app); + + let res = client.get("/nested/does-not-exist").send().await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + assert_eq!(res.text().await, "inner"); + + let res = client.get("/does-not-exist").send().await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + assert_eq!(res.text().await, "outer"); +} + +#[crate::test] +async fn merge_empty_into_router_with_fallback() { + let app = Router::new().fallback(outer_fallback).merge(Router::new()); + + let client = TestClient::new(app); + + let res = client.get("/does-not-exist").send().await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + assert_eq!(res.text().await, "outer"); +} + +#[crate::test] +async fn merge_router_with_fallback_into_empty() { + let app = Router::new().merge(Router::new().fallback(outer_fallback)); + + let client = TestClient::new(app); + + let res = client.get("/does-not-exist").send().await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + assert_eq!(res.text().await, "outer"); +} From 60eb560c4f2caf45a06c76f08e4e0058679357da Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Sun, 16 Jul 2023 12:50:27 +0200 Subject: [PATCH 16/21] Add missing changes to changelog --- axum-extra/CHANGELOG.md | 4 +++- axum-macros/CHANGELOG.md | 4 +++- axum/CHANGELOG.md | 16 ++++++++++++---- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/axum-extra/CHANGELOG.md b/axum-extra/CHANGELOG.md index 67955a67ea..0f16d6049e 100644 --- a/axum-extra/CHANGELOG.md +++ b/axum-extra/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning]. # Unreleased -- None. +- **fixed:** Remove explicit auto deref from PrivateCookieJar example ([#2028]) + +[#2028]: https://github.com/tokio-rs/axum/pull/2028 # 0.7.4 (18. April, 2023) diff --git a/axum-macros/CHANGELOG.md b/axum-macros/CHANGELOG.md index 2e04bd75ea..dab761c2ff 100644 --- a/axum-macros/CHANGELOG.md +++ b/axum-macros/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # Unreleased -- None. +- **fixed:** Allow unreachable code in `#[debug_handler]` ([#2014]) + +[#2014]: https://github.com/tokio-rs/axum/pull/2014 # 0.3.7 (22. March, 2023) diff --git a/axum/CHANGELOG.md b/axum/CHANGELOG.md index 24d100e9ed..815633a766 100644 --- a/axum/CHANGELOG.md +++ b/axum/CHANGELOG.md @@ -7,18 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # Unreleased +- **added:** Add `axum::extract::Query::try_from_uri` ([#2058]) +- **added:** Implement `IntoResponse` for `Box` and `Box<[u8]>` ([#2035]) - **fixed:** Fix bugs around merging routers with nested fallbacks ([#2096]) - +- **fixed:** Fix `.source()` of composite rejections ([#2030]) +- **fixed:** Allow unreachable code in `#[debug_handler]` ([#2014]) +- **change:** Update tokio-tungstenite to 0.19 ([#2021]) +- **change:** axum's MSRV is now 1.63 ([#2021]) + +[#2014]: https://github.com/tokio-rs/axum/pull/2014 +[#2021]: https://github.com/tokio-rs/axum/pull/2021 +[#2030]: https://github.com/tokio-rs/axum/pull/2030 +[#2035]: https://github.com/tokio-rs/axum/pull/2035 +[#2058]: https://github.com/tokio-rs/axum/pull/2058 [#2096]: https://github.com/tokio-rs/axum/pull/2096 # 0.6.18 (30. April, 2023) - **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]) [#1972]: https://github.com/tokio-rs/axum/pull/1972 -[#2058]: https://github.com/tokio-rs/axum/pull/2058 # 0.6.17 (25. April, 2023) From 24850c78d492eae47737bcccee67da9e798ab28d Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Sun, 16 Jul 2023 12:58:35 +0200 Subject: [PATCH 17/21] Fix misc warnings --- axum-extra/src/json_lines.rs | 2 +- axum/src/extract/matched_path.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/axum-extra/src/json_lines.rs b/axum-extra/src/json_lines.rs index 8b04825f2c..215f4247d0 100644 --- a/axum-extra/src/json_lines.rs +++ b/axum-extra/src/json_lines.rs @@ -243,7 +243,7 @@ mod tests { let res = client .post("/") .body( - vec![ + [ "{\"id\":1}", "{\"id\":2}", "{\"id\":3}", diff --git a/axum/src/extract/matched_path.rs b/axum/src/extract/matched_path.rs index c4f9984e24..c3bd7b4589 100644 --- a/axum/src/extract/matched_path.rs +++ b/axum/src/extract/matched_path.rs @@ -143,7 +143,7 @@ pub(crate) fn set_matched_path_for_request( if matched_path.ends_with(NEST_TAIL_PARAM_CAPTURE) { extensions.insert(MatchedNestedPath(matched_path)); - debug_assert!(matches!(extensions.remove::(), None)); + debug_assert!(extensions.remove::().is_none()); } else { extensions.insert(MatchedPath(matched_path)); extensions.remove::(); From 2e7ea31f91f67a9c8b062018c2ed02369f39c3e0 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Sun, 16 Jul 2023 13:05:13 +0200 Subject: [PATCH 18/21] Remove sessions example There are better libraries these days for doing sessions with axum. So think this example was just causing more confusion than helping. --- examples/sessions/Cargo.toml | 14 --- examples/sessions/src/main.rs | 159 ---------------------------------- 2 files changed, 173 deletions(-) delete mode 100644 examples/sessions/Cargo.toml delete mode 100644 examples/sessions/src/main.rs diff --git a/examples/sessions/Cargo.toml b/examples/sessions/Cargo.toml deleted file mode 100644 index 247df4d3c6..0000000000 --- a/examples/sessions/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "example-sessions" -version = "0.1.0" -edition = "2021" -publish = false - -[dependencies] -async-session = "3.0.0" -axum = { path = "../../axum", features = ["headers"] } -serde = { version = "1.0", features = ["derive"] } -tokio = { version = "1.0", features = ["full"] } -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -uuid = { version = "1.0", features = ["v4", "serde"] } diff --git a/examples/sessions/src/main.rs b/examples/sessions/src/main.rs deleted file mode 100644 index 9bea9c1b06..0000000000 --- a/examples/sessions/src/main.rs +++ /dev/null @@ -1,159 +0,0 @@ -//! Run with -//! -//! ```not_rust -//! cargo run -p example-sessions -//! ``` - -use async_session::{MemoryStore, Session, SessionStore as _}; -use axum::{ - async_trait, - extract::{FromRef, FromRequestParts, TypedHeader}, - headers::Cookie, - http::{ - self, - header::{HeaderMap, HeaderValue}, - request::Parts, - StatusCode, - }, - response::IntoResponse, - routing::get, - RequestPartsExt, Router, -}; -use serde::{Deserialize, Serialize}; -use std::fmt::Debug; -use std::net::SocketAddr; -use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; -use uuid::Uuid; - -const AXUM_SESSION_COOKIE_NAME: &str = "axum_session"; - -#[tokio::main] -async fn main() { - tracing_subscriber::registry() - .with( - tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| "example_sessions=debug".into()), - ) - .with(tracing_subscriber::fmt::layer()) - .init(); - - // `MemoryStore` just used as an example. Don't use this in production. - let store = MemoryStore::new(); - - let app = Router::new().route("/", get(handler)).with_state(store); - - let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); - tracing::debug!("listening on {}", addr); - axum::Server::bind(&addr) - .serve(app.into_make_service()) - .await - .unwrap(); -} - -async fn handler(user_id: UserIdFromSession) -> impl IntoResponse { - let (headers, user_id, create_cookie) = match user_id { - UserIdFromSession::FoundUserId(user_id) => (HeaderMap::new(), user_id, false), - UserIdFromSession::CreatedFreshUserId(new_user) => { - let mut headers = HeaderMap::new(); - headers.insert(http::header::SET_COOKIE, new_user.cookie); - (headers, new_user.user_id, true) - } - }; - - tracing::debug!("handler: user_id={:?} send_headers={:?}", user_id, headers); - - ( - headers, - format!( - "user_id={:?} session_cookie_name={} create_new_session_cookie={}", - user_id, AXUM_SESSION_COOKIE_NAME, create_cookie - ), - ) -} - -struct FreshUserId { - pub user_id: UserId, - pub cookie: HeaderValue, -} - -enum UserIdFromSession { - FoundUserId(UserId), - CreatedFreshUserId(FreshUserId), -} - -#[async_trait] -impl FromRequestParts for UserIdFromSession -where - MemoryStore: FromRef, - S: Send + Sync, -{ - type Rejection = (StatusCode, &'static str); - - async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { - let store = MemoryStore::from_ref(state); - - let cookie: Option> = parts.extract().await.unwrap(); - - let session_cookie = cookie - .as_ref() - .and_then(|cookie| cookie.get(AXUM_SESSION_COOKIE_NAME)); - - // return the new created session cookie for client - if session_cookie.is_none() { - let user_id = UserId::new(); - let mut session = Session::new(); - session.insert("user_id", user_id).unwrap(); - let cookie = store.store_session(session).await.unwrap().unwrap(); - return Ok(Self::CreatedFreshUserId(FreshUserId { - user_id, - cookie: HeaderValue::from_str( - format!("{}={}", AXUM_SESSION_COOKIE_NAME, cookie).as_str(), - ) - .unwrap(), - })); - } - - tracing::debug!( - "UserIdFromSession: got session cookie from user agent, {}={}", - AXUM_SESSION_COOKIE_NAME, - session_cookie.unwrap() - ); - // continue to decode the session cookie - let user_id = if let Some(session) = store - .load_session(session_cookie.unwrap().to_owned()) - .await - .unwrap() - { - if let Some(user_id) = session.get::("user_id") { - tracing::debug!( - "UserIdFromSession: session decoded success, user_id={:?}", - user_id - ); - user_id - } else { - return Err(( - StatusCode::INTERNAL_SERVER_ERROR, - "No `user_id` found in session", - )); - } - } else { - tracing::debug!( - "UserIdFromSession: err session not exists in store, {}={}", - AXUM_SESSION_COOKIE_NAME, - session_cookie.unwrap() - ); - return Err((StatusCode::BAD_REQUEST, "No session found for cookie")); - }; - - Ok(Self::FoundUserId(user_id)) - } -} - -#[derive(Serialize, Deserialize, Debug, Clone, Copy)] -struct UserId(Uuid); - -impl UserId { - fn new() -> Self { - Self(Uuid::new_v4()) - } -} From 67d90a2a8cadf2a8b13573209dd748accc95ceab Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Sun, 16 Jul 2023 13:15:15 +0200 Subject: [PATCH 19/21] Fix deny.toml --- deny.toml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/deny.toml b/deny.toml index bc2a9dffc7..ab96ec4d17 100644 --- a/deny.toml +++ b/deny.toml @@ -25,8 +25,14 @@ skip-tree = [ { name = "spin" }, # lots still pulls in syn 1.x { name = "syn" }, - # old version pulled in by tracing-subscriber in dev-deps and examples + # pulled in by tracing-subscriber { name = "regex-syntax" }, + # pulled in by tracing-subscriber + { name = "regex-automata" }, + # pulled in by h2 + { name = "indexmap" }, + # pulled in by axum, can't update without breaking changes + { name = "bitflags" }, ] [sources] From fd54d1a2087d7e4c4b35101f1848e949fc398273 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Sun, 16 Jul 2023 13:27:14 +0200 Subject: [PATCH 20/21] Update UI tests --- axum-macros/tests/typed_path/fail/not_deserialize.stderr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axum-macros/tests/typed_path/fail/not_deserialize.stderr b/axum-macros/tests/typed_path/fail/not_deserialize.stderr index 3f2c9ad500..b476b44f97 100644 --- a/axum-macros/tests/typed_path/fail/not_deserialize.stderr +++ b/axum-macros/tests/typed_path/fail/not_deserialize.stderr @@ -13,7 +13,7 @@ error[E0277]: the trait bound `for<'de> MyPath: serde::de::Deserialize<'de>` is (T0, T1) (T0, T1, T2) (T0, T1, T2, T3) - and 129 others + and $N others = note: required for `MyPath` to implement `serde::de::DeserializeOwned` = note: required for `axum::extract::Path` to implement `FromRequestParts` = note: this error originates in the derive macro `TypedPath` (in Nightly builds, run with -Z macro-backtrace for more info) From feaa86d25ebd099894c6fe8cb040452610359c89 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Sun, 16 Jul 2023 13:49:53 +0200 Subject: [PATCH 21/21] Update to latest sqlx in example (#2099) --- examples/sqlx-postgres/Cargo.toml | 3 +-- examples/sqlx-postgres/src/main.rs | 7 +++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/examples/sqlx-postgres/Cargo.toml b/examples/sqlx-postgres/Cargo.toml index 0587dc7f46..043532bcf8 100644 --- a/examples/sqlx-postgres/Cargo.toml +++ b/examples/sqlx-postgres/Cargo.toml @@ -6,8 +6,7 @@ publish = false [dependencies] axum = { path = "../../axum" } +sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "any", "postgres"] } tokio = { version = "1.0", features = ["full"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } - -sqlx = { version = "0.5.10", features = ["runtime-tokio-rustls", "any", "postgres"] } diff --git a/examples/sqlx-postgres/src/main.rs b/examples/sqlx-postgres/src/main.rs index 8e3353b9a1..b49ab394df 100644 --- a/examples/sqlx-postgres/src/main.rs +++ b/examples/sqlx-postgres/src/main.rs @@ -41,7 +41,7 @@ async fn main() { // setup connection pool let pool = PgPoolOptions::new() .max_connections(5) - .connect_timeout(Duration::from_secs(3)) + .acquire_timeout(Duration::from_secs(3)) .connect(&db_connection_str) .await .expect("can't connect to database"); @@ -95,11 +95,10 @@ where } async fn using_connection_extractor( - DatabaseConnection(conn): DatabaseConnection, + DatabaseConnection(mut conn): DatabaseConnection, ) -> Result { - let mut conn = conn; sqlx::query_scalar("select 'hello world from pg'") - .fetch_one(&mut conn) + .fetch_one(&mut *conn) .await .map_err(internal_error) }