From e4e89903fee48d09ddadde45050eac00693c8c85 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Thu, 24 Feb 2022 23:39:05 +0100 Subject: [PATCH 1/4] Easily convert typed paths into URIs `#[derive(TypedPath)]` will now also generate `TryFrom<_> for Uri` for easily converting paths into URIs for use with `Redirect` and friends. Fixes /~https://github.com/tokio-rs/axum/issues/789 --- axum-extra/src/routing/typed.rs | 2 ++ axum-macros/CHANGELOG.md | 6 ++++- axum-macros/src/typed_path.rs | 22 +++++++++++++---- .../tests/typed_path/pass/try_into_uri.rs | 24 +++++++++++++++++++ 4 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 axum-macros/tests/typed_path/pass/try_into_uri.rs diff --git a/axum-extra/src/routing/typed.rs b/axum-extra/src/routing/typed.rs index 5c85545355..3ced9093bb 100644 --- a/axum-extra/src/routing/typed.rs +++ b/axum-extra/src/routing/typed.rs @@ -87,6 +87,8 @@ use super::sealed::Sealed; /// things, create links to known paths and have them verified statically. Note that the /// [`Display`] implementation for each field must return something that's compatible with its /// [`Deserialize`] implementation. +/// - A [`TryFrom<_> for Uri`](std::convert::TryFrom) implementation to converting your paths into +/// [`Uri`](axum::http::Uri). /// /// Additionally the macro will verify the captures in the path matches the fields of the struct. /// For example this fails to compile since the struct doesn't have a `team_id` field: diff --git a/axum-macros/CHANGELOG.md b/axum-macros/CHANGELOG.md index da3338d030..896e5dcb7f 100644 --- a/axum-macros/CHANGELOG.md +++ b/axum-macros/CHANGELOG.md @@ -7,7 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # Unreleased -- Add `#[derive(TypedPath)]` for use with axum-extra's new "type safe" routing API ([#756]) +- **added:** Add `#[derive(TypedPath)]` for use with axum-extra's new "type safe" routing API ([#756]) +- **added:** `#[derive(TypedPath)]` now also generates a `TryFrom<_> for Uri` + implementation ([#790]) + +[#790]: /~https://github.com/tokio-rs/axum/pull/790 # 0.1.0 (31. January, 2022) diff --git a/axum-macros/src/typed_path.rs b/axum-macros/src/typed_path.rs index d7b1414f41..f0e4fc16de 100644 --- a/axum-macros/src/typed_path.rs +++ b/axum-macros/src/typed_path.rs @@ -20,17 +20,29 @@ pub(crate) fn expand(item_struct: ItemStruct) -> syn::Result { let Attrs { path } = parse_attrs(attrs)?; - match fields { + let code = match fields { syn::Fields::Named(_) => { let segments = parse_path(&path)?; - Ok(expand_named_fields(ident, path, &segments)) + expand_named_fields(ident, path, &segments) } syn::Fields::Unnamed(fields) => { let segments = parse_path(&path)?; - expand_unnamed_fields(fields, ident, path, &segments) + expand_unnamed_fields(fields, ident, path, &segments)? } - syn::Fields::Unit => Ok(expand_unit_fields(ident, path)?), - } + syn::Fields::Unit => expand_unit_fields(ident, path)?, + }; + + Ok(quote! { + #code + + impl ::std::convert::TryFrom<#ident> for ::axum::http::Uri { + type Error = ::axum::http::uri::InvalidUri; + + fn try_from(value: #ident) -> ::std::result::Result { + value.to_string().parse() + } + } + }) } struct Attrs { diff --git a/axum-macros/tests/typed_path/pass/try_into_uri.rs b/axum-macros/tests/typed_path/pass/try_into_uri.rs new file mode 100644 index 0000000000..f6e310848d --- /dev/null +++ b/axum-macros/tests/typed_path/pass/try_into_uri.rs @@ -0,0 +1,24 @@ +use axum_extra::routing::TypedPath; +use axum::http::Uri; +use serde::Deserialize; +use std::convert::TryInto; + +#[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 }.try_into().unwrap(); + let _: Uri = Unnamed(1).try_into().unwrap(); + let _: Uri = Unit.try_into().unwrap(); +} From eaeb052d0a7f51ecbb03f2f7d62ef641a2a42098 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Fri, 25 Feb 2022 16:27:55 +0100 Subject: [PATCH 2/4] Use a method on the `TypedPath` trait to convert to `Uri` --- axum-extra/src/routing/typed.rs | 14 ++++++++++++ axum-macros/src/typed_path.rs | 22 +++++-------------- .../pass/{try_into_uri.rs => into_uri.rs} | 7 +++--- 3 files changed, 22 insertions(+), 21 deletions(-) rename axum-macros/tests/typed_path/pass/{try_into_uri.rs => into_uri.rs} (65%) diff --git a/axum-extra/src/routing/typed.rs b/axum-extra/src/routing/typed.rs index 3ced9093bb..acea651f05 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. /// @@ -150,6 +151,19 @@ 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. + 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 f0e4fc16de..ee1104d815 100644 --- a/axum-macros/src/typed_path.rs +++ b/axum-macros/src/typed_path.rs @@ -20,29 +20,17 @@ pub(crate) fn expand(item_struct: ItemStruct) -> syn::Result { let Attrs { path } = parse_attrs(attrs)?; - let code = match fields { + match fields { syn::Fields::Named(_) => { let segments = parse_path(&path)?; - expand_named_fields(ident, path, &segments) + Ok(expand_named_fields(ident, path, &segments)) } syn::Fields::Unnamed(fields) => { let segments = parse_path(&path)?; - expand_unnamed_fields(fields, ident, path, &segments)? + expand_unnamed_fields(fields, ident, path, &segments) } - syn::Fields::Unit => expand_unit_fields(ident, path)?, - }; - - Ok(quote! { - #code - - impl ::std::convert::TryFrom<#ident> for ::axum::http::Uri { - type Error = ::axum::http::uri::InvalidUri; - - fn try_from(value: #ident) -> ::std::result::Result { - value.to_string().parse() - } - } - }) + syn::Fields::Unit => expand_unit_fields(ident, path), + } } struct Attrs { diff --git a/axum-macros/tests/typed_path/pass/try_into_uri.rs b/axum-macros/tests/typed_path/pass/into_uri.rs similarity index 65% rename from axum-macros/tests/typed_path/pass/try_into_uri.rs rename to axum-macros/tests/typed_path/pass/into_uri.rs index f6e310848d..5276627c2f 100644 --- a/axum-macros/tests/typed_path/pass/try_into_uri.rs +++ b/axum-macros/tests/typed_path/pass/into_uri.rs @@ -1,7 +1,6 @@ use axum_extra::routing::TypedPath; use axum::http::Uri; use serde::Deserialize; -use std::convert::TryInto; #[derive(TypedPath, Deserialize)] #[typed_path("/:id")] @@ -18,7 +17,7 @@ struct Unnamed(u32); struct Unit; fn main() { - let _: Uri = Named { id: 1 }.try_into().unwrap(); - let _: Uri = Unnamed(1).try_into().unwrap(); - let _: Uri = Unit.try_into().unwrap(); + let _: Uri = Named { id: 1 }.to_uri(); + let _: Uri = Unnamed(1).to_uri(); + let _: Uri = Unit.to_uri(); } From 4401d619ddde5ea932a5e19d8ff638fdf4692522 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Fri, 25 Feb 2022 16:30:01 +0100 Subject: [PATCH 3/4] fix doc ref --- axum-extra/src/routing/typed.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/axum-extra/src/routing/typed.rs b/axum-extra/src/routing/typed.rs index acea651f05..89294daf3e 100644 --- a/axum-extra/src/routing/typed.rs +++ b/axum-extra/src/routing/typed.rs @@ -161,6 +161,8 @@ pub trait TypedPath: std::fmt::Display { /// /// 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() } From 2d531450954efa4890a2fb46c672da3dda9f15c4 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Mon, 28 Feb 2022 08:38:34 +0100 Subject: [PATCH 4/4] Update changelogs --- axum-extra/CHANGELOG.md | 2 ++ axum-macros/CHANGELOG.md | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/axum-extra/CHANGELOG.md b/axum-extra/CHANGELOG.md index 00764fd446..1319fec8be 100644 --- a/axum-extra/CHANGELOG.md +++ b/axum-extra/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning]. # Unreleased +- **added:** Add `TypedPath::to_uri` for converting the path into a `Uri` ([#790]) - **added:** Add type safe routing. See `axum_extra::routing::typed` for more details ([#756]) - **breaking:** `CachedRejection` has been removed ([#699]) - **breaking:** ` as FromRequest>::Rejection` is now `T::Rejection`. ([#699]) @@ -16,6 +17,7 @@ and this project adheres to [Semantic Versioning]. [#699]: /~https://github.com/tokio-rs/axum/pull/699 [#719]: /~https://github.com/tokio-rs/axum/pull/719 [#756]: /~https://github.com/tokio-rs/axum/pull/756 +[#790]: /~https://github.com/tokio-rs/axum/pull/790 # 0.1.2 (13. January, 2022) diff --git a/axum-macros/CHANGELOG.md b/axum-macros/CHANGELOG.md index 896e5dcb7f..3346a5eaca 100644 --- a/axum-macros/CHANGELOG.md +++ b/axum-macros/CHANGELOG.md @@ -8,10 +8,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # Unreleased - **added:** Add `#[derive(TypedPath)]` for use with axum-extra's new "type safe" routing API ([#756]) -- **added:** `#[derive(TypedPath)]` now also generates a `TryFrom<_> for Uri` - implementation ([#790]) - -[#790]: /~https://github.com/tokio-rs/axum/pull/790 # 0.1.0 (31. January, 2022)