diff --git a/Cargo.lock b/Cargo.lock index 8d985514..fc23d0d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -394,6 +394,7 @@ dependencies = [ "pin-project-lite", "prost", "reqwest 0.12.12", + "rustversion", "serde", "serde_html_form", "serde_json", diff --git a/axum-extra/CHANGELOG.md b/axum-extra/CHANGELOG.md index f69433c5..1939baba 100644 --- a/axum-extra/CHANGELOG.md +++ b/axum-extra/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning]. # Unreleased - **fixed:** Fix a broken link in the documentation of `ErasedJson` ([#3186]) +- **added:** Add `vpath!` for compile time path verification on static paths. ([#3288]) [#3186]: https://github.com/tokio-rs/axum/pull/3186 diff --git a/axum-extra/Cargo.toml b/axum-extra/Cargo.toml index 15ef717d..44f1ed06 100644 --- a/axum-extra/Cargo.toml +++ b/axum-extra/Cargo.toml @@ -58,6 +58,7 @@ http-body = "1.0.0" http-body-util = "0.1.0" mime = "0.3" pin-project-lite = "0.2" +rustversion = "1.0.9" serde = "1.0" tower = { version = "0.5.2", default-features = false, features = ["util"] } tower-layer = "0.3" diff --git a/axum-extra/src/routing/mod.rs b/axum-extra/src/routing/mod.rs index 5732f8a3..cf85dc53 100644 --- a/axum-extra/src/routing/mod.rs +++ b/axum-extra/src/routing/mod.rs @@ -25,6 +25,56 @@ pub use axum_macros::TypedPath; #[cfg(feature = "typed-routing")] pub use self::typed::{SecondElementIs, TypedPath}; +// Validates a path at compile time, used with the vpath macro. +#[rustversion::since(1.80)] +#[doc(hidden)] +pub const fn __private_validate_static_path(path: &'static str) -> &'static str { + if path.is_empty() { + panic!("Paths must start with a `/`. Use \"/\" for root routes") + } + if path.as_bytes()[0] != b'/' { + panic!("Paths must start with /"); + } + path +} + +/// This macro aborts compilation if the path is invalid. +/// +/// This example will fail to compile: +/// +/// ```compile_fail +/// use axum::routing::{Router, get}; +/// use axum_extra::vpath; +/// +/// let router = axum::Router::<()>::new() +/// .route(vpath!("invalid_path"), get(root)) +/// .to_owned(); +/// +/// async fn root() {} +/// ``` +/// +/// This one will compile without problems: +/// +/// ```no_run +/// use axum::routing::{Router, get}; +/// use axum_extra::vpath; +/// +/// let router = axum::Router::<()>::new() +/// .route(vpath!("/valid_path"), get(root)) +/// .to_owned(); +/// +/// async fn root() {} +/// ``` +/// +/// This macro is available only on rust versions 1.80 and above. +#[rustversion::since(1.80)] +#[macro_export] +macro_rules! vpath { + ($e:expr) => { + const { $crate::routing::__private_validate_static_path($e) } + }; +} + /// Extension trait that adds additional methods to [`Router`]. pub trait RouterExt: sealed::Sealed { /// Add a typed `GET` route to the router.