From 46f85fd9ba1ab2e752e2e58e888fd6a0fc5b95c6 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Sun, 13 Feb 2022 22:08:49 +0100 Subject: [PATCH] docs --- axum-extra/src/routing/mod.rs | 57 ++++++++++++++++++---- axum-extra/src/routing/typed.rs | 84 +++++++++++++++++++++++++++++---- axum-macros/Cargo.toml | 1 + axum-macros/src/lib.rs | 4 +- 4 files changed, 126 insertions(+), 20 deletions(-) diff --git a/axum-extra/src/routing/mod.rs b/axum-extra/src/routing/mod.rs index 4c573a5f..d3e92ef2 100644 --- a/axum-extra/src/routing/mod.rs +++ b/axum-extra/src/routing/mod.rs @@ -7,7 +7,6 @@ mod resource; #[cfg(feature = "typed-routing")] mod typed; -/// TODO(david): docs #[cfg(feature = "typed-routing")] #[cfg_attr(docsrs, doc(cfg(feature = "typed-routing")))] pub use axum_macros::TypedPath; @@ -44,7 +43,12 @@ pub trait RouterExt: sealed::Sealed { where T: HasRoutes; - /// TODO(david): docs + /// Add a typed `GET` route to the router. + /// + /// The path will be inferred from the first argument to the handler function which must + /// implement [`TypedPath`]. + /// + /// See [`TypedPath`] for more details and examples. #[cfg(feature = "typed-routing")] #[cfg_attr(docsrs, doc(cfg(feature = "typed-routing")))] fn typed_get(self, handler: H) -> Self @@ -53,7 +57,12 @@ pub trait RouterExt: sealed::Sealed { T: FirstElementIs

+ 'static, P: TypedPath; - /// TODO(david): docs + /// Add a typed `DELETE` route to the router. + /// + /// The path will be inferred from the first argument to the handler function which must + /// implement [`TypedPath`]. + /// + /// See [`TypedPath`] for more details and examples. #[cfg(feature = "typed-routing")] #[cfg_attr(docsrs, doc(cfg(feature = "typed-routing")))] fn typed_delete(self, handler: H) -> Self @@ -62,7 +71,12 @@ pub trait RouterExt: sealed::Sealed { T: FirstElementIs

+ 'static, P: TypedPath; - /// TODO(david): docs + /// Add a typed `HEAD` route to the router. + /// + /// The path will be inferred from the first argument to the handler function which must + /// implement [`TypedPath`]. + /// + /// See [`TypedPath`] for more details and examples. #[cfg(feature = "typed-routing")] #[cfg_attr(docsrs, doc(cfg(feature = "typed-routing")))] fn typed_head(self, handler: H) -> Self @@ -71,7 +85,12 @@ pub trait RouterExt: sealed::Sealed { T: FirstElementIs

+ 'static, P: TypedPath; - /// TODO(david): docs + /// Add a typed `OPTIONS` route to the router. + /// + /// The path will be inferred from the first argument to the handler function which must + /// implement [`TypedPath`]. + /// + /// See [`TypedPath`] for more details and examples. #[cfg(feature = "typed-routing")] #[cfg_attr(docsrs, doc(cfg(feature = "typed-routing")))] fn typed_options(self, handler: H) -> Self @@ -80,7 +99,12 @@ pub trait RouterExt: sealed::Sealed { T: FirstElementIs

+ 'static, P: TypedPath; - /// TODO(david): docs + /// Add a typed `PATCH` route to the router. + /// + /// The path will be inferred from the first argument to the handler function which must + /// implement [`TypedPath`]. + /// + /// See [`TypedPath`] for more details and examples. #[cfg(feature = "typed-routing")] #[cfg_attr(docsrs, doc(cfg(feature = "typed-routing")))] fn typed_patch(self, handler: H) -> Self @@ -89,7 +113,12 @@ pub trait RouterExt: sealed::Sealed { T: FirstElementIs

+ 'static, P: TypedPath; - /// TODO(david): docs + /// Add a typed `POST` route to the router. + /// + /// The path will be inferred from the first argument to the handler function which must + /// implement [`TypedPath`]. + /// + /// See [`TypedPath`] for more details and examples. #[cfg(feature = "typed-routing")] #[cfg_attr(docsrs, doc(cfg(feature = "typed-routing")))] fn typed_post(self, handler: H) -> Self @@ -98,7 +127,12 @@ pub trait RouterExt: sealed::Sealed { T: FirstElementIs

+ 'static, P: TypedPath; - /// TODO(david): docs + /// Add a typed `PUT` route to the router. + /// + /// The path will be inferred from the first argument to the handler function which must + /// implement [`TypedPath`]. + /// + /// See [`TypedPath`] for more details and examples. #[cfg(feature = "typed-routing")] #[cfg_attr(docsrs, doc(cfg(feature = "typed-routing")))] fn typed_put(self, handler: H) -> Self @@ -107,7 +141,12 @@ pub trait RouterExt: sealed::Sealed { T: FirstElementIs

