diff --git a/axum-extra/CHANGELOG.md b/axum-extra/CHANGELOG.md index c03a1c38..72f4a216 100644 --- a/axum-extra/CHANGELOG.md +++ b/axum-extra/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # Unreleased -- None. +- **added:** Add `TypedPath::to_uri` for converting the path into a `Uri` ([#790]) + +[#790]: https://github.com/tokio-rs/axum/pull/790 # 0.1.4 (22. February, 2022) diff --git a/axum-extra/src/routing/typed.rs b/axum-extra/src/routing/typed.rs index 5c855453..22c93c56 100644 --- a/axum-extra/src/routing/typed.rs +++ b/axum-extra/src/routing/typed.rs @@ -1,4 +1,5 @@ use super::sealed::Sealed; +use http::Uri; /// A type safe path. /// @@ -148,6 +149,21 @@ use super::sealed::Sealed; pub trait TypedPath: std::fmt::Display { /// The path with optional captures such as `/users/:id`. const PATH: &'static str; + + /// Convert the path into a `Uri`. + /// + /// # Panics + /// + /// The default implementation parses the required [`Display`] implemetation. If that fails it + /// will panic. + /// + /// Using `#[derive(TypedPath)]` will never result in a panic since it percent-encodes + /// arguments. + /// + /// [`Display`]: std::fmt::Display + fn to_uri(&self) -> Uri { + self.to_string().parse().unwrap() + } } /// Utility trait used with [`RouterExt`] to ensure the first element of a tuple type is a diff --git a/axum-macros/src/typed_path.rs b/axum-macros/src/typed_path.rs index d7b1414f..3336584f 100644 --- a/axum-macros/src/typed_path.rs +++ b/axum-macros/src/typed_path.rs @@ -29,7 +29,7 @@ pub(crate) fn expand(item_struct: ItemStruct) -> syn::Result { let segments = parse_path(&path)?; expand_unnamed_fields(fields, ident, path, &segments) } - syn::Fields::Unit => Ok(expand_unit_fields(ident, path)?), + syn::Fields::Unit => expand_unit_fields(ident, path), } } @@ -97,7 +97,7 @@ fn expand_named_fields(ident: &syn::Ident, path: LitStr, segments: &[Segment]) - { type Rejection = <::axum::extract::Path as ::axum::extract::FromRequest>::Rejection; - async fn from_request(req: &mut ::axum::extract::RequestParts) -> Result { + async fn from_request(req: &mut ::axum::extract::RequestParts) -> ::std::result::Result { ::axum::extract::Path::from_request(req).await.map(|path| path.0) } } @@ -186,7 +186,7 @@ fn expand_unnamed_fields( { type Rejection = <::axum::extract::Path as ::axum::extract::FromRequest>::Rejection; - async fn from_request(req: &mut ::axum::extract::RequestParts) -> Result { + async fn from_request(req: &mut ::axum::extract::RequestParts) -> ::std::result::Result { ::axum::extract::Path::from_request(req).await.map(|path| path.0) } } @@ -245,7 +245,7 @@ fn expand_unit_fields(ident: &syn::Ident, path: LitStr) -> syn::Result) -> Result { + async fn from_request(req: &mut ::axum::extract::RequestParts) -> ::std::result::Result { if req.uri().path() == ::PATH { Ok(Self) } else { diff --git a/axum-macros/tests/typed_path/pass/into_uri.rs b/axum-macros/tests/typed_path/pass/into_uri.rs new file mode 100644 index 00000000..5276627c --- /dev/null +++ b/axum-macros/tests/typed_path/pass/into_uri.rs @@ -0,0 +1,23 @@ +use axum_extra::routing::TypedPath; +use axum::http::Uri; +use serde::Deserialize; + +#[derive(TypedPath, Deserialize)] +#[typed_path("/:id")] +struct Named { + id: u32, +} + +#[derive(TypedPath, Deserialize)] +#[typed_path("/:id")] +struct Unnamed(u32); + +#[derive(TypedPath, Deserialize)] +#[typed_path("/")] +struct Unit; + +fn main() { + let _: Uri = Named { id: 1 }.to_uri(); + let _: Uri = Unnamed(1).to_uri(); + let _: Uri = Unit.to_uri(); +} diff --git a/axum-macros/tests/typed_path/pass/tuple_struct.rs b/axum-macros/tests/typed_path/pass/tuple_struct.rs index a0b2e609..5e3d27ff 100644 --- a/axum-macros/tests/typed_path/pass/tuple_struct.rs +++ b/axum-macros/tests/typed_path/pass/tuple_struct.rs @@ -1,6 +1,8 @@ use axum_extra::routing::TypedPath; use serde::Deserialize; +pub type Result = std::result::Result; + #[derive(TypedPath, Deserialize)] #[typed_path("/users/:user_id/teams/:team_id")] struct MyPath(u32, u32);