diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index a8a4e7e7..dc6780fe 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -43,6 +43,9 @@ jobs: cargo-hack: runs-on: ubuntu-24.04 + env: + # Fail the build if there are any warnings + RUSTFLAGS: "-D warnings" steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable diff --git a/axum/src/extract/mod.rs b/axum/src/extract/mod.rs index 61b57418..f63a7506 100644 --- a/axum/src/extract/mod.rs +++ b/axum/src/extract/mod.rs @@ -11,6 +11,8 @@ pub mod rejection; pub mod ws; pub(crate) mod nested_path; +#[cfg(feature = "original-uri")] +mod original_uri; mod raw_form; mod raw_query; mod request_parts; @@ -72,7 +74,7 @@ pub use self::query::Query; #[cfg(feature = "original-uri")] #[doc(inline)] -pub use self::request_parts::OriginalUri; +pub use self::original_uri::OriginalUri; #[cfg(feature = "ws")] #[doc(inline)] diff --git a/axum/src/extract/original_uri.rs b/axum/src/extract/original_uri.rs new file mode 100644 index 00000000..35364281 --- /dev/null +++ b/axum/src/extract/original_uri.rs @@ -0,0 +1,85 @@ +use super::{Extension, FromRequestParts}; +use http::{request::Parts, Uri}; +use std::convert::Infallible; + +/// Extractor that gets the original request URI regardless of nesting. +/// +/// This is necessary since [`Uri`](http::Uri), when used as an extractor, will +/// have the prefix stripped if used in a nested service. +/// +/// # Example +/// +/// ``` +/// use axum::{ +/// routing::get, +/// Router, +/// extract::OriginalUri, +/// http::Uri +/// }; +/// +/// let api_routes = Router::new() +/// .route( +/// "/users", +/// get(|uri: Uri, OriginalUri(original_uri): OriginalUri| async { +/// // `uri` is `/users` +/// // `original_uri` is `/api/users` +/// }), +/// ); +/// +/// let app = Router::new().nest("/api", api_routes); +/// # let _: Router = app; +/// ``` +/// +/// # Extracting via request extensions +/// +/// `OriginalUri` can also be accessed from middleware via request extensions. +/// This is useful for example with [`Trace`](tower_http::trace::Trace) to +/// create a span that contains the full path, if your service might be nested: +/// +/// ``` +/// use axum::{ +/// Router, +/// extract::OriginalUri, +/// http::Request, +/// routing::get, +/// }; +/// use tower_http::trace::TraceLayer; +/// +/// let api_routes = Router::new() +/// .route("/users/{id}", get(|| async { /* ... */ })) +/// .layer( +/// TraceLayer::new_for_http().make_span_with(|req: &Request<_>| { +/// let path = if let Some(path) = req.extensions().get::() { +/// // This will include `/api` +/// path.0.path().to_owned() +/// } else { +/// // The `OriginalUri` extension will always be present if using +/// // `Router` unless another extractor or middleware has removed it +/// req.uri().path().to_owned() +/// }; +/// tracing::info_span!("http-request", %path) +/// }), +/// ); +/// +/// let app = Router::new().nest("/api", api_routes); +/// # let _: Router = app; +/// ``` +#[derive(Debug, Clone)] +pub struct OriginalUri(pub Uri); + +impl FromRequestParts for OriginalUri +where + S: Send + Sync, +{ + type Rejection = Infallible; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let uri = Extension::::from_request_parts(parts, state) + .await + .unwrap_or_else(|_| Extension(OriginalUri(parts.uri.clone()))) + .0; + Ok(uri) + } +} + +axum_core::__impl_deref!(OriginalUri: Uri); diff --git a/axum/src/extract/request_parts.rs b/axum/src/extract/request_parts.rs index 6d9adc67..0fb3247b 100644 --- a/axum/src/extract/request_parts.rs +++ b/axum/src/extract/request_parts.rs @@ -1,92 +1,6 @@ -use super::{Extension, FromRequestParts}; -use http::{request::Parts, Uri}; -use std::convert::Infallible; - -/// Extractor that gets the original request URI regardless of nesting. -/// -/// This is necessary since [`Uri`](http::Uri), when used as an extractor, will -/// have the prefix stripped if used in a nested service. -/// -/// # Example -/// -/// ``` -/// use axum::{ -/// routing::get, -/// Router, -/// extract::OriginalUri, -/// http::Uri -/// }; -/// -/// let api_routes = Router::new() -/// .route( -/// "/users", -/// get(|uri: Uri, OriginalUri(original_uri): OriginalUri| async { -/// // `uri` is `/users` -/// // `original_uri` is `/api/users` -/// }), -/// ); -/// -/// let app = Router::new().nest("/api", api_routes); -/// # let _: Router = app; -/// ``` -/// -/// # Extracting via request extensions -/// -/// `OriginalUri` can also be accessed from middleware via request extensions. -/// This is useful for example with [`Trace`](tower_http::trace::Trace) to -/// create a span that contains the full path, if your service might be nested: -/// -/// ``` -/// use axum::{ -/// Router, -/// extract::OriginalUri, -/// http::Request, -/// routing::get, -/// }; -/// use tower_http::trace::TraceLayer; -/// -/// let api_routes = Router::new() -/// .route("/users/{id}", get(|| async { /* ... */ })) -/// .layer( -/// TraceLayer::new_for_http().make_span_with(|req: &Request<_>| { -/// let path = if let Some(path) = req.extensions().get::() { -/// // This will include `/api` -/// path.0.path().to_owned() -/// } else { -/// // The `OriginalUri` extension will always be present if using -/// // `Router` unless another extractor or middleware has removed it -/// req.uri().path().to_owned() -/// }; -/// tracing::info_span!("http-request", %path) -/// }), -/// ); -/// -/// let app = Router::new().nest("/api", api_routes); -/// # let _: Router = app; -/// ``` -#[cfg(feature = "original-uri")] -#[derive(Debug, Clone)] -pub struct OriginalUri(pub Uri); - -#[cfg(feature = "original-uri")] -impl FromRequestParts for OriginalUri -where - S: Send + Sync, -{ - type Rejection = Infallible; - - async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { - let uri = Extension::::from_request_parts(parts, state) - .await - .unwrap_or_else(|_| Extension(OriginalUri(parts.uri.clone()))) - .0; - Ok(uri) - } -} - -#[cfg(feature = "original-uri")] -axum_core::__impl_deref!(OriginalUri: Uri); - +/// This module contains the tests for the `impl FromRequestParts for Parts` +/// implementation in the `axum-core` crate. The tests cannot be moved there +/// because we don't have access to the `TestClient` and `Router` types there. #[cfg(test)] mod tests { use crate::{extract::Extension, routing::get, test_helpers::*, Router}; diff --git a/axum/src/macros.rs b/axum/src/macros.rs index 5b8a335e..37b8fc3b 100644 --- a/axum/src/macros.rs +++ b/axum/src/macros.rs @@ -68,6 +68,7 @@ macro_rules! all_the_tuples { } #[cfg(feature = "tracing")] +#[allow(unused_macros)] macro_rules! trace { ($($tt:tt)*) => { tracing::trace!($($tt)*) @@ -75,6 +76,7 @@ macro_rules! trace { } #[cfg(feature = "tracing")] +#[allow(unused_macros)] macro_rules! error { ($($tt:tt)*) => { tracing::error!($($tt)*) @@ -82,11 +84,13 @@ macro_rules! error { } #[cfg(not(feature = "tracing"))] +#[allow(unused_macros)] macro_rules! trace { ($($tt:tt)*) => {}; } #[cfg(not(feature = "tracing"))] +#[allow(unused_macros)] macro_rules! error { ($($tt:tt)*) => {}; } diff --git a/axum/src/routing/mod.rs b/axum/src/routing/mod.rs index 6404ce7d..2e975d99 100644 --- a/axum/src/routing/mod.rs +++ b/axum/src/routing/mod.rs @@ -99,6 +99,7 @@ impl fmt::Debug for Router { } pub(crate) const NEST_TAIL_PARAM: &str = "__private__axum_nest_tail_param"; +#[cfg(feature = "matched-path")] 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}"; diff --git a/axum/src/routing/path_router.rs b/axum/src/routing/path_router.rs index 68ab4d9e..83f33e4e 100644 --- a/axum/src/routing/path_router.rs +++ b/axum/src/routing/path_router.rs @@ -369,7 +369,7 @@ where pub(super) fn call_with_state( &self, - mut req: Request, + #[cfg_attr(not(feature = "original-uri"), allow(unused_mut))] mut req: Request, state: S, ) -> Result, (Request, S)> { #[cfg(feature = "original-uri")]