+ 'static, P: TypedPath; - /// TODO(david): docs + /// Add a typed `TRACE` route to the router. + /// + /// The path will be inferred from the first argument to the handler function which must + /// implement [`TypedPath`]. + /// + /// See [`TypedPath`] for more details and examples. #[cfg(feature = "typed-routing")] #[cfg_attr(docsrs, doc(cfg(feature = "typed-routing")))] fn typed_trace(self, handler: H) -> Self diff --git a/axum-extra/src/routing/typed.rs b/axum-extra/src/routing/typed.rs index c6f07575..65a5efdd 100644 --- a/axum-extra/src/routing/typed.rs +++ b/axum-extra/src/routing/typed.rs @@ -1,10 +1,9 @@ -//! Type safe routing. -//! -//! See [`TypedPath`] for more details. - use super::sealed::Sealed; -/// TODO(david): more docs +/// A type safe path. +/// +/// This is used to statically connect a path to its corresponding handler using +/// [`RouterExt::typed_get`], [`RouterExt::typed_post`], etc. /// /// # Example /// @@ -18,14 +17,14 @@ use super::sealed::Sealed; /// }; /// /// // A type safe route with `/users/:id` as its associated path. -/// #[derive(Deserialize, TypedPath)] +/// #[derive(TypedPath, Deserialize)] /// #[typed_path("/users/:id")] /// struct UsersMember { /// id: u32, /// } /// /// // A regular handler function that takes `UsersMember` as the first argument -/// // and thus creates a typed connection between this handler and the `/users/:id` route. +/// // and thus creates a typed connection between this handler and the `/users/:id` path. /// // /// // The `TypedPath` must be the first argument to the function. /// async fn users_show( @@ -63,11 +62,68 @@ use super::sealed::Sealed; /// # /// # let app: Router = app; /// ``` +/// +/// # Using `#[derive(TypedPath)]` +/// +/// While `TypedPath` can be implemented manually its _highly_ recommended to derive it: +/// +/// ``` +/// # use serde::Deserialize; +/// # +/// #[derive(TypedPath, Deserialize)] +/// #[typed_path("/users/:id")] +/// struct UsersMember { +/// id: u32, +/// } +/// ``` +/// +/// The macro expands to: +/// +/// - A `TypedPath` implementation. +/// - A [`Display`] implementation that interpolates the captures. This can be used to, among other +/// things, create links to known paths and have them verified statically. +/// - A [`FromRequest`] implementation compatible with [`RouterExt::typed_get`], +/// [`RouterExt::typed_post`], etc. This implementation uses [`Path`] and thus your struct must +/// also implement [`serde::Deserialize`], unless its a unit struct. +/// +/// 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: +/// +/// ```compile_fail +/// # use serde::Deserialize; +/// # +/// #[derive(TypedPath, Deserialize)] +/// #[typed_path("/users/:id/teams/:team_id")] +/// struct UsersMember { +/// id: u32, +/// } +/// ``` +/// +/// Unit and tuple structs are also supported: +/// +/// ``` +/// # use serde::Deserialize; +/// # +/// #[derive(TypedPath)] +/// #[typed_path("/users")] +/// struct UsersCollection; +/// +/// #[derive(TypedPath, Deserialize)] +/// #[typed_path("/users/:id")] +/// struct UsersMember(u32); +/// ``` +/// +/// [`FromRequest`]: axum::extract::FromRequest +/// [`RouterExt::typed_get`]: super::RouterExt::typed_get +/// [`RouterExt::typed_post`]: super::RouterExt::typed_post +/// [`Path`]: axum::extract::Path +/// [`Display`]: std::fmt::Display pub trait TypedPath: std::fmt::Display { + /// The path with optional captures such as `/users/:id`. const PATH: &'static str; } -/// Utility trait used with [`TypedRouter`] to ensure the first element of a tuple type is a +/// Utility trait used with [`RouterExt`] to ensure the first element of a tuple type is a /// given type. /// /// If you see it in type errors its most likely because the first argument to your handler doesn't @@ -76,13 +132,21 @@ pub trait TypedPath: std::fmt::Display { /// You normally shouldn't have to use this trait directly. /// /// It is sealed such that it cannot be implemented outside this crate. +/// +/// [`RouterExt`]: super::RouterExt pub trait FirstElementIs

: Sealed {} macro_rules! impl_first_element_is { ( $($ty:ident),* $(,)? ) => { - impl FirstElementIs

for (P, $($ty,)*) {} + impl FirstElementIs

for (P, $($ty,)*) + where + P: TypedPath + {} - impl Sealed for (P, $($ty,)*) {} + impl Sealed for (P, $($ty,)*) + where + P: TypedPath + {} }; } diff --git a/axum-macros/Cargo.toml b/axum-macros/Cargo.toml index 383fbced..fc667f48 100644 --- a/axum-macros/Cargo.toml +++ b/axum-macros/Cargo.toml @@ -21,6 +21,7 @@ syn = { version = "1.0", features = ["full"] } [dev-dependencies] axum = { path = "../axum", version = "0.4", features = ["headers"] } +axum-extra = { path = "../axum-extra", version = "0.1" } rustversion = "1.0" serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.0", features = ["full"] } diff --git a/axum-macros/src/lib.rs b/axum-macros/src/lib.rs index 244766bd..e6751698 100644 --- a/axum-macros/src/lib.rs +++ b/axum-macros/src/lib.rs @@ -386,7 +386,9 @@ pub fn debug_handler(_attr: TokenStream, input: TokenStream) -> TokenStream { return expand_attr_with(_attr, input, debug_handler::expand); } -/// TODO +/// Derive an implementation of [`axum_extra::routing::TypedPath`]. +/// +/// See that trait for more details. #[proc_macro_derive(TypedPath, attributes(typed_path))] pub fn derive_typed_path(input: TokenStream) -> TokenStream { expand_with(input, typed_path::expand)