diff --git a/Cargo.toml b/Cargo.toml index f9c9d027b2..298b9b3b3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,41 @@ resolver = "2" [workspace.package] rust-version = "1.75" + +[workspace.lints.rust] +unsafe_code = "forbid" + +rust_2018_idioms = { level = "warn", priority = -1 } +missing_debug_implementations = "warn" +missing_docs = "warn" +unreachable_pub = "warn" + +[workspace.lints.clippy] +type_complexity = "allow" + +await_holding_lock = "warn" +dbg_macro = "warn" +empty_enum = "warn" +enum_glob_use = "warn" +exit = "warn" +filter_map_next = "warn" +fn_params_excessive_bools = "warn" +if_let_mutex = "warn" +imprecise_flops = "warn" +inefficient_to_string = "warn" +linkedlist = "warn" +lossy_float_literal = "warn" +macro_use_imports = "warn" +match_on_vec_items = "warn" +match_wildcard_for_single_variants = "warn" +mem_forget = "warn" +needless_borrow = "warn" +needless_continue = "warn" +option_option = "warn" +rest_pat_in_fully_bound_structs = "warn" +str_to_string = "warn" +suboptimal_flops = "warn" +todo = "warn" +unnested_or_patterns = "warn" +unused_self = "warn" +verbose_file_reads = "warn" diff --git a/ECOSYSTEM.md b/ECOSYSTEM.md index b97e917dd0..5e13e4c1bc 100644 --- a/ECOSYSTEM.md +++ b/ECOSYSTEM.md @@ -50,6 +50,7 @@ If your project isn't listed here and you would like it to be, please feel free - [spring-rs](https://github.com/spring-rs/spring-rs): spring-rs is a microservice framework written in rust inspired by java's spring-boot, based on axum - [zino](https://github.com/zino-rs/zino): Zino is a next-generation framework for composable applications which provides full integrations with axum. - [axum-rails-cookie](https://github.com/endoze/axum-rails-cookie): Extract rails session cookies in axum based apps. +- [axum-ws-broadcaster](https://github.com/Necoo33/axum-ws-broadcaster): A broadcasting liblary for both [axum-typed-websockets](https://crates.io/crates/axum-typed-websockets) and `axum::extract::ws`. ## Project showcase diff --git a/axum-core/Cargo.toml b/axum-core/Cargo.toml index d8207e1399..adfe5b3a16 100644 --- a/axum-core/Cargo.toml +++ b/axum-core/Cargo.toml @@ -42,6 +42,9 @@ hyper = "1.0.0" tokio = { version = "1.25.0", features = ["macros"] } tower-http = { version = "0.6.0", features = ["limit"] } +[lints] +workspace = true + [package.metadata.cargo-public-api-crates] allowed = [ # not 1.0 diff --git a/axum-core/src/lib.rs b/axum-core/src/lib.rs index 134c566b30..60f0dcfcd3 100644 --- a/axum-core/src/lib.rs +++ b/axum-core/src/lib.rs @@ -8,43 +8,6 @@ //! [`axum`]: https://crates.io/crates/axum //! [`axum-core`]: http://crates.io/crates/axum-core -#![warn( - clippy::all, - clippy::dbg_macro, - clippy::todo, - clippy::empty_enum, - clippy::enum_glob_use, - clippy::mem_forget, - clippy::unused_self, - clippy::filter_map_next, - clippy::needless_continue, - clippy::needless_borrow, - clippy::match_wildcard_for_single_variants, - clippy::if_let_mutex, - clippy::await_holding_lock, - clippy::match_on_vec_items, - clippy::imprecise_flops, - clippy::suboptimal_flops, - clippy::lossy_float_literal, - clippy::rest_pat_in_fully_bound_structs, - clippy::fn_params_excessive_bools, - clippy::exit, - clippy::inefficient_to_string, - clippy::linkedlist, - clippy::macro_use_imports, - clippy::option_option, - clippy::verbose_file_reads, - clippy::unnested_or_patterns, - clippy::str_to_string, - rust_2018_idioms, - future_incompatible, - nonstandard_style, - missing_debug_implementations, - missing_docs -)] -#![deny(unreachable_pub)] -#![allow(elided_lifetimes_in_paths, clippy::type_complexity)] -#![forbid(unsafe_code)] #![cfg_attr(test, allow(clippy::float_cmp))] #![cfg_attr(not(test), warn(clippy::print_stdout, clippy::dbg_macro))] diff --git a/axum-extra/CHANGELOG.md b/axum-extra/CHANGELOG.md index 4690a49465..b6a7300380 100644 --- a/axum-extra/CHANGELOG.md +++ b/axum-extra/CHANGELOG.md @@ -8,13 +8,18 @@ and this project adheres to [Semantic Versioning]. # Unreleased - **fixed:** `Host` extractor includes port number when parsing authority ([#2242]) +- **changed:** The `multipart` feature is no longer on by default ([#3058]) - **added:** Add `RouterExt::typed_connect` ([#2961]) - **added:** Add `json!` for easy construction of JSON responses ([#2962]) +- **added:** Add `InternalServerError` response for logging an internal error + and returning HTTP 500 in a convenient way. ([#3010]) - **added:** Add `FileStream` for easy construction of file stream responses ([#3047]) [#2242]: https://github.com/tokio-rs/axum/pull/2242 +[#3058]: https://github.com/tokio-rs/axum/pull/3058 [#2961]: https://github.com/tokio-rs/axum/pull/2961 [#2962]: https://github.com/tokio-rs/axum/pull/2962 +[#3010]: https://github.com/tokio-rs/axum/pull/3010 [#3047]: https://github.com/tokio-rs/axum/pull/3047 # 0.10.0 diff --git a/axum-extra/Cargo.toml b/axum-extra/Cargo.toml index 6c37e5c24b..036bba2478 100644 --- a/axum-extra/Cargo.toml +++ b/axum-extra/Cargo.toml @@ -12,12 +12,12 @@ repository = "https://github.com/tokio-rs/axum" version = "0.10.0-alpha.1" [features] -default = ["tracing", "multipart"] +default = ["tracing"] async-read-body = ["dep:tokio-util", "tokio-util?/io", "dep:tokio"] file-stream = ["dep:tokio-util", "tokio-util?/io", "dep:tokio", "tokio?/fs", "tokio?/io-util", "dep:async-stream"] attachment = ["dep:tracing"] -error_response = ["dep:tracing", "tracing/std"] +error-response = ["dep:tracing", "tracing/std"] cookie = ["dep:cookie"] cookie-private = ["cookie", "cookie?/private"] cookie-signed = ["cookie", "cookie?/signed"] @@ -87,6 +87,9 @@ tokio = { version = "1.14", features = ["full"] } tower = { version = "0.5.1", features = ["util"] } tower-http = { version = "0.6.0", features = ["map-response-body", "timeout"] } +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true diff --git a/axum-extra/src/extract/cookie/private.rs b/axum-extra/src/extract/cookie/private.rs index 937d73e6b8..f852b8c4ba 100644 --- a/axum-extra/src/extract/cookie/private.rs +++ b/axum-extra/src/extract/cookie/private.rs @@ -48,11 +48,11 @@ use std::{convert::Infallible, fmt, marker::PhantomData}; /// // our application state /// #[derive(Clone)] /// struct AppState { -/// // that holds the key used to sign cookies +/// // that holds the key used to encrypt cookies /// key: Key, /// } /// -/// // this impl tells `SignedCookieJar` how to access the key from our state +/// // this impl tells `PrivateCookieJar` how to access the key from our state /// impl FromRef for Key { /// fn from_ref(state: &AppState) -> Self { /// state.key.clone() diff --git a/axum-extra/src/extract/json_deserializer.rs b/axum-extra/src/extract/json_deserializer.rs index 4a84a72418..051ab0f1bd 100644 --- a/axum-extra/src/extract/json_deserializer.rs +++ b/axum-extra/src/extract/json_deserializer.rs @@ -202,7 +202,7 @@ fn json_content_type(headers: &HeaderMap) -> bool { }; let is_json_content_type = mime.type_() == "application" - && (mime.subtype() == "json" || mime.suffix().map_or(false, |name| name == "json")); + && (mime.subtype() == "json" || mime.suffix().is_some_and(|name| name == "json")); is_json_content_type } diff --git a/axum-extra/src/lib.rs b/axum-extra/src/lib.rs index 468658081a..e14c5a16fc 100644 --- a/axum-extra/src/lib.rs +++ b/axum-extra/src/lib.rs @@ -16,6 +16,7 @@ //! `cookie-signed` | Enables the [`SignedCookieJar`](crate::extract::SignedCookieJar) extractor | No //! `cookie-key-expansion` | Enables the [`Key::derive_from`](crate::extract::cookie::Key::derive_from) method | No //! `erased-json` | Enables the [`ErasedJson`](crate::response::ErasedJson) response | No +//! `error-response` | Enables the [`InternalServerError`](crate::response::InternalServerError) response | No //! `form` | Enables the [`Form`](crate::extract::Form) extractor | No //! `json-deserializer` | Enables the [`JsonDeserializer`](crate::extract::JsonDeserializer) extractor | No //! `json-lines` | Enables the [`JsonLines`](crate::extract::JsonLines) extractor and response | No @@ -29,43 +30,6 @@ //! //! [`axum`]: https://crates.io/crates/axum -#![warn( - clippy::all, - clippy::dbg_macro, - clippy::todo, - clippy::empty_enum, - clippy::enum_glob_use, - clippy::mem_forget, - clippy::unused_self, - clippy::filter_map_next, - clippy::needless_continue, - clippy::needless_borrow, - clippy::match_wildcard_for_single_variants, - clippy::if_let_mutex, - clippy::await_holding_lock, - clippy::match_on_vec_items, - clippy::imprecise_flops, - clippy::suboptimal_flops, - clippy::lossy_float_literal, - clippy::rest_pat_in_fully_bound_structs, - clippy::fn_params_excessive_bools, - clippy::exit, - clippy::inefficient_to_string, - clippy::linkedlist, - clippy::macro_use_imports, - clippy::option_option, - clippy::verbose_file_reads, - clippy::unnested_or_patterns, - clippy::str_to_string, - rust_2018_idioms, - future_incompatible, - nonstandard_style, - missing_debug_implementations, - missing_docs -)] -#![deny(unreachable_pub)] -#![allow(elided_lifetimes_in_paths, clippy::type_complexity)] -#![forbid(unsafe_code)] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(test, allow(clippy::float_cmp))] #![cfg_attr(not(test), warn(clippy::print_stdout, clippy::dbg_macro))] diff --git a/axum-extra/src/response/mod.rs b/axum-extra/src/response/mod.rs index 9240c11a23..044d2b90b1 100644 --- a/axum-extra/src/response/mod.rs +++ b/axum-extra/src/response/mod.rs @@ -9,7 +9,7 @@ mod attachment; #[cfg(feature = "multipart")] pub mod multiple; -#[cfg(feature = "error_response")] +#[cfg(feature = "error-response")] mod error_response; #[cfg(feature = "file-stream")] @@ -22,7 +22,7 @@ pub use file_stream::FileStream; #[cfg(feature = "file-stream")] pub use file_stream::AsyncReaderStream; -#[cfg(feature = "error_response")] +#[cfg(feature = "error-response")] pub use error_response::InternalServerError; #[cfg(feature = "erased-json")] diff --git a/axum-macros/Cargo.toml b/axum-macros/Cargo.toml index 1960a465da..8ba7c099db 100644 --- a/axum-macros/Cargo.toml +++ b/axum-macros/Cargo.toml @@ -38,6 +38,9 @@ syn = { version = "2.0", features = ["full", "extra-traits"] } tokio = { version = "1.25.0", features = ["full"] } trybuild = "1.0.63" +[lints] +workspace = true + [package.metadata.cargo-public-api-crates] allowed = [] diff --git a/axum-macros/src/attr_parsing.rs b/axum-macros/src/attr_parsing.rs index 67da01eb68..9e3b4bca7a 100644 --- a/axum-macros/src/attr_parsing.rs +++ b/axum-macros/src/attr_parsing.rs @@ -5,7 +5,7 @@ use syn::{ }; pub(crate) fn parse_parenthesized_attribute( - input: ParseStream, + input: ParseStream<'_>, out: &mut Option<(K, T)>, ) -> syn::Result<()> where @@ -30,7 +30,7 @@ where } pub(crate) fn parse_assignment_attribute( - input: ParseStream, + input: ParseStream<'_>, out: &mut Option<(K, T)>, ) -> syn::Result<()> where diff --git a/axum-macros/src/axum_test.rs b/axum-macros/src/axum_test.rs index bc664fd8f3..0e881ef2da 100644 --- a/axum-macros/src/axum_test.rs +++ b/axum-macros/src/axum_test.rs @@ -16,7 +16,7 @@ pub(crate) fn expand(_attr: Attrs, mut item_fn: ItemFn) -> TokenStream { pub(crate) struct Attrs; impl Parse for Attrs { - fn parse(_input: syn::parse::ParseStream) -> syn::Result { + fn parse(_input: syn::parse::ParseStream<'_>) -> syn::Result { Ok(Self) } } diff --git a/axum-macros/src/debug_handler.rs b/axum-macros/src/debug_handler.rs index 3a37c17ab3..82f383286a 100644 --- a/axum-macros/src/debug_handler.rs +++ b/axum-macros/src/debug_handler.rs @@ -122,7 +122,7 @@ pub(crate) struct Attrs { } impl Parse for Attrs { - fn parse(input: syn::parse::ParseStream) -> syn::Result { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { let mut state_ty = None; while !input.is_empty() { @@ -225,7 +225,7 @@ fn check_inputs_impls_from_request( state_ty: Type, kind: FunctionKind, ) -> TokenStream { - let takes_self = item_fn.sig.inputs.first().map_or(false, |arg| match arg { + let takes_self = item_fn.sig.inputs.first().is_some_and(|arg| match arg { FnArg::Receiver(_) => true, FnArg::Typed(typed) => is_self_pat_type(typed), }); @@ -604,10 +604,7 @@ fn request_consuming_type_name(ty: &Type) -> Option<&'static str> { } fn well_known_last_response_type(ty: &Type) -> Option<&'static str> { - let typename = match extract_clean_typename(ty) { - Some(tn) => tn, - None => return None, - }; + let typename = extract_clean_typename(ty)?; let type_name = match &*typename { "Json" => "Json<_>", diff --git a/axum-macros/src/from_ref.rs b/axum-macros/src/from_ref.rs index 1a27765a4f..9cf317da97 100644 --- a/axum-macros/src/from_ref.rs +++ b/axum-macros/src/from_ref.rs @@ -73,7 +73,7 @@ pub(super) struct FieldAttrs { } impl Parse for FieldAttrs { - fn parse(input: ParseStream) -> syn::Result { + fn parse(input: ParseStream<'_>) -> syn::Result { let mut skip = None; while !input.is_empty() { diff --git a/axum-macros/src/from_request.rs b/axum-macros/src/from_request.rs index a6c95ab7ff..145fdad361 100644 --- a/axum-macros/src/from_request.rs +++ b/axum-macros/src/from_request.rs @@ -290,11 +290,7 @@ fn parse_single_generic_type_on_struct( let field = fields_unnamed.unnamed.first().unwrap(); if let syn::Type::Path(type_path) = &field.ty { - if type_path - .path - .get_ident() - .map_or(true, |field_type_ident| field_type_ident != ty_ident) - { + if type_path.path.get_ident() != Some(ty_ident) { return Err(syn::Error::new_spanned( type_path, format_args!( diff --git a/axum-macros/src/from_request/attr.rs b/axum-macros/src/from_request/attr.rs index 77dfc470ae..ae56075a25 100644 --- a/axum-macros/src/from_request/attr.rs +++ b/axum-macros/src/from_request/attr.rs @@ -18,7 +18,7 @@ pub(super) struct FromRequestContainerAttrs { } impl Parse for FromRequestContainerAttrs { - fn parse(input: ParseStream) -> syn::Result { + fn parse(input: ParseStream<'_>) -> syn::Result { let mut via = None; let mut rejection = None; let mut state = None; @@ -66,7 +66,7 @@ pub(super) struct FromRequestFieldAttrs { } impl Parse for FromRequestFieldAttrs { - fn parse(input: ParseStream) -> syn::Result { + fn parse(input: ParseStream<'_>) -> syn::Result { let mut via = None; while !input.is_empty() { diff --git a/axum-macros/src/lib.rs b/axum-macros/src/lib.rs index f5aeaab748..86b2c1c21b 100644 --- a/axum-macros/src/lib.rs +++ b/axum-macros/src/lib.rs @@ -2,43 +2,6 @@ //! //! [`axum`]: https://crates.io/crates/axum -#![warn( - clippy::all, - clippy::dbg_macro, - clippy::todo, - clippy::empty_enum, - clippy::enum_glob_use, - clippy::mem_forget, - clippy::unused_self, - clippy::filter_map_next, - clippy::needless_continue, - clippy::needless_borrow, - clippy::match_wildcard_for_single_variants, - clippy::if_let_mutex, - clippy::await_holding_lock, - clippy::match_on_vec_items, - clippy::imprecise_flops, - clippy::suboptimal_flops, - clippy::lossy_float_literal, - clippy::rest_pat_in_fully_bound_structs, - clippy::fn_params_excessive_bools, - clippy::exit, - clippy::inefficient_to_string, - clippy::linkedlist, - clippy::macro_use_imports, - clippy::option_option, - clippy::verbose_file_reads, - clippy::unnested_or_patterns, - clippy::str_to_string, - rust_2018_idioms, - future_incompatible, - nonstandard_style, - missing_debug_implementations, - missing_docs -)] -#![deny(unreachable_pub)] -#![allow(elided_lifetimes_in_paths, clippy::type_complexity)] -#![forbid(unsafe_code)] #![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(test, allow(clippy::float_cmp))] #![cfg_attr(not(test), warn(clippy::print_stdout, clippy::dbg_macro))] diff --git a/axum-macros/src/typed_path.rs b/axum-macros/src/typed_path.rs index fa272252be..397cc94c0d 100644 --- a/axum-macros/src/typed_path.rs +++ b/axum-macros/src/typed_path.rs @@ -55,7 +55,7 @@ struct Attrs { } impl Parse for Attrs { - fn parse(input: syn::parse::ParseStream) -> syn::Result { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { let mut path = None; let mut rejection = None; diff --git a/axum-macros/tests/debug_handler/fail/multiple_request_consumers.rs b/axum-macros/tests/debug_handler/fail/multiple_request_consumers.rs index 4c86ceae69..77235dfdb6 100644 --- a/axum-macros/tests/debug_handler/fail/multiple_request_consumers.rs +++ b/axum-macros/tests/debug_handler/fail/multiple_request_consumers.rs @@ -1,5 +1,9 @@ +use axum::{ + body::Bytes, + http::{Method, Uri}, + Json, +}; use axum_macros::debug_handler; -use axum::{Json, body::Bytes, http::{Method, Uri}}; #[debug_handler] async fn one(_: Json<()>, _: String, _: Uri) {} diff --git a/axum-macros/tests/debug_handler/fail/multiple_request_consumers.stderr b/axum-macros/tests/debug_handler/fail/multiple_request_consumers.stderr index ba2ff7adff..011ce89934 100644 --- a/axum-macros/tests/debug_handler/fail/multiple_request_consumers.stderr +++ b/axum-macros/tests/debug_handler/fail/multiple_request_consumers.stderr @@ -1,11 +1,11 @@ error: Can't have two extractors that consume the request body. `Json<_>` and `String` both do that. - --> tests/debug_handler/fail/multiple_request_consumers.rs:5:14 + --> tests/debug_handler/fail/multiple_request_consumers.rs:9:14 | -5 | async fn one(_: Json<()>, _: String, _: Uri) {} +9 | async fn one(_: Json<()>, _: String, _: Uri) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Can't have more than one extractor that consume the request body. `Json<_>`, `Bytes`, and `String` all do that. - --> tests/debug_handler/fail/multiple_request_consumers.rs:8:14 - | -8 | async fn two(_: Json<()>, _: Method, _: Bytes, _: Uri, _: String) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/debug_handler/fail/multiple_request_consumers.rs:12:14 + | +12 | async fn two(_: Json<()>, _: Method, _: Bytes, _: Uri, _: String) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/axum-macros/tests/debug_handler/fail/output_tuple_too_many.rs b/axum-macros/tests/debug_handler/fail/output_tuple_too_many.rs index a6f1ba0866..ea15e66a37 100644 --- a/axum-macros/tests/debug_handler/fail/output_tuple_too_many.rs +++ b/axum-macros/tests/debug_handler/fail/output_tuple_too_many.rs @@ -1,8 +1,7 @@ use axum::response::AppendHeaders; #[axum::debug_handler] -async fn handler( -) -> ( +async fn handler() -> ( axum::http::StatusCode, AppendHeaders<[(axum::http::HeaderName, &'static str); 1]>, AppendHeaders<[(axum::http::HeaderName, &'static str); 1]>, diff --git a/axum-macros/tests/debug_handler/fail/output_tuple_too_many.stderr b/axum-macros/tests/debug_handler/fail/output_tuple_too_many.stderr index b6eb85079d..fb31388a4e 100644 --- a/axum-macros/tests/debug_handler/fail/output_tuple_too_many.stderr +++ b/axum-macros/tests/debug_handler/fail/output_tuple_too_many.stderr @@ -1,12 +1,12 @@ error: Cannot return tuples with more than 17 elements - --> tests/debug_handler/fail/output_tuple_too_many.rs:5:3 + --> tests/debug_handler/fail/output_tuple_too_many.rs:4:20 | -5 | ) -> ( - | ___^ -6 | | axum::http::StatusCode, +4 | async fn handler() -> ( + | ____________________^ +5 | | axum::http::StatusCode, +6 | | AppendHeaders<[(axum::http::HeaderName, &'static str); 1]>, 7 | | AppendHeaders<[(axum::http::HeaderName, &'static str); 1]>, -8 | | AppendHeaders<[(axum::http::HeaderName, &'static str); 1]>, ... | -24 | | axum::http::StatusCode, -25 | | ) { +23 | | axum::http::StatusCode, +24 | | ) { | |_^ diff --git a/axum-macros/tests/debug_handler/fail/returning_request_parts.rs b/axum-macros/tests/debug_handler/fail/returning_request_parts.rs index e29cba3f23..0658dc02bf 100644 --- a/axum-macros/tests/debug_handler/fail/returning_request_parts.rs +++ b/axum-macros/tests/debug_handler/fail/returning_request_parts.rs @@ -1,10 +1,9 @@ #[axum::debug_handler] -async fn handler( -) -> ( +async fn handler() -> ( axum::http::request::Parts, // this should be response parts, not request parts axum::http::StatusCode, ) { panic!() } -fn main(){} +fn main() {} diff --git a/axum-macros/tests/debug_handler/fail/returning_request_parts.stderr b/axum-macros/tests/debug_handler/fail/returning_request_parts.stderr index c3935ad52d..440f20fe58 100644 --- a/axum-macros/tests/debug_handler/fail/returning_request_parts.stderr +++ b/axum-macros/tests/debug_handler/fail/returning_request_parts.stderr @@ -1,7 +1,7 @@ error[E0308]: mismatched types - --> tests/debug_handler/fail/returning_request_parts.rs:4:5 + --> tests/debug_handler/fail/returning_request_parts.rs:3:5 | -4 | axum::http::request::Parts, // this should be response parts, not request parts +3 | axum::http::request::Parts, // this should be response parts, not request parts | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | expected `axum::http::response::Parts`, found `axum::http::request::Parts` diff --git a/axum-macros/tests/debug_handler/fail/too_many_extractors.rs b/axum-macros/tests/debug_handler/fail/too_many_extractors.rs index 441d0f0059..894a4e0d46 100644 --- a/axum-macros/tests/debug_handler/fail/too_many_extractors.rs +++ b/axum-macros/tests/debug_handler/fail/too_many_extractors.rs @@ -1,5 +1,5 @@ -use axum_macros::debug_handler; use axum::http::Uri; +use axum_macros::debug_handler; #[debug_handler] async fn handler( @@ -20,6 +20,7 @@ async fn handler( _e15: Uri, _e16: Uri, _e17: Uri, -) {} +) { +} fn main() {} diff --git a/axum-macros/tests/debug_handler/fail/wrong_order.rs b/axum-macros/tests/debug_handler/fail/wrong_order.rs index 7d22bf5251..8dfd73670f 100644 --- a/axum-macros/tests/debug_handler/fail/wrong_order.rs +++ b/axum-macros/tests/debug_handler/fail/wrong_order.rs @@ -1,5 +1,5 @@ +use axum::{http::Uri, Json}; use axum_macros::debug_handler; -use axum::{Json, http::Uri}; #[debug_handler] async fn one(_: Json<()>, _: Uri) {} diff --git a/axum-macros/tests/debug_handler/fail/wrong_return_tuple.rs b/axum-macros/tests/debug_handler/fail/wrong_return_tuple.rs index c2b4249520..0b2afa168e 100644 --- a/axum-macros/tests/debug_handler/fail/wrong_return_tuple.rs +++ b/axum-macros/tests/debug_handler/fail/wrong_return_tuple.rs @@ -4,16 +4,13 @@ async fn named_type() -> ( axum::http::StatusCode, axum::Json<&'static str>, - axum::response::AppendHeaders<[( axum::http::HeaderName,&'static str); 1]>, + axum::response::AppendHeaders<[(axum::http::HeaderName, &'static str); 1]>, ) { panic!() } - -struct CustomIntoResponse{ - -} -impl axum::response::IntoResponse for CustomIntoResponse{ +struct CustomIntoResponse {} +impl axum::response::IntoResponse for CustomIntoResponse { fn into_response(self) -> axum::response::Response { todo!() } @@ -22,7 +19,7 @@ impl axum::response::IntoResponse for CustomIntoResponse{ async fn custom_type() -> ( axum::http::StatusCode, CustomIntoResponse, - axum::response::AppendHeaders<[( axum::http::HeaderName,&'static str); 1]>, + axum::response::AppendHeaders<[(axum::http::HeaderName, &'static str); 1]>, ) { panic!() } diff --git a/axum-macros/tests/debug_handler/fail/wrong_return_tuple.stderr b/axum-macros/tests/debug_handler/fail/wrong_return_tuple.stderr index 77597b3358..8779d35805 100644 --- a/axum-macros/tests/debug_handler/fail/wrong_return_tuple.stderr +++ b/axum-macros/tests/debug_handler/fail/wrong_return_tuple.stderr @@ -5,9 +5,9 @@ error: `Json<_>` must be the last element in a response tuple | ^^^^^^^^^^^^^^^^^^^^^^^^ error[E0277]: the trait bound `CustomIntoResponse: IntoResponseParts` is not satisfied - --> tests/debug_handler/fail/wrong_return_tuple.rs:24:5 + --> tests/debug_handler/fail/wrong_return_tuple.rs:21:5 | -24 | CustomIntoResponse, +21 | CustomIntoResponse, | ^^^^^^^^^^^^^^^^^^ the trait `IntoResponseParts` is not implemented for `CustomIntoResponse` | = help: the following other types implement trait `IntoResponseParts`: @@ -27,9 +27,9 @@ help: add `#![feature(trivial_bounds)]` to the crate attributes to enable | error[E0277]: the trait bound `CustomIntoResponse: IntoResponseParts` is not satisfied - --> tests/debug_handler/fail/wrong_return_tuple.rs:24:5 + --> tests/debug_handler/fail/wrong_return_tuple.rs:21:5 | -24 | CustomIntoResponse, +21 | CustomIntoResponse, | ^^^^^^^^^^^^^^^^^^ the trait `IntoResponseParts` is not implemented for `CustomIntoResponse` | = help: the following other types implement trait `IntoResponseParts`: @@ -43,7 +43,7 @@ error[E0277]: the trait bound `CustomIntoResponse: IntoResponseParts` is not sat (T1, T2, T3, T4, T5, T6, T7, T8) and $N others note: required by a bound in `__axum_macros_check_custom_type_into_response_parts_1_check` - --> tests/debug_handler/fail/wrong_return_tuple.rs:24:5 + --> tests/debug_handler/fail/wrong_return_tuple.rs:21:5 | -24 | CustomIntoResponse, +21 | CustomIntoResponse, | ^^^^^^^^^^^^^^^^^^ required by this bound in `__axum_macros_check_custom_type_into_response_parts_1_check` diff --git a/axum-macros/tests/debug_handler/pass/impl_into_response.rs b/axum-macros/tests/debug_handler/pass/impl_into_response.rs index 69b884e390..f15f29c4d0 100644 --- a/axum-macros/tests/debug_handler/pass/impl_into_response.rs +++ b/axum-macros/tests/debug_handler/pass/impl_into_response.rs @@ -1,5 +1,5 @@ -use axum_macros::debug_handler; use axum::response::IntoResponse; +use axum_macros::debug_handler; #[debug_handler] async fn handler() -> impl IntoResponse { diff --git a/axum-macros/tests/debug_handler/pass/infer_state.rs b/axum-macros/tests/debug_handler/pass/infer_state.rs index 9f21a8a626..fceeb78acc 100644 --- a/axum-macros/tests/debug_handler/pass/infer_state.rs +++ b/axum-macros/tests/debug_handler/pass/infer_state.rs @@ -1,5 +1,5 @@ -use axum_macros::debug_handler; use axum::extract::State; +use axum_macros::debug_handler; #[debug_handler] async fn handler(_: State) {} @@ -8,22 +8,13 @@ async fn handler(_: State) {} async fn handler_2(_: axum::extract::State) {} #[debug_handler] -async fn handler_3( - _: axum::extract::State, - _: axum::extract::State, -) {} +async fn handler_3(_: axum::extract::State, _: axum::extract::State) {} #[debug_handler] -async fn handler_4( - _: State, - _: State, -) {} +async fn handler_4(_: State, _: State) {} #[debug_handler] -async fn handler_5( - _: axum::extract::State, - _: State, -) {} +async fn handler_5(_: axum::extract::State, _: State) {} #[derive(Clone)] struct AppState; diff --git a/axum-macros/tests/debug_handler/pass/multiple_extractors.rs b/axum-macros/tests/debug_handler/pass/multiple_extractors.rs index 6cc05b5166..e54c43e61a 100644 --- a/axum-macros/tests/debug_handler/pass/multiple_extractors.rs +++ b/axum-macros/tests/debug_handler/pass/multiple_extractors.rs @@ -1,5 +1,5 @@ -use axum_macros::debug_handler; use axum::http::{Method, Uri}; +use axum_macros::debug_handler; #[debug_handler] async fn handler(_one: Method, _two: Uri, _three: String) {} diff --git a/axum-macros/tests/debug_handler/pass/ready.rs b/axum-macros/tests/debug_handler/pass/ready.rs index 4ee73e99c8..d705b8fefb 100644 --- a/axum-macros/tests/debug_handler/pass/ready.rs +++ b/axum-macros/tests/debug_handler/pass/ready.rs @@ -1,5 +1,5 @@ use axum_macros::debug_handler; -use std::future::{Ready, ready}; +use std::future::{ready, Ready}; #[debug_handler] fn handler() -> Ready<()> { diff --git a/axum-macros/tests/debug_handler/pass/state_and_body.rs b/axum-macros/tests/debug_handler/pass/state_and_body.rs index f348360b3a..629023aa03 100644 --- a/axum-macros/tests/debug_handler/pass/state_and_body.rs +++ b/axum-macros/tests/debug_handler/pass/state_and_body.rs @@ -1,5 +1,5 @@ +use axum::{extract::Request, extract::State}; use axum_macros::debug_handler; -use axum::{extract::State, extract::Request}; #[debug_handler(state = AppState)] async fn handler(_: State, _: Request) {} diff --git a/axum-macros/tests/debug_middleware/fail/next_not_last.rs b/axum-macros/tests/debug_middleware/fail/next_not_last.rs index 0108c85433..d2596ffb50 100644 --- a/axum-macros/tests/debug_middleware/fail/next_not_last.rs +++ b/axum-macros/tests/debug_middleware/fail/next_not_last.rs @@ -1,9 +1,4 @@ -use axum::{ - extract::Request, - response::Response, - middleware::Next, - debug_middleware, -}; +use axum::{debug_middleware, extract::Request, middleware::Next, response::Response}; #[debug_middleware] async fn my_middleware(next: Next, request: Request) -> Response { diff --git a/axum-macros/tests/debug_middleware/fail/next_not_last.stderr b/axum-macros/tests/debug_middleware/fail/next_not_last.stderr index 8f08bed72d..4a5fea4546 100644 --- a/axum-macros/tests/debug_middleware/fail/next_not_last.stderr +++ b/axum-macros/tests/debug_middleware/fail/next_not_last.stderr @@ -1,5 +1,5 @@ error: `axum::middleware::Next` must the last argument - --> tests/debug_middleware/fail/next_not_last.rs:9:24 + --> tests/debug_middleware/fail/next_not_last.rs:4:24 | -9 | async fn my_middleware(next: Next, request: Request) -> Response { +4 | async fn my_middleware(next: Next, request: Request) -> Response { | ^^^^^^^^^^ diff --git a/axum-macros/tests/debug_middleware/pass/basic.rs b/axum-macros/tests/debug_middleware/pass/basic.rs index 605cacfd40..1d2a412ac4 100644 --- a/axum-macros/tests/debug_middleware/pass/basic.rs +++ b/axum-macros/tests/debug_middleware/pass/basic.rs @@ -1,9 +1,4 @@ -use axum::{ - extract::Request, - response::Response, - middleware::Next, - debug_middleware, -}; +use axum::{debug_middleware, extract::Request, middleware::Next, response::Response}; #[debug_middleware] async fn my_middleware(request: Request, next: Next) -> Response { diff --git a/axum-macros/tests/from_ref/pass/basic.rs b/axum-macros/tests/from_ref/pass/basic.rs index e410e11a05..2b66d4064d 100644 --- a/axum-macros/tests/from_ref/pass/basic.rs +++ b/axum-macros/tests/from_ref/pass/basic.rs @@ -1,4 +1,8 @@ -use axum::{Router, routing::get, extract::{State, FromRef}}; +use axum::{ + extract::{FromRef, State}, + routing::get, + Router, +}; // This will implement `FromRef` for each field in the struct. #[derive(Clone, FromRef)] @@ -14,7 +18,5 @@ fn main() { auth_token: Default::default(), }; - let _: axum::Router = Router::new() - .route("/", get(handler)) - .with_state(state); + let _: axum::Router = Router::new().route("/", get(handler)).with_state(state); } diff --git a/axum-macros/tests/from_request/fail/enum_from_request_ident_in_variant.rs b/axum-macros/tests/from_request/fail/enum_from_request_ident_in_variant.rs index 336850e5a4..69942e4476 100644 --- a/axum-macros/tests/from_request/fail/enum_from_request_ident_in_variant.rs +++ b/axum-macros/tests/from_request/fail/enum_from_request_ident_in_variant.rs @@ -6,7 +6,7 @@ enum Extractor { Foo { #[from_request(via(axum::Extension))] foo: (), - } + }, } fn main() {} diff --git a/axum-macros/tests/from_request/fail/state_infer_multiple_different_types.rs b/axum-macros/tests/from_request/fail/state_infer_multiple_different_types.rs index 6533d3276a..18c0698f9f 100644 --- a/axum-macros/tests/from_request/fail/state_infer_multiple_different_types.rs +++ b/axum-macros/tests/from_request/fail/state_infer_multiple_different_types.rs @@ -1,5 +1,5 @@ -use axum_macros::FromRequest; use axum::extract::State; +use axum_macros::FromRequest; #[derive(FromRequest)] struct Extractor { diff --git a/axum-macros/tests/from_request/pass/container_parts.rs b/axum-macros/tests/from_request/pass/container_parts.rs index dedc1719a7..c90703d0fc 100644 --- a/axum-macros/tests/from_request/pass/container_parts.rs +++ b/axum-macros/tests/from_request/pass/container_parts.rs @@ -1,5 +1,5 @@ use axum::{ - extract::{FromRequestParts, Extension}, + extract::{Extension, FromRequestParts}, response::Response, }; diff --git a/axum-macros/tests/from_request/pass/named.rs b/axum-macros/tests/from_request/pass/named.rs index f63ae8e9db..d396847cce 100644 --- a/axum-macros/tests/from_request/pass/named.rs +++ b/axum-macros/tests/from_request/pass/named.rs @@ -1,11 +1,8 @@ -use axum::{ - extract::FromRequest, - response::Response, -}; +use axum::{extract::FromRequest, response::Response}; use axum_extra::{ - TypedHeader, - typed_header::TypedHeaderRejection, headers::{self, UserAgent}, + typed_header::TypedHeaderRejection, + TypedHeader, }; #[derive(FromRequest)] diff --git a/axum-macros/tests/from_request/pass/named_parts.rs b/axum-macros/tests/from_request/pass/named_parts.rs index cbb67e61da..1168b3a119 100644 --- a/axum-macros/tests/from_request/pass/named_parts.rs +++ b/axum-macros/tests/from_request/pass/named_parts.rs @@ -1,11 +1,8 @@ -use axum::{ - extract::FromRequestParts, - response::Response, -}; +use axum::{extract::FromRequestParts, response::Response}; use axum_extra::{ - TypedHeader, - typed_header::TypedHeaderRejection, headers::{self, UserAgent}, + typed_header::TypedHeaderRejection, + TypedHeader, }; #[derive(FromRequestParts)] diff --git a/axum-macros/tests/from_request/pass/named_via.rs b/axum-macros/tests/from_request/pass/named_via.rs index 691627b08d..5159c49b88 100644 --- a/axum-macros/tests/from_request/pass/named_via.rs +++ b/axum-macros/tests/from_request/pass/named_via.rs @@ -1,11 +1,11 @@ use axum::{ - response::Response, extract::{Extension, FromRequest}, + response::Response, }; use axum_extra::{ - TypedHeader, - typed_header::TypedHeaderRejection, headers::{self, UserAgent}, + typed_header::TypedHeaderRejection, + TypedHeader, }; #[derive(FromRequest)] diff --git a/axum-macros/tests/from_request/pass/named_via_parts.rs b/axum-macros/tests/from_request/pass/named_via_parts.rs index 0377af7b10..38fe0964c0 100644 --- a/axum-macros/tests/from_request/pass/named_via_parts.rs +++ b/axum-macros/tests/from_request/pass/named_via_parts.rs @@ -1,11 +1,11 @@ use axum::{ - response::Response, extract::{Extension, FromRequestParts}, + response::Response, }; use axum_extra::{ - TypedHeader, - typed_header::TypedHeaderRejection, headers::{self, UserAgent}, + typed_header::TypedHeaderRejection, + TypedHeader, }; #[derive(FromRequestParts)] diff --git a/axum-macros/tests/from_request/pass/override_rejection.rs b/axum-macros/tests/from_request/pass/override_rejection.rs index 736006edad..ee9a4540d4 100644 --- a/axum-macros/tests/from_request/pass/override_rejection.rs +++ b/axum-macros/tests/from_request/pass/override_rejection.rs @@ -1,5 +1,4 @@ use axum::{ - body::Body, extract::{rejection::ExtensionRejection, FromRequest, Request}, http::StatusCode, response::{IntoResponse, Response}, diff --git a/axum-macros/tests/from_request/pass/override_rejection_non_generic.rs b/axum-macros/tests/from_request/pass/override_rejection_non_generic.rs index 6c4d87fe01..b88b483e3b 100644 --- a/axum-macros/tests/from_request/pass/override_rejection_non_generic.rs +++ b/axum-macros/tests/from_request/pass/override_rejection_non_generic.rs @@ -5,8 +5,8 @@ use axum::{ Router, }; use axum_macros::FromRequest; -use std::collections::HashMap; use serde::Deserialize; +use std::collections::HashMap; fn main() { let _: Router = Router::new().route("/", get(handler).post(handler_result)); @@ -17,10 +17,7 @@ async fn handler(_: MyJson) {} async fn handler_result(_: Result) {} #[derive(FromRequest, Deserialize)] -#[from_request( - via(axum::extract::Json), - rejection(MyJsonRejection), -)] +#[from_request(via(axum::extract::Json), rejection(MyJsonRejection))] #[serde(transparent)] struct MyJson(HashMap); diff --git a/axum-macros/tests/from_request/pass/override_rejection_non_generic_parts.rs b/axum-macros/tests/from_request/pass/override_rejection_non_generic_parts.rs index 9aca7345dd..bcf213d1e2 100644 --- a/axum-macros/tests/from_request/pass/override_rejection_non_generic_parts.rs +++ b/axum-macros/tests/from_request/pass/override_rejection_non_generic_parts.rs @@ -5,8 +5,8 @@ use axum::{ Router, }; use axum_macros::FromRequestParts; -use std::collections::HashMap; use serde::Deserialize; +use std::collections::HashMap; fn main() { let _: Router = Router::new().route("/", get(handler).post(handler_result)); @@ -17,10 +17,7 @@ async fn handler(_: MyQuery) {} async fn handler_result(_: Result) {} #[derive(FromRequestParts, Deserialize)] -#[from_request( - via(axum::extract::Query), - rejection(MyQueryRejection), -)] +#[from_request(via(axum::extract::Query), rejection(MyQueryRejection))] #[serde(transparent)] struct MyQuery(HashMap); diff --git a/axum-macros/tests/from_request/pass/override_rejection_with_via_on_struct.rs b/axum-macros/tests/from_request/pass/override_rejection_with_via_on_struct.rs index 2a046fae50..8dab4165e4 100644 --- a/axum-macros/tests/from_request/pass/override_rejection_with_via_on_struct.rs +++ b/axum-macros/tests/from_request/pass/override_rejection_with_via_on_struct.rs @@ -19,10 +19,7 @@ async fn handler(_: MyJson) {} async fn handler_result(_: Result, MyJsonRejection>) {} #[derive(FromRequest)] -#[from_request( - via(axum::Json), - rejection(MyJsonRejection), -)] +#[from_request(via(axum::Json), rejection(MyJsonRejection))] struct MyJson(T); struct MyJsonRejection {} diff --git a/axum-macros/tests/from_request/pass/override_rejection_with_via_on_struct_parts.rs b/axum-macros/tests/from_request/pass/override_rejection_with_via_on_struct_parts.rs index eaeeeacf23..19e7307678 100644 --- a/axum-macros/tests/from_request/pass/override_rejection_with_via_on_struct_parts.rs +++ b/axum-macros/tests/from_request/pass/override_rejection_with_via_on_struct_parts.rs @@ -19,10 +19,7 @@ async fn handler(_: MyQuery) {} async fn handler_result(_: Result, MyQueryRejection>) {} #[derive(FromRequestParts)] -#[from_request( - via(axum::extract::Query), - rejection(MyQueryRejection), -)] +#[from_request(via(axum::extract::Query), rejection(MyQueryRejection))] struct MyQuery(T); struct MyQueryRejection {} diff --git a/axum-macros/tests/from_request/pass/state_cookie.rs b/axum-macros/tests/from_request/pass/state_cookie.rs index 6e2aa1f4ed..ce935d67fa 100644 --- a/axum-macros/tests/from_request/pass/state_cookie.rs +++ b/axum-macros/tests/from_request/pass/state_cookie.rs @@ -1,6 +1,6 @@ -use axum_macros::FromRequest; use axum::extract::FromRef; -use axum_extra::extract::cookie::{PrivateCookieJar, Key}; +use axum_extra::extract::cookie::{Key, PrivateCookieJar}; +use axum_macros::FromRequest; #[derive(FromRequest)] #[from_request(state(AppState))] diff --git a/axum-macros/tests/from_request/pass/state_explicit.rs b/axum-macros/tests/from_request/pass/state_explicit.rs index aed9dad6d5..df32a72568 100644 --- a/axum-macros/tests/from_request/pass/state_explicit.rs +++ b/axum-macros/tests/from_request/pass/state_explicit.rs @@ -1,9 +1,9 @@ -use axum_macros::FromRequest; use axum::{ extract::{FromRef, State}, - Router, routing::get, + Router, }; +use axum_macros::FromRequest; fn main() { let _: axum::Router = Router::new() diff --git a/axum-macros/tests/from_request/pass/state_explicit_parts.rs b/axum-macros/tests/from_request/pass/state_explicit_parts.rs index 94f37cf6b8..2822699379 100644 --- a/axum-macros/tests/from_request/pass/state_explicit_parts.rs +++ b/axum-macros/tests/from_request/pass/state_explicit_parts.rs @@ -1,9 +1,9 @@ -use axum_macros::FromRequestParts; use axum::{ - extract::{FromRef, State, Query}, - Router, + extract::{FromRef, Query, State}, routing::get, + Router, }; +use axum_macros::FromRequestParts; use std::collections::HashMap; fn main() { diff --git a/axum-macros/tests/from_request/pass/state_field_explicit.rs b/axum-macros/tests/from_request/pass/state_field_explicit.rs index b6d003dc00..90a903261e 100644 --- a/axum-macros/tests/from_request/pass/state_field_explicit.rs +++ b/axum-macros/tests/from_request/pass/state_field_explicit.rs @@ -1,5 +1,5 @@ use axum::{ - extract::{State, FromRef}, + extract::{FromRef, State}, routing::get, Router, }; diff --git a/axum-macros/tests/from_request/pass/state_field_infer.rs b/axum-macros/tests/from_request/pass/state_field_infer.rs index a24861a162..5e399c1bf0 100644 --- a/axum-macros/tests/from_request/pass/state_field_infer.rs +++ b/axum-macros/tests/from_request/pass/state_field_infer.rs @@ -1,8 +1,4 @@ -use axum::{ - extract::State, - routing::get, - Router, -}; +use axum::{extract::State, routing::get, Router}; use axum_macros::FromRequest; fn main() { diff --git a/axum-macros/tests/from_request/pass/state_infer.rs b/axum-macros/tests/from_request/pass/state_infer.rs index 07545ab074..39966b7c9b 100644 --- a/axum-macros/tests/from_request/pass/state_infer.rs +++ b/axum-macros/tests/from_request/pass/state_infer.rs @@ -1,5 +1,5 @@ -use axum_macros::FromRequest; use axum::extract::State; +use axum_macros::FromRequest; #[derive(FromRequest)] struct Extractor { diff --git a/axum-macros/tests/from_request/pass/state_infer_multiple.rs b/axum-macros/tests/from_request/pass/state_infer_multiple.rs index cb8de1d59c..d727a7e337 100644 --- a/axum-macros/tests/from_request/pass/state_infer_multiple.rs +++ b/axum-macros/tests/from_request/pass/state_infer_multiple.rs @@ -1,5 +1,5 @@ -use axum_macros::FromRequest; use axum::extract::State; +use axum_macros::FromRequest; #[derive(FromRequest)] struct Extractor { diff --git a/axum-macros/tests/from_request/pass/state_infer_parts.rs b/axum-macros/tests/from_request/pass/state_infer_parts.rs index f3f078c5f3..35ffcc31f0 100644 --- a/axum-macros/tests/from_request/pass/state_infer_parts.rs +++ b/axum-macros/tests/from_request/pass/state_infer_parts.rs @@ -1,5 +1,5 @@ -use axum_macros::FromRequestParts; use axum::extract::State; +use axum_macros::FromRequestParts; #[derive(FromRequestParts)] struct Extractor { diff --git a/axum-macros/tests/from_request/pass/state_via_infer.rs b/axum-macros/tests/from_request/pass/state_via_infer.rs index 40c52d8d4d..685ff1c7fe 100644 --- a/axum-macros/tests/from_request/pass/state_via_infer.rs +++ b/axum-macros/tests/from_request/pass/state_via_infer.rs @@ -1,8 +1,4 @@ -use axum::{ - extract::State, - routing::get, - Router, -}; +use axum::{extract::State, routing::get, Router}; use axum_macros::FromRequest; fn main() { diff --git a/axum-macros/tests/from_request/pass/state_with_rejection.rs b/axum-macros/tests/from_request/pass/state_with_rejection.rs index 9921add02b..8e730c961e 100644 --- a/axum-macros/tests/from_request/pass/state_with_rejection.rs +++ b/axum-macros/tests/from_request/pass/state_with_rejection.rs @@ -1,4 +1,3 @@ -use std::convert::Infallible; use axum::{ extract::State, response::{IntoResponse, Response}, @@ -6,6 +5,7 @@ use axum::{ Router, }; use axum_macros::FromRequest; +use std::convert::Infallible; fn main() { let _: axum::Router = Router::new() diff --git a/axum-macros/tests/from_request/pass/tuple_same_type_twice.rs b/axum-macros/tests/from_request/pass/tuple_same_type_twice.rs index 343563ddb6..56c8fd0f12 100644 --- a/axum-macros/tests/from_request/pass/tuple_same_type_twice.rs +++ b/axum-macros/tests/from_request/pass/tuple_same_type_twice.rs @@ -3,10 +3,7 @@ use axum_macros::FromRequest; use serde::Deserialize; #[derive(FromRequest)] -struct Extractor( - Query, - axum::extract::Json, -); +struct Extractor(Query, axum::extract::Json); #[derive(Deserialize)] struct Payload {} diff --git a/axum-macros/tests/from_request/pass/tuple_same_type_twice_parts.rs b/axum-macros/tests/from_request/pass/tuple_same_type_twice_parts.rs index 44c42dc5ab..a781baf605 100644 --- a/axum-macros/tests/from_request/pass/tuple_same_type_twice_parts.rs +++ b/axum-macros/tests/from_request/pass/tuple_same_type_twice_parts.rs @@ -3,10 +3,7 @@ use axum_macros::FromRequestParts; use serde::Deserialize; #[derive(FromRequestParts)] -struct Extractor( - Query, - axum::extract::Path, -); +struct Extractor(Query, axum::extract::Path); #[derive(Deserialize)] struct Payload {} diff --git a/axum-macros/tests/typed_path/fail/missing_field.rs b/axum-macros/tests/typed_path/fail/missing_field.rs index 6991ed1643..7f1f3be06d 100644 --- a/axum-macros/tests/typed_path/fail/missing_field.rs +++ b/axum-macros/tests/typed_path/fail/missing_field.rs @@ -5,5 +5,4 @@ use serde::Deserialize; #[typed_path("/users/{id}")] struct MyPath {} -fn main() { -} +fn main() {} diff --git a/axum-macros/tests/typed_path/pass/into_uri.rs b/axum-macros/tests/typed_path/pass/into_uri.rs index 2269b53133..20b01c1d57 100644 --- a/axum-macros/tests/typed_path/pass/into_uri.rs +++ b/axum-macros/tests/typed_path/pass/into_uri.rs @@ -1,5 +1,5 @@ -use axum_extra::routing::TypedPath; use axum::http::Uri; +use axum_extra::routing::TypedPath; use serde::Deserialize; #[derive(TypedPath, Deserialize)] diff --git a/axum-macros/tests/typed_path/pass/option_result.rs b/axum-macros/tests/typed_path/pass/option_result.rs index 36ea33707e..81cfb29482 100644 --- a/axum-macros/tests/typed_path/pass/option_result.rs +++ b/axum-macros/tests/typed_path/pass/option_result.rs @@ -1,5 +1,5 @@ -use axum_extra::routing::{TypedPath, RouterExt}; use axum::{extract::rejection::PathRejection, http::StatusCode}; +use axum_extra::routing::{RouterExt, TypedPath}; use serde::Deserialize; #[derive(TypedPath, Deserialize)] diff --git a/axum-macros/tests/typed_path/pass/unit_struct.rs b/axum-macros/tests/typed_path/pass/unit_struct.rs index f3bb164075..832e1001d3 100644 --- a/axum-macros/tests/typed_path/pass/unit_struct.rs +++ b/axum-macros/tests/typed_path/pass/unit_struct.rs @@ -5,8 +5,7 @@ use axum_extra::routing::TypedPath; struct MyPath; fn main() { - _ = axum::Router::<()>::new() - .route("/", axum::routing::get(|_: MyPath| async {})); + _ = axum::Router::<()>::new().route("/", axum::routing::get(|_: MyPath| async {})); assert_eq!(MyPath::PATH, "/users"); assert_eq!(format!("{}", MyPath), "/users"); diff --git a/axum-macros/tests/typed_path/pass/url_encoding.rs b/axum-macros/tests/typed_path/pass/url_encoding.rs index 5ac412e447..5e773d698e 100644 --- a/axum-macros/tests/typed_path/pass/url_encoding.rs +++ b/axum-macros/tests/typed_path/pass/url_encoding.rs @@ -22,11 +22,5 @@ fn main() { "/a%20b" ); - assert_eq!( - format!( - "{}", - Unnamed("a b".to_string()), - ), - "/a%20b" - ); + assert_eq!(format!("{}", Unnamed("a b".to_string()),), "/a%20b"); } diff --git a/axum/CHANGELOG.md b/axum/CHANGELOG.md index ce98bca697..51654fa420 100644 --- a/axum/CHANGELOG.md +++ b/axum/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **added:** Extend `FailedToDeserializePathParams::kind` enum with (`ErrorKind::DeserializeError`) This new variant captures both `key`, `value`, and `message` from named path parameters parse errors, instead of only deserialization error message in `ErrorKind::Message`. ([#2720]) +- **breaking:** Make `serve` generic over the listener and IO types ([#2941]) [#2897]: https://github.com/tokio-rs/axum/pull/2897 [#2903]: https://github.com/tokio-rs/axum/pull/2903 @@ -34,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#2992]: https://github.com/tokio-rs/axum/pull/2992 [#2720]: https://github.com/tokio-rs/axum/pull/2720 [#3039]: https://github.com/tokio-rs/axum/pull/3039 +[#2941]: https://github.com/tokio-rs/axum/pull/2941 # 0.8.0 diff --git a/axum/Cargo.toml b/axum/Cargo.toml index ef113c335b..5ede7517c1 100644 --- a/axum/Cargo.toml +++ b/axum/Cargo.toml @@ -131,9 +131,6 @@ tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["json"] } uuid = { version = "1.0", features = ["serde", "v4"] } -[package.metadata.docs.rs] -all-features = true - [dev-dependencies.tower] package = "tower" version = "0.5.1" @@ -180,6 +177,12 @@ features = [ "validate-request", ] +[lints] +workspace = true + +[package.metadata.docs.rs] +all-features = true + [package.metadata.playground] features = [ "http1", diff --git a/axum/benches/benches.rs b/axum/benches/benches.rs index bb1c303dd1..445f5de03e 100644 --- a/axum/benches/benches.rs +++ b/axum/benches/benches.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs)] + use axum::{ extract::State, routing::{get, post}, diff --git a/axum/src/box_clone_service.rs b/axum/src/box_clone_service.rs index 25c0b205b8..298bf2894a 100644 --- a/axum/src/box_clone_service.rs +++ b/axum/src/box_clone_service.rs @@ -74,7 +74,7 @@ where } impl fmt::Debug for BoxCloneService { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("BoxCloneService").finish() } } diff --git a/axum/src/docs/routing/into_make_service_with_connect_info.md b/axum/src/docs/routing/into_make_service_with_connect_info.md index 26d0602f31..088f21f9d4 100644 --- a/axum/src/docs/routing/into_make_service_with_connect_info.md +++ b/axum/src/docs/routing/into_make_service_with_connect_info.md @@ -35,6 +35,7 @@ use axum::{ serve::IncomingStream, Router, }; +use tokio::net::TcpListener; let app = Router::new().route("/", get(handler)); @@ -49,8 +50,8 @@ struct MyConnectInfo { // ... } -impl Connected> for MyConnectInfo { - fn connect_info(target: IncomingStream<'_>) -> Self { +impl Connected> for MyConnectInfo { + fn connect_info(target: IncomingStream<'_, TcpListener>) -> Self { MyConnectInfo { // ... } diff --git a/axum/src/extract/connect_info.rs b/axum/src/extract/connect_info.rs index 3d8f9a0163..2c7866f9f6 100644 --- a/axum/src/extract/connect_info.rs +++ b/axum/src/extract/connect_info.rs @@ -79,16 +79,28 @@ where /// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info pub trait Connected: Clone + Send + Sync + 'static { /// Create type holding information about the connection. - fn connect_info(target: T) -> Self; + fn connect_info(stream: T) -> Self; } #[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))] const _: () = { - use crate::serve::IncomingStream; + use crate::serve; + use tokio::net::TcpListener; + + impl Connected> for SocketAddr { + fn connect_info(stream: serve::IncomingStream<'_, TcpListener>) -> Self { + *stream.remote_addr() + } + } - impl Connected> for SocketAddr { - fn connect_info(target: IncomingStream<'_>) -> Self { - target.remote_addr() + impl<'a, L, F> Connected>> for L::Addr + where + L: serve::Listener, + L::Addr: Clone + Sync + 'static, + F: FnMut(&mut L::Io) + Send + 'static, + { + fn connect_info(stream: serve::IncomingStream<'a, serve::TapIo>) -> Self { + stream.remote_addr().clone() } } }; @@ -261,8 +273,8 @@ mod tests { value: &'static str, } - impl Connected> for MyConnectInfo { - fn connect_info(_target: IncomingStream<'_>) -> Self { + impl Connected> for MyConnectInfo { + fn connect_info(_target: IncomingStream<'_, TcpListener>) -> Self { Self { value: "it worked!", } diff --git a/axum/src/extract/matched_path.rs b/axum/src/extract/matched_path.rs index 8fdd8e35a9..124154f7ef 100644 --- a/axum/src/extract/matched_path.rs +++ b/axum/src/extract/matched_path.rs @@ -324,14 +324,14 @@ mod tests { "/{*path}", any(|req: Request| { Router::new() - .nest("/", Router::new().route("/foo", get(|| async {}))) + .nest("/foo", Router::new().route("/bar", get(|| async {}))) .oneshot(req) }), ); let client = TestClient::new(app); - let res = client.get("/foo").await; + let res = client.get("/foo/bar").await; assert_eq!(res.status(), StatusCode::OK); } diff --git a/axum/src/extract/multipart.rs b/axum/src/extract/multipart.rs index 57d32f7563..9f278f6d25 100644 --- a/axum/src/extract/multipart.rs +++ b/axum/src/extract/multipart.rs @@ -117,7 +117,7 @@ impl Stream for Field<'_> { } } -impl<'a> Field<'a> { +impl Field<'_> { /// The field name found in the /// [`Content-Disposition`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) /// header. diff --git a/axum/src/extract/nested_path.rs b/axum/src/extract/nested_path.rs index 61966a076a..77d1316781 100644 --- a/axum/src/extract/nested_path.rs +++ b/axum/src/extract/nested_path.rs @@ -191,42 +191,6 @@ mod tests { assert_eq!(res.status(), StatusCode::OK); } - #[crate::test] - async fn nested_at_root() { - let api = Router::new().route( - "/users", - get(|nested_path: NestedPath| { - assert_eq!(nested_path.as_str(), "/"); - async {} - }), - ); - - let app = Router::new().nest("/", api); - - let client = TestClient::new(app); - - let res = client.get("/users").await; - assert_eq!(res.status(), StatusCode::OK); - } - - #[crate::test] - async fn deeply_nested_from_root() { - let api = Router::new().route( - "/users", - get(|nested_path: NestedPath| { - assert_eq!(nested_path.as_str(), "/api"); - async {} - }), - ); - - let app = Router::new().nest("/", Router::new().nest("/api", api)); - - let client = TestClient::new(app); - - let res = client.get("/api/users").await; - assert_eq!(res.status(), StatusCode::OK); - } - #[crate::test] async fn in_fallbacks() { let api = Router::new().fallback(get(|nested_path: NestedPath| { diff --git a/axum/src/handler/service.rs b/axum/src/handler/service.rs index e6b8df9316..2090051978 100644 --- a/axum/src/handler/service.rs +++ b/axum/src/handler/service.rs @@ -180,12 +180,13 @@ where // for `axum::serve(listener, handler)` #[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))] const _: () = { - use crate::serve::IncomingStream; + use crate::serve; - impl Service> for HandlerService + impl Service> for HandlerService where H: Clone, S: Clone, + L: serve::Listener, { type Response = Self; type Error = Infallible; @@ -195,7 +196,7 @@ const _: () = { Poll::Ready(Ok(())) } - fn call(&mut self, _req: IncomingStream<'_>) -> Self::Future { + fn call(&mut self, _req: serve::IncomingStream<'_, L>) -> Self::Future { std::future::ready(Ok(self.clone())) } } diff --git a/axum/src/json.rs b/axum/src/json.rs index d11419a2c7..7082ac8bc3 100644 --- a/axum/src/json.rs +++ b/axum/src/json.rs @@ -132,7 +132,7 @@ fn json_content_type(headers: &HeaderMap) -> bool { }; let is_json_content_type = mime.type_() == "application" - && (mime.subtype() == "json" || mime.suffix().map_or(false, |name| name == "json")); + && (mime.subtype() == "json" || mime.suffix().is_some_and(|name| name == "json")); is_json_content_type } diff --git a/axum/src/lib.rs b/axum/src/lib.rs index fcc929a6ab..2eab2af58c 100644 --- a/axum/src/lib.rs +++ b/axum/src/lib.rs @@ -420,42 +420,6 @@ //! [`axum-core`]: http://crates.io/crates/axum-core //! [`State`]: crate::extract::State -#![warn( - clippy::all, - clippy::todo, - clippy::empty_enum, - clippy::enum_glob_use, - clippy::mem_forget, - clippy::unused_self, - clippy::filter_map_next, - clippy::needless_continue, - clippy::needless_borrow, - clippy::match_wildcard_for_single_variants, - clippy::if_let_mutex, - clippy::await_holding_lock, - clippy::match_on_vec_items, - clippy::imprecise_flops, - clippy::suboptimal_flops, - clippy::lossy_float_literal, - clippy::rest_pat_in_fully_bound_structs, - clippy::fn_params_excessive_bools, - clippy::exit, - clippy::inefficient_to_string, - clippy::linkedlist, - clippy::macro_use_imports, - clippy::option_option, - clippy::verbose_file_reads, - clippy::unnested_or_patterns, - clippy::str_to_string, - rust_2018_idioms, - future_incompatible, - nonstandard_style, - missing_debug_implementations, - missing_docs -)] -#![deny(unreachable_pub)] -#![allow(elided_lifetimes_in_paths, clippy::type_complexity)] -#![forbid(unsafe_code)] #![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))] #![cfg_attr(test, allow(clippy::float_cmp))] #![cfg_attr(not(test), warn(clippy::print_stdout, clippy::dbg_macro))] diff --git a/axum/src/routing/method_routing.rs b/axum/src/routing/method_routing.rs index cfc47e1f7f..2c03e7d5d0 100644 --- a/axum/src/routing/method_routing.rs +++ b/axum/src/routing/method_routing.rs @@ -1313,9 +1313,12 @@ where // for `axum::serve(listener, router)` #[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))] const _: () = { - use crate::serve::IncomingStream; + use crate::serve; - impl Service> for MethodRouter<()> { + impl Service> for MethodRouter<()> + where + L: serve::Listener, + { type Response = Self; type Error = Infallible; type Future = std::future::Ready>; @@ -1324,7 +1327,7 @@ const _: () = { Poll::Ready(Ok(())) } - fn call(&mut self, _req: IncomingStream<'_>) -> Self::Future { + fn call(&mut self, _req: serve::IncomingStream<'_, L>) -> Self::Future { std::future::ready(Ok(self.clone().with_state(()))) } } diff --git a/axum/src/routing/mod.rs b/axum/src/routing/mod.rs index d3ddf1fe6e..6404ce7db3 100644 --- a/axum/src/routing/mod.rs +++ b/axum/src/routing/mod.rs @@ -200,6 +200,10 @@ where #[doc(alias = "scope")] // Some web frameworks like actix-web use this term #[track_caller] pub fn nest(self, path: &str, router: Router) -> Self { + if path.is_empty() || path == "/" { + panic!("Nesting at the root is no longer supported. Use merge instead."); + } + let RouterInner { path_router, fallback_router, @@ -227,6 +231,10 @@ where T::Response: IntoResponse, T::Future: Send + 'static, { + if path.is_empty() || path == "/" { + panic!("Nesting at the root is no longer supported. Use fallback_service instead."); + } + tap_inner!(self, mut this => { panic_on_err!(this.path_router.nest_service(path, service)); }) @@ -510,9 +518,12 @@ impl Router { // for `axum::serve(listener, router)` #[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))] const _: () = { - use crate::serve::IncomingStream; + use crate::serve; - impl Service> for Router<()> { + impl Service> for Router<()> + where + L: serve::Listener, + { type Response = Self; type Error = Infallible; type Future = std::future::Ready>; @@ -521,7 +532,7 @@ const _: () = { Poll::Ready(Ok(())) } - fn call(&mut self, _req: IncomingStream<'_>) -> Self::Future { + fn call(&mut self, _req: serve::IncomingStream<'_, L>) -> Self::Future { // call `Router::with_state` such that everything is turned into `Route` eagerly // rather than doing that per request std::future::ready(Ok(self.clone().with_state(()))) diff --git a/axum/src/routing/path_router.rs b/axum/src/routing/path_router.rs index 293205debd..68ab4d9e5d 100644 --- a/axum/src/routing/path_router.rs +++ b/axum/src/routing/path_router.rs @@ -515,10 +515,8 @@ impl fmt::Debug for Node { #[track_caller] fn validate_nest_path(v7_checks: bool, path: &str) -> &str { - if path.is_empty() { - // nesting at `""` and `"/"` should mean the same thing - return "/"; - } + assert!(path.starts_with('/')); + assert!(path.len() > 1); if path.split('/').any(|segment| { segment.starts_with("{*") && segment.ends_with('}') && !segment.ends_with("}}") diff --git a/axum/src/routing/tests/fallback.rs b/axum/src/routing/tests/fallback.rs index 9dd1c6c2b1..3c8755bb85 100644 --- a/axum/src/routing/tests/fallback.rs +++ b/axum/src/routing/tests/fallback.rs @@ -200,13 +200,13 @@ async fn doesnt_panic_if_used_with_nested_router() { async fn handler() {} let routes_static = - Router::new().nest_service("/", crate::routing::get_service(handler.into_service())); + Router::new().nest_service("/foo", crate::routing::get_service(handler.into_service())); let routes_all = Router::new().fallback_service(routes_static); let client = TestClient::new(routes_all); - let res = client.get("/foobar").await; + let res = client.get("/foo/bar").await; assert_eq!(res.status(), StatusCode::OK); } diff --git a/axum/src/routing/tests/mod.rs b/axum/src/routing/tests/mod.rs index cb2666a950..8851738667 100644 --- a/axum/src/routing/tests/mod.rs +++ b/axum/src/routing/tests/mod.rs @@ -600,7 +600,7 @@ async fn head_with_middleware_applied() { let app = Router::new() .nest( - "/", + "/foo", Router::new().route("/", get(|| async { "Hello, World!" })), ) .layer(CompressionLayer::new().compress_when(SizeAbove::new(0))); @@ -608,13 +608,13 @@ async fn head_with_middleware_applied() { let client = TestClient::new(app); // send GET request - let res = client.get("/").header("accept-encoding", "gzip").await; + let res = client.get("/foo").header("accept-encoding", "gzip").await; assert_eq!(res.headers()["transfer-encoding"], "chunked"); // cannot have `transfer-encoding: chunked` and `content-length` assert!(!res.headers().contains_key("content-length")); // send HEAD request - let res = client.head("/").header("accept-encoding", "gzip").await; + let res = client.head("/foo").header("accept-encoding", "gzip").await; // no response body so no `transfer-encoding` assert!(!res.headers().contains_key("transfer-encoding")); // no content-length since we cannot know it since the response @@ -981,7 +981,7 @@ async fn logging_rejections() { .await; assert_eq!( - dbg!(events), + events, Vec::from([ TracingEvent { fields: RejectionEvent { diff --git a/axum/src/routing/tests/nest.rs b/axum/src/routing/tests/nest.rs index 7067f21fe8..6e14203662 100644 --- a/axum/src/routing/tests/nest.rs +++ b/axum/src/routing/tests/nest.rs @@ -60,74 +60,49 @@ async fn nesting_apps() { #[crate::test] async fn wrong_method_nest() { let nested_app = Router::new().route("/", get(|| async {})); - let app = Router::new().nest("/", nested_app); + let app = Router::new().nest("/foo", nested_app); let client = TestClient::new(app); - let res = client.get("/").await; + let res = client.get("/foo").await; assert_eq!(res.status(), StatusCode::OK); - let res = client.post("/").await; + let res = client.post("/foo").await; assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED); assert_eq!(res.headers()[ALLOW], "GET,HEAD"); - let res = client.patch("/foo").await; + let res = client.patch("/foo/bar").await; assert_eq!(res.status(), StatusCode::NOT_FOUND); } -#[crate::test] -async fn nesting_router_at_root() { - let nested = Router::new().route("/foo", get(|uri: Uri| async move { uri.to_string() })); - let app = Router::new().nest("/", nested); - - let client = TestClient::new(app); - - let res = client.get("/").await; - assert_eq!(res.status(), StatusCode::NOT_FOUND); - - let res = client.get("/foo").await; - assert_eq!(res.status(), StatusCode::OK); - assert_eq!(res.text().await, "/foo"); - - let res = client.get("/foo/bar").await; - assert_eq!(res.status(), StatusCode::NOT_FOUND); +#[test] +#[should_panic(expected = "Nesting at the root is no longer supported. Use merge instead.")] +fn nest_router_at_root() { + let nested = Router::new().route("/foo", get(|| async {})); + let _: Router = Router::new().nest("/", nested); } -#[crate::test] -async fn nesting_router_at_empty_path() { - let nested = Router::new().route("/foo", get(|uri: Uri| async move { uri.to_string() })); - let app = Router::new().nest("", nested); - - let client = TestClient::new(app); - - let res = client.get("/").await; - assert_eq!(res.status(), StatusCode::NOT_FOUND); - - let res = client.get("/foo").await; - assert_eq!(res.status(), StatusCode::OK); - assert_eq!(res.text().await, "/foo"); - - let res = client.get("/foo/bar").await; - assert_eq!(res.status(), StatusCode::NOT_FOUND); +#[test] +#[should_panic(expected = "Nesting at the root is no longer supported. Use merge instead.")] +fn nest_router_at_empty_path() { + let nested = Router::new().route("/foo", get(|| async {})); + let _: Router = Router::new().nest("", nested); } -#[crate::test] -async fn nesting_handler_at_root() { - let app = Router::new().nest_service("/", get(|uri: Uri| async move { uri.to_string() })); - - let client = TestClient::new(app); - - let res = client.get("/").await; - assert_eq!(res.status(), StatusCode::OK); - assert_eq!(res.text().await, "/"); - - let res = client.get("/foo").await; - assert_eq!(res.status(), StatusCode::OK); - assert_eq!(res.text().await, "/foo"); +#[test] +#[should_panic( + expected = "Nesting at the root is no longer supported. Use fallback_service instead." +)] +fn nest_service_at_root() { + let _: Router = Router::new().nest_service("/", get(|| async {})); +} - let res = client.get("/foo/bar").await; - assert_eq!(res.status(), StatusCode::OK); - assert_eq!(res.text().await, "/foo/bar"); +#[test] +#[should_panic( + expected = "Nesting at the root is no longer supported. Use fallback_service instead." +)] +fn nest_service_at_empty_path() { + let _: Router = Router::new().nest_service("", get(|| async {})); } #[crate::test] @@ -227,21 +202,6 @@ async fn nested_multiple_routes() { assert_eq!(client.get("/api/teams").await.text().await, "teams"); } -#[test] -#[should_panic = r#"Invalid route "/": Insertion failed due to conflict with previously registered route: /"#] -fn nested_service_at_root_with_other_routes() { - let _: Router = Router::new() - .nest_service("/", Router::new().route("/users", get(|| async {}))) - .route("/", get(|| async {})); -} - -#[test] -fn nested_at_root_with_other_routes() { - let _: Router = Router::new() - .nest("/", Router::new().route("/users", get(|| async {}))) - .route("/", get(|| async {})); -} - #[crate::test] async fn multiple_top_level_nests() { let app = Router::new() @@ -405,18 +365,12 @@ macro_rules! nested_route_test { } // test cases taken from https://github.com/tokio-rs/axum/issues/714#issuecomment-1058144460 -nested_route_test!(nest_1, nest = "", route = "/", expected = "/"); -nested_route_test!(nest_2, nest = "", route = "/a", expected = "/a"); -nested_route_test!(nest_3, nest = "", route = "/a/", expected = "/a/"); -nested_route_test!(nest_4, nest = "/", route = "/", expected = "/"); -nested_route_test!(nest_5, nest = "/", route = "/a", expected = "/a"); -nested_route_test!(nest_6, nest = "/", route = "/a/", expected = "/a/"); -nested_route_test!(nest_7, nest = "/a", route = "/", expected = "/a"); -nested_route_test!(nest_8, nest = "/a", route = "/a", expected = "/a/a"); -nested_route_test!(nest_9, nest = "/a", route = "/a/", expected = "/a/a/"); -nested_route_test!(nest_11, nest = "/a/", route = "/", expected = "/a/"); -nested_route_test!(nest_12, nest = "/a/", route = "/a", expected = "/a/a"); -nested_route_test!(nest_13, nest = "/a/", route = "/a/", expected = "/a/a/"); +nested_route_test!(nest_1, nest = "/a", route = "/", expected = "/a"); +nested_route_test!(nest_2, nest = "/a", route = "/a", expected = "/a/a"); +nested_route_test!(nest_3, nest = "/a", route = "/a/", expected = "/a/a/"); +nested_route_test!(nest_4, nest = "/a/", route = "/", expected = "/a/"); +nested_route_test!(nest_5, nest = "/a/", route = "/a", expected = "/a/a"); +nested_route_test!(nest_6, nest = "/a/", route = "/a/", expected = "/a/a/"); #[crate::test] #[should_panic( diff --git a/axum/src/routing/url_params.rs b/axum/src/routing/url_params.rs index eb5a08a330..1649a9e4cf 100644 --- a/axum/src/routing/url_params.rs +++ b/axum/src/routing/url_params.rs @@ -9,7 +9,7 @@ pub(crate) enum UrlParams { InvalidUtf8InPathParam { key: Arc }, } -pub(super) fn insert_url_params(extensions: &mut Extensions, params: Params) { +pub(super) fn insert_url_params(extensions: &mut Extensions, params: Params<'_, '_>) { let current_params = extensions.get_mut(); if let Some(UrlParams::InvalidUtf8InPathParam { .. }) = current_params { diff --git a/axum/src/serve.rs b/axum/src/serve.rs index 87b103ee90..a1358b1502 100644 --- a/axum/src/serve.rs +++ b/axum/src/serve.rs @@ -1,14 +1,13 @@ //! Serve services. use std::{ + any::TypeId, convert::Infallible, fmt::Debug, future::{poll_fn, Future, IntoFuture}, io, marker::PhantomData, - net::SocketAddr, sync::Arc, - time::Duration, }; use axum_core::{body::Body, extract::Request, response::Response}; @@ -17,13 +16,14 @@ use hyper::body::Incoming; use hyper_util::rt::{TokioExecutor, TokioIo}; #[cfg(any(feature = "http1", feature = "http2"))] use hyper_util::{server::conn::auto::Builder, service::TowerToHyperService}; -use tokio::{ - net::{TcpListener, TcpStream}, - sync::watch, -}; +use tokio::{net::TcpListener, sync::watch}; use tower::ServiceExt as _; use tower_service::Service; +mod listener; + +pub use self::listener::{Listener, ListenerExt, TapIo}; + /// Serve the service with the supplied listener. /// /// This method of running a service is intentionally simple and doesn't support any configuration. @@ -89,14 +89,15 @@ use tower_service::Service; /// [`HandlerWithoutStateExt::into_make_service_with_connect_info`]: crate::handler::HandlerWithoutStateExt::into_make_service_with_connect_info /// [`HandlerService::into_make_service_with_connect_info`]: crate::handler::HandlerService::into_make_service_with_connect_info #[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))] -pub fn serve(tcp_listener: TcpListener, make_service: M) -> Serve +pub fn serve(listener: L, make_service: M) -> Serve where - M: for<'a> Service, Error = Infallible, Response = S>, + L: Listener, + M: for<'a> Service, Error = Infallible, Response = S>, S: Service + Clone + Send + 'static, S::Future: Send, { Serve { - tcp_listener, + listener, make_service, tcp_nodelay: None, _marker: PhantomData, @@ -106,15 +107,18 @@ where /// Future returned by [`serve`]. #[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))] #[must_use = "futures must be awaited or polled"] -pub struct Serve { - tcp_listener: TcpListener, +pub struct Serve { + listener: L, make_service: M, tcp_nodelay: Option, _marker: PhantomData, } #[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))] -impl Serve { +impl Serve +where + L: Listener, +{ /// Prepares a server to handle graceful shutdown when the provided future completes. /// /// # Example @@ -136,76 +140,57 @@ impl Serve { /// // ... /// } /// ``` - pub fn with_graceful_shutdown(self, signal: F) -> WithGracefulShutdown + pub fn with_graceful_shutdown(self, signal: F) -> WithGracefulShutdown where F: Future + Send + 'static, { WithGracefulShutdown { - tcp_listener: self.tcp_listener, + listener: self.listener, make_service: self.make_service, signal, - tcp_nodelay: self.tcp_nodelay, _marker: PhantomData, } } - /// Instructs the server to set the value of the `TCP_NODELAY` option on every accepted connection. - /// - /// See also [`TcpStream::set_nodelay`]. - /// - /// # Example - /// ``` - /// use axum::{Router, routing::get}; - /// - /// # async { - /// let router = Router::new().route("/", get(|| async { "Hello, World!" })); - /// - /// let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); - /// axum::serve(listener, router) - /// .tcp_nodelay(true) - /// .await - /// .unwrap(); - /// # }; - /// ``` - pub fn tcp_nodelay(self, nodelay: bool) -> Self { - Self { - tcp_nodelay: Some(nodelay), - ..self - } - } - /// Returns the local address this server is bound to. - pub fn local_addr(&self) -> io::Result { - self.tcp_listener.local_addr() + pub fn local_addr(&self) -> io::Result { + self.listener.local_addr() } } #[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))] -impl Debug for Serve +impl Debug for Serve where + L: Debug + 'static, M: Debug, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let Self { - tcp_listener, + listener, make_service, tcp_nodelay, _marker: _, } = self; - f.debug_struct("Serve") - .field("tcp_listener", tcp_listener) - .field("make_service", make_service) - .field("tcp_nodelay", tcp_nodelay) - .finish() + let mut s = f.debug_struct("Serve"); + s.field("listener", listener) + .field("make_service", make_service); + + if TypeId::of::() == TypeId::of::() { + s.field("tcp_nodelay", tcp_nodelay); + } + + s.finish() } } #[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))] -impl IntoFuture for Serve +impl IntoFuture for Serve where - M: for<'a> Service, Error = Infallible, Response = S> + Send + 'static, - for<'a> >>::Future: Send, + L: Listener, + L::Addr: Debug, + M: for<'a> Service, Error = Infallible, Response = S> + Send + 'static, + for<'a> >>::Future: Send, S: Service + Clone + Send + 'static, S::Future: Send, { @@ -221,81 +206,55 @@ where /// Serve future with graceful shutdown enabled. #[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))] #[must_use = "futures must be awaited or polled"] -pub struct WithGracefulShutdown { - tcp_listener: TcpListener, +pub struct WithGracefulShutdown { + listener: L, make_service: M, signal: F, - tcp_nodelay: Option, _marker: PhantomData, } -impl WithGracefulShutdown { - /// Instructs the server to set the value of the `TCP_NODELAY` option on every accepted connection. - /// - /// See also [`TcpStream::set_nodelay`]. - /// - /// # Example - /// ``` - /// use axum::{Router, routing::get}; - /// - /// # async { - /// let router = Router::new().route("/", get(|| async { "Hello, World!" })); - /// - /// let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); - /// axum::serve(listener, router) - /// .with_graceful_shutdown(shutdown_signal()) - /// .tcp_nodelay(true) - /// .await - /// .unwrap(); - /// # }; - /// - /// async fn shutdown_signal() { - /// // ... - /// } - /// ``` - pub fn tcp_nodelay(self, nodelay: bool) -> Self { - Self { - tcp_nodelay: Some(nodelay), - ..self - } - } - +#[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))] +impl WithGracefulShutdown +where + L: Listener, +{ /// Returns the local address this server is bound to. - pub fn local_addr(&self) -> io::Result { - self.tcp_listener.local_addr() + pub fn local_addr(&self) -> io::Result { + self.listener.local_addr() } } #[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))] -impl Debug for WithGracefulShutdown +impl Debug for WithGracefulShutdown where + L: Debug + 'static, M: Debug, S: Debug, F: Debug, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let Self { - tcp_listener, + listener, make_service, signal, - tcp_nodelay, _marker: _, } = self; f.debug_struct("WithGracefulShutdown") - .field("tcp_listener", tcp_listener) + .field("listener", listener) .field("make_service", make_service) .field("signal", signal) - .field("tcp_nodelay", tcp_nodelay) .finish() } } #[cfg(all(feature = "tokio", any(feature = "http1", feature = "http2")))] -impl IntoFuture for WithGracefulShutdown +impl IntoFuture for WithGracefulShutdown where - M: for<'a> Service, Error = Infallible, Response = S> + Send + 'static, - for<'a> >>::Future: Send, + L: Listener, + L::Addr: Debug, + M: for<'a> Service, Error = Infallible, Response = S> + Send + 'static, + for<'a> >>::Future: Send, S: Service + Clone + Send + 'static, S::Future: Send, F: Future + Send + 'static, @@ -305,10 +264,9 @@ where fn into_future(self) -> Self::IntoFuture { let Self { - tcp_listener, + mut listener, mut make_service, signal, - tcp_nodelay, _marker: _, } = self; @@ -324,28 +282,17 @@ where let (close_tx, close_rx) = watch::channel(()); loop { - let (tcp_stream, remote_addr) = tokio::select! { - conn = tcp_accept(&tcp_listener) => { - match conn { - Some(conn) => conn, - None => continue, - } - } + let (io, remote_addr) = tokio::select! { + conn = listener.accept() => conn, _ = signal_tx.closed() => { trace!("signal received, not accepting new connections"); break; } }; - if let Some(nodelay) = tcp_nodelay { - if let Err(err) = tcp_stream.set_nodelay(nodelay) { - trace!("failed to set TCP_NODELAY on incoming connection: {err:#}"); - } - } - - let tcp_stream = TokioIo::new(tcp_stream); + let io = TokioIo::new(io); - trace!("connection {remote_addr} accepted"); + trace!("connection {remote_addr:?} accepted"); poll_fn(|cx| make_service.poll_ready(cx)) .await @@ -353,7 +300,7 @@ where let tower_service = make_service .call(IncomingStream { - tcp_stream: &tcp_stream, + io: &io, remote_addr, }) .await @@ -372,7 +319,7 @@ where // CONNECT protocol needed for HTTP/2 websockets #[cfg(feature = "http2")] builder.http2().enable_connect_protocol(); - let conn = builder.serve_connection_with_upgrades(tcp_stream, hyper_service); + let conn = builder.serve_connection_with_upgrades(io, hyper_service); pin_mut!(conn); let signal_closed = signal_tx.closed().fuse(); @@ -393,14 +340,12 @@ where } } - trace!("connection {remote_addr} closed"); - drop(close_rx); }); } drop(close_rx); - drop(tcp_listener); + drop(listener); trace!( "waiting for {} task(s) to finish", @@ -413,38 +358,32 @@ where } } -fn is_connection_error(e: &io::Error) -> bool { - matches!( - e.kind(), - io::ErrorKind::ConnectionRefused - | io::ErrorKind::ConnectionAborted - | io::ErrorKind::ConnectionReset - ) +/// An incoming stream. +/// +/// Used with [`serve`] and [`IntoMakeServiceWithConnectInfo`]. +/// +/// [`IntoMakeServiceWithConnectInfo`]: crate::extract::connect_info::IntoMakeServiceWithConnectInfo +#[derive(Debug)] +pub struct IncomingStream<'a, L> +where + L: Listener, +{ + io: &'a TokioIo, + remote_addr: L::Addr, } -async fn tcp_accept(listener: &TcpListener) -> Option<(TcpStream, SocketAddr)> { - match listener.accept().await { - Ok(conn) => Some(conn), - Err(e) => { - if is_connection_error(&e) { - return None; - } +impl IncomingStream<'_, L> +where + L: Listener, +{ + /// Get a reference to the inner IO type. + pub fn io(&self) -> &L::Io { + self.io.inner() + } - // [From `hyper::Server` in 0.14](https://github.com/hyperium/hyper/blob/v0.14.27/src/server/tcp.rs#L186) - // - // > A possible scenario is that the process has hit the max open files - // > allowed, and so trying to accept a new connection will fail with - // > `EMFILE`. In some cases, it's preferable to just wait for some time, if - // > the application will likely close some files (or connections), and try - // > to accept the connection again. If this option is `true`, the error - // > will be logged at the `error` level, since it is still a big deal, - // > and then the listener will sleep for 1 second. - // - // hyper allowed customizing this but axum does not. - error!("accept error: {e}"); - tokio::time::sleep(Duration::from_secs(1)).await; - None - } + /// Returns the remote address that this stream is bound to. + pub fn remote_addr(&self) -> &L::Addr { + &self.remote_addr } } @@ -474,68 +413,120 @@ mod private { } } -/// An incoming stream. -/// -/// Used with [`serve`] and [`IntoMakeServiceWithConnectInfo`]. -/// -/// [`IntoMakeServiceWithConnectInfo`]: crate::extract::connect_info::IntoMakeServiceWithConnectInfo -#[derive(Debug)] -pub struct IncomingStream<'a> { - tcp_stream: &'a TokioIo, - remote_addr: SocketAddr, -} - -impl IncomingStream<'_> { - /// Returns the local address that this stream is bound to. - pub fn local_addr(&self) -> std::io::Result { - self.tcp_stream.inner().local_addr() - } - - /// Returns the remote address that this stream is bound to. - pub fn remote_addr(&self) -> SocketAddr { - self.remote_addr - } -} - #[cfg(test)] mod tests { - use super::*; + use std::{ + future::{pending, IntoFuture as _}, + net::{IpAddr, Ipv4Addr}, + }; + + use axum_core::{body::Body, extract::Request}; + use http::StatusCode; + use hyper_util::rt::TokioIo; + use tokio::{ + io::{self, AsyncRead, AsyncWrite}, + net::{TcpListener, UnixListener}, + }; + + use super::{serve, IncomingStream, Listener}; use crate::{ + body::to_bytes, + extract::connect_info::Connected, handler::{Handler, HandlerWithoutStateExt}, routing::get, + serve::ListenerExt, Router, }; - use std::{ - future::pending, - net::{IpAddr, Ipv4Addr}, - }; #[allow(dead_code, unused_must_use)] async fn if_it_compiles_it_works() { + #[derive(Clone, Debug)] + struct UdsConnectInfo; + + impl Connected> for UdsConnectInfo { + fn connect_info(_stream: IncomingStream<'_, UnixListener>) -> Self { + Self + } + } + let router: Router = Router::new(); let addr = "0.0.0.0:0"; + let tcp_nodelay_listener = || async { + TcpListener::bind(addr).await.unwrap().tap_io(|tcp_stream| { + if let Err(err) = tcp_stream.set_nodelay(true) { + eprintln!("failed to set TCP_NODELAY on incoming connection: {err:#}"); + } + }) + }; + // router serve(TcpListener::bind(addr).await.unwrap(), router.clone()); + serve(tcp_nodelay_listener().await, router.clone()) + .await + .unwrap(); + serve(UnixListener::bind("").unwrap(), router.clone()); + serve( TcpListener::bind(addr).await.unwrap(), router.clone().into_make_service(), ); + serve( + tcp_nodelay_listener().await, + router.clone().into_make_service(), + ); + serve( + UnixListener::bind("").unwrap(), + router.clone().into_make_service(), + ); + serve( TcpListener::bind(addr).await.unwrap(), - router.into_make_service_with_connect_info::(), + router + .clone() + .into_make_service_with_connect_info::(), + ); + serve( + tcp_nodelay_listener().await, + router + .clone() + .into_make_service_with_connect_info::(), + ); + serve( + UnixListener::bind("").unwrap(), + router.into_make_service_with_connect_info::(), ); // method router serve(TcpListener::bind(addr).await.unwrap(), get(handler)); + serve(tcp_nodelay_listener().await, get(handler)); + serve(UnixListener::bind("").unwrap(), get(handler)); + serve( TcpListener::bind(addr).await.unwrap(), get(handler).into_make_service(), ); + serve( + tcp_nodelay_listener().await, + get(handler).into_make_service(), + ); + serve( + UnixListener::bind("").unwrap(), + get(handler).into_make_service(), + ); + serve( TcpListener::bind(addr).await.unwrap(), - get(handler).into_make_service_with_connect_info::(), + get(handler).into_make_service_with_connect_info::(), + ); + serve( + tcp_nodelay_listener().await, + get(handler).into_make_service_with_connect_info::(), + ); + serve( + UnixListener::bind("").unwrap(), + get(handler).into_make_service_with_connect_info::(), ); // handler @@ -543,32 +534,35 @@ mod tests { TcpListener::bind(addr).await.unwrap(), handler.into_service(), ); + serve(tcp_nodelay_listener().await, handler.into_service()); + serve(UnixListener::bind("").unwrap(), handler.into_service()); + serve( TcpListener::bind(addr).await.unwrap(), handler.with_state(()), ); + serve(tcp_nodelay_listener().await, handler.with_state(())); + serve(UnixListener::bind("").unwrap(), handler.with_state(())); + serve( TcpListener::bind(addr).await.unwrap(), handler.into_make_service(), ); + serve(tcp_nodelay_listener().await, handler.into_make_service()); + serve(UnixListener::bind("").unwrap(), handler.into_make_service()); + serve( TcpListener::bind(addr).await.unwrap(), - handler.into_make_service_with_connect_info::(), + handler.into_make_service_with_connect_info::(), ); - - // nodelay serve( - TcpListener::bind(addr).await.unwrap(), - handler.into_service(), - ) - .tcp_nodelay(true); - + tcp_nodelay_listener().await, + handler.into_make_service_with_connect_info::(), + ); serve( - TcpListener::bind(addr).await.unwrap(), - handler.into_service(), - ) - .with_graceful_shutdown(async { /*...*/ }) - .tcp_nodelay(true); + UnixListener::bind("").unwrap(), + handler.into_make_service_with_connect_info::(), + ); } async fn handler() {} @@ -613,4 +607,49 @@ mod tests { // Call Serve::into_future outside of a tokio context. This used to panic. _ = serve(listener, router).into_future(); } + + #[crate::test] + async fn serving_on_custom_io_type() { + struct ReadyListener(Option); + + impl Listener for ReadyListener + where + T: AsyncRead + AsyncWrite + Unpin + Send + 'static, + { + type Io = T; + type Addr = (); + + async fn accept(&mut self) -> (Self::Io, Self::Addr) { + match self.0.take() { + Some(server) => (server, ()), + None => std::future::pending().await, + } + } + + fn local_addr(&self) -> io::Result { + Ok(()) + } + } + + let (client, server) = io::duplex(1024); + let listener = ReadyListener(Some(server)); + + let app = Router::new().route("/", get(|| async { "Hello, World!" })); + + tokio::spawn(serve(listener, app).into_future()); + + let stream = TokioIo::new(client); + let (mut sender, conn) = hyper::client::conn::http1::handshake(stream).await.unwrap(); + tokio::spawn(conn); + + let request = Request::builder().body(Body::empty()).unwrap(); + + let response = sender.send_request(request).await.unwrap(); + assert_eq!(response.status(), StatusCode::OK); + + let body = Body::new(response.into_body()); + let body = to_bytes(body, usize::MAX).await.unwrap(); + let body = String::from_utf8(body.to_vec()).unwrap(); + assert_eq!(body, "Hello, World!"); + } } diff --git a/axum/src/serve/listener.rs b/axum/src/serve/listener.rs new file mode 100644 index 0000000000..91effae5bd --- /dev/null +++ b/axum/src/serve/listener.rs @@ -0,0 +1,167 @@ +use std::{fmt, future::Future, time::Duration}; + +use tokio::{ + io::{self, AsyncRead, AsyncWrite}, + net::{TcpListener, TcpStream}, +}; + +/// Types that can listen for connections. +pub trait Listener: Send + 'static { + /// The listener's IO type. + type Io: AsyncRead + AsyncWrite + Unpin + Send + 'static; + + /// The listener's address type. + type Addr: Send; + + /// Accept a new incoming connection to this listener. + /// + /// If the underlying accept call can return an error, this function must + /// take care of logging and retrying. + fn accept(&mut self) -> impl Future + Send; + + /// Returns the local address that this listener is bound to. + fn local_addr(&self) -> io::Result; +} + +impl Listener for TcpListener { + type Io = TcpStream; + type Addr = std::net::SocketAddr; + + async fn accept(&mut self) -> (Self::Io, Self::Addr) { + loop { + match Self::accept(self).await { + Ok(tup) => return tup, + Err(e) => handle_accept_error(e).await, + } + } + } + + #[inline] + fn local_addr(&self) -> io::Result { + Self::local_addr(self) + } +} + +#[cfg(unix)] +impl Listener for tokio::net::UnixListener { + type Io = tokio::net::UnixStream; + type Addr = tokio::net::unix::SocketAddr; + + async fn accept(&mut self) -> (Self::Io, Self::Addr) { + loop { + match Self::accept(self).await { + Ok(tup) => return tup, + Err(e) => handle_accept_error(e).await, + } + } + } + + #[inline] + fn local_addr(&self) -> io::Result { + Self::local_addr(self) + } +} + +/// Extensions to [`Listener`]. +pub trait ListenerExt: Listener + Sized { + /// Run a mutable closure on every accepted `Io`. + /// + /// # Example + /// + /// ``` + /// use axum::{Router, routing::get, serve::ListenerExt}; + /// use tracing::trace; + /// + /// # async { + /// let router = Router::new().route("/", get(|| async { "Hello, World!" })); + /// + /// let listener = tokio::net::TcpListener::bind("0.0.0.0:3000") + /// .await + /// .unwrap() + /// .tap_io(|tcp_stream| { + /// if let Err(err) = tcp_stream.set_nodelay(true) { + /// trace!("failed to set TCP_NODELAY on incoming connection: {err:#}"); + /// } + /// }); + /// axum::serve(listener, router).await.unwrap(); + /// # }; + /// ``` + fn tap_io(self, tap_fn: F) -> TapIo + where + F: FnMut(&mut Self::Io) + Send + 'static, + { + TapIo { + listener: self, + tap_fn, + } + } +} + +impl ListenerExt for L {} + +/// Return type of [`ListenerExt::tap_io`]. +/// +/// See that method for details. +pub struct TapIo { + listener: L, + tap_fn: F, +} + +impl fmt::Debug for TapIo +where + L: Listener + fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("TapIo") + .field("listener", &self.listener) + .finish_non_exhaustive() + } +} + +impl Listener for TapIo +where + L: Listener, + F: FnMut(&mut L::Io) + Send + 'static, +{ + type Io = L::Io; + type Addr = L::Addr; + + async fn accept(&mut self) -> (Self::Io, Self::Addr) { + let (mut io, addr) = self.listener.accept().await; + (self.tap_fn)(&mut io); + (io, addr) + } + + fn local_addr(&self) -> io::Result { + self.listener.local_addr() + } +} + +async fn handle_accept_error(e: io::Error) { + if is_connection_error(&e) { + return; + } + + // [From `hyper::Server` in 0.14](https://github.com/hyperium/hyper/blob/v0.14.27/src/server/tcp.rs#L186) + // + // > A possible scenario is that the process has hit the max open files + // > allowed, and so trying to accept a new connection will fail with + // > `EMFILE`. In some cases, it's preferable to just wait for some time, if + // > the application will likely close some files (or connections), and try + // > to accept the connection again. If this option is `true`, the error + // > will be logged at the `error` level, since it is still a big deal, + // > and then the listener will sleep for 1 second. + // + // hyper allowed customizing this but axum does not. + error!("accept error: {e}"); + tokio::time::sleep(Duration::from_secs(1)).await; +} + +fn is_connection_error(e: &io::Error) -> bool { + matches!( + e.kind(), + io::ErrorKind::ConnectionRefused + | io::ErrorKind::ConnectionAborted + | io::ErrorKind::ConnectionReset + ) +} diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 6f115daf3c..cef395b715 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -82,14 +82,9 @@ pub struct Pagination { pub limit: Option, } -async fn todos_index( - pagination: Option>, - State(db): State, -) -> impl IntoResponse { +async fn todos_index(pagination: Query, State(db): State) -> impl IntoResponse { let todos = db.read().unwrap(); - let Query(pagination) = pagination.unwrap_or_default(); - let todos = todos .values() .skip(pagination.offset.unwrap_or(0)) diff --git a/examples/unix-domain-socket/src/main.rs b/examples/unix-domain-socket/src/main.rs index 697f31a557..07b38d9191 100644 --- a/examples/unix-domain-socket/src/main.rs +++ b/examples/unix-domain-socket/src/main.rs @@ -21,17 +21,13 @@ mod unix { extract::connect_info::{self, ConnectInfo}, http::{Method, Request, StatusCode}, routing::get, + serve::IncomingStream, Router, }; use http_body_util::BodyExt; - use hyper::body::Incoming; - use hyper_util::{ - rt::{TokioExecutor, TokioIo}, - server, - }; - use std::{convert::Infallible, path::PathBuf, sync::Arc}; + use hyper_util::rt::TokioIo; + use std::{path::PathBuf, sync::Arc}; use tokio::net::{unix::UCred, UnixListener, UnixStream}; - use tower::Service; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; pub async fn server() { @@ -52,33 +48,11 @@ mod unix { let uds = UnixListener::bind(path.clone()).unwrap(); tokio::spawn(async move { - let app = Router::new().route("/", get(handler)); - - let mut make_service = app.into_make_service_with_connect_info::(); - - // See https://github.com/tokio-rs/axum/blob/main/examples/serve-with-hyper/src/main.rs for - // more details about this setup - loop { - let (socket, _remote_addr) = uds.accept().await.unwrap(); - - let tower_service = unwrap_infallible(make_service.call(&socket).await); - - tokio::spawn(async move { - let socket = TokioIo::new(socket); - - let hyper_service = - hyper::service::service_fn(move |request: Request| { - tower_service.clone().call(request) - }); + let app = Router::new() + .route("/", get(handler)) + .into_make_service_with_connect_info::(); - if let Err(err) = server::conn::auto::Builder::new(TokioExecutor::new()) - .serve_connection_with_upgrades(socket, hyper_service) - .await - { - eprintln!("failed to serve connection: {err:#}"); - } - }); - } + axum::serve(uds, app).await.unwrap(); }); let stream = TokioIo::new(UnixStream::connect(path).await.unwrap()); @@ -117,22 +91,14 @@ mod unix { peer_cred: UCred, } - impl connect_info::Connected<&UnixStream> for UdsConnectInfo { - fn connect_info(target: &UnixStream) -> Self { - let peer_addr = target.peer_addr().unwrap(); - let peer_cred = target.peer_cred().unwrap(); - + impl connect_info::Connected> for UdsConnectInfo { + fn connect_info(stream: IncomingStream<'_, UnixListener>) -> Self { + let peer_addr = stream.io().peer_addr().unwrap(); + let peer_cred = stream.io().peer_cred().unwrap(); Self { peer_addr: Arc::new(peer_addr), peer_cred, } } } - - fn unwrap_infallible(result: Result) -> T { - match result { - Ok(value) => value, - Err(err) => match err {}, - } - } }