mirror of
https://github.com/tokio-rs/axum.git
synced 2026-09-02 00:00:22 +02:00
Simplify things quite a bit
This commit is contained in:
@@ -31,6 +31,7 @@ serde_json = { version = "1.0.71", optional = true }
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
axum-macros = { path = "../axum-macros", version = "0.1" }
|
axum-macros = { path = "../axum-macros", version = "0.1" }
|
||||||
hyper = "0.14"
|
hyper = "0.14"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
tokio = { version = "1.14", features = ["full"] }
|
tokio = { version = "1.14", features = ["full"] }
|
||||||
tower = { version = "0.4", features = ["util"] }
|
tower = { version = "0.4", features = ["util"] }
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
//! Additional types for defining routes.
|
//! Additional types for defining routes.
|
||||||
|
|
||||||
use axum::{body::Body, Router};
|
use axum::{body::Body, handler::Handler, Router};
|
||||||
|
|
||||||
mod resource;
|
mod resource;
|
||||||
pub mod typed_path;
|
mod typed;
|
||||||
|
|
||||||
pub use self::{resource::Resource, typed_path::TypedPath};
|
pub use self::{
|
||||||
|
resource::Resource,
|
||||||
|
typed::{FirstElementIs, TypedPath},
|
||||||
|
};
|
||||||
|
|
||||||
/// Extension trait that adds additional methods to [`Router`].
|
/// Extension trait that adds additional methods to [`Router`].
|
||||||
pub trait RouterExt<B>: sealed::Sealed {
|
pub trait RouterExt<B>: sealed::Sealed {
|
||||||
@@ -33,6 +36,62 @@ pub trait RouterExt<B>: sealed::Sealed {
|
|||||||
fn with<T>(self, routes: T) -> Self
|
fn with<T>(self, routes: T) -> Self
|
||||||
where
|
where
|
||||||
T: HasRoutes<B>;
|
T: HasRoutes<B>;
|
||||||
|
|
||||||
|
/// TODO(david): docs
|
||||||
|
fn typed_get<H, T, P>(self, handler: H) -> Self
|
||||||
|
where
|
||||||
|
H: Handler<T, B>,
|
||||||
|
T: FirstElementIs<P> + 'static,
|
||||||
|
P: TypedPath;
|
||||||
|
|
||||||
|
/// TODO(david): docs
|
||||||
|
fn typed_delete<H, T, P>(self, handler: H) -> Self
|
||||||
|
where
|
||||||
|
H: Handler<T, B>,
|
||||||
|
T: FirstElementIs<P> + 'static,
|
||||||
|
P: TypedPath;
|
||||||
|
|
||||||
|
/// TODO(david): docs
|
||||||
|
fn typed_head<H, T, P>(self, handler: H) -> Self
|
||||||
|
where
|
||||||
|
H: Handler<T, B>,
|
||||||
|
T: FirstElementIs<P> + 'static,
|
||||||
|
P: TypedPath;
|
||||||
|
|
||||||
|
/// TODO(david): docs
|
||||||
|
fn typed_options<H, T, P>(self, handler: H) -> Self
|
||||||
|
where
|
||||||
|
H: Handler<T, B>,
|
||||||
|
T: FirstElementIs<P> + 'static,
|
||||||
|
P: TypedPath;
|
||||||
|
|
||||||
|
/// TODO(david): docs
|
||||||
|
fn typed_patch<H, T, P>(self, handler: H) -> Self
|
||||||
|
where
|
||||||
|
H: Handler<T, B>,
|
||||||
|
T: FirstElementIs<P> + 'static,
|
||||||
|
P: TypedPath;
|
||||||
|
|
||||||
|
/// TODO(david): docs
|
||||||
|
fn typed_post<H, T, P>(self, handler: H) -> Self
|
||||||
|
where
|
||||||
|
H: Handler<T, B>,
|
||||||
|
T: FirstElementIs<P> + 'static,
|
||||||
|
P: TypedPath;
|
||||||
|
|
||||||
|
/// TODO(david): docs
|
||||||
|
fn typed_put<H, T, P>(self, handler: H) -> Self
|
||||||
|
where
|
||||||
|
H: Handler<T, B>,
|
||||||
|
T: FirstElementIs<P> + 'static,
|
||||||
|
P: TypedPath;
|
||||||
|
|
||||||
|
/// TODO(david): docs
|
||||||
|
fn typed_trace<H, T, P>(self, handler: H) -> Self
|
||||||
|
where
|
||||||
|
H: Handler<T, B>,
|
||||||
|
T: FirstElementIs<P> + 'static,
|
||||||
|
P: TypedPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B> RouterExt<B> for Router<B>
|
impl<B> RouterExt<B> for Router<B>
|
||||||
@@ -45,6 +104,78 @@ where
|
|||||||
{
|
{
|
||||||
self.merge(routes.routes())
|
self.merge(routes.routes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn typed_get<H, T, P>(self, handler: H) -> Self
|
||||||
|
where
|
||||||
|
H: Handler<T, B>,
|
||||||
|
T: FirstElementIs<P> + 'static,
|
||||||
|
P: TypedPath,
|
||||||
|
{
|
||||||
|
self.route(P::PATH, axum::routing::get(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn typed_delete<H, T, P>(self, handler: H) -> Self
|
||||||
|
where
|
||||||
|
H: Handler<T, B>,
|
||||||
|
T: FirstElementIs<P> + 'static,
|
||||||
|
P: TypedPath,
|
||||||
|
{
|
||||||
|
self.route(P::PATH, axum::routing::delete(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn typed_head<H, T, P>(self, handler: H) -> Self
|
||||||
|
where
|
||||||
|
H: Handler<T, B>,
|
||||||
|
T: FirstElementIs<P> + 'static,
|
||||||
|
P: TypedPath,
|
||||||
|
{
|
||||||
|
self.route(P::PATH, axum::routing::head(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn typed_options<H, T, P>(self, handler: H) -> Self
|
||||||
|
where
|
||||||
|
H: Handler<T, B>,
|
||||||
|
T: FirstElementIs<P> + 'static,
|
||||||
|
P: TypedPath,
|
||||||
|
{
|
||||||
|
self.route(P::PATH, axum::routing::options(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn typed_patch<H, T, P>(self, handler: H) -> Self
|
||||||
|
where
|
||||||
|
H: Handler<T, B>,
|
||||||
|
T: FirstElementIs<P> + 'static,
|
||||||
|
P: TypedPath,
|
||||||
|
{
|
||||||
|
self.route(P::PATH, axum::routing::patch(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn typed_post<H, T, P>(self, handler: H) -> Self
|
||||||
|
where
|
||||||
|
H: Handler<T, B>,
|
||||||
|
T: FirstElementIs<P> + 'static,
|
||||||
|
P: TypedPath,
|
||||||
|
{
|
||||||
|
self.route(P::PATH, axum::routing::post(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn typed_put<H, T, P>(self, handler: H) -> Self
|
||||||
|
where
|
||||||
|
H: Handler<T, B>,
|
||||||
|
T: FirstElementIs<P> + 'static,
|
||||||
|
P: TypedPath,
|
||||||
|
{
|
||||||
|
self.route(P::PATH, axum::routing::put(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn typed_trace<H, T, P>(self, handler: H) -> Self
|
||||||
|
where
|
||||||
|
H: Handler<T, B>,
|
||||||
|
T: FirstElementIs<P> + 'static,
|
||||||
|
P: TypedPath,
|
||||||
|
{
|
||||||
|
self.route(P::PATH, axum::routing::trace(handler))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait for things that can provide routes.
|
/// Trait for things that can provide routes.
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
/// Type safe routing.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use serde::Deserialize;
|
||||||
|
/// use axum_macros::TypedPath;
|
||||||
|
/// use axum::{Router, extract::Json};
|
||||||
|
/// use axum_extra::routing::{
|
||||||
|
/// typed,
|
||||||
|
/// RouterExt, // for `Router::with`
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// // A type safe route with `/users/:id` as its associated path.
|
||||||
|
/// #[derive(Deserialize, TypedPath)]
|
||||||
|
/// #[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.
|
||||||
|
/// //
|
||||||
|
/// // The `TypedPath` must be the first argument to the function.
|
||||||
|
/// async fn users_show(
|
||||||
|
/// UsersMember { id }: UsersMember,
|
||||||
|
/// ) {
|
||||||
|
/// // ...
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let app = Router::new()
|
||||||
|
/// // Add our typed route to the router.
|
||||||
|
/// //
|
||||||
|
/// // The path will be inferred to `/users/:id` since `users_show`'s
|
||||||
|
/// // first argument is `UsersMember` which implements `TypedPath`
|
||||||
|
/// .with(typed::get(users_show))
|
||||||
|
/// // Add multiple handlers for `/users` depending on the HTTP method.
|
||||||
|
/// .with(typed::post(users_create).delete(users_destroy))
|
||||||
|
/// // We can still add regular routes.
|
||||||
|
/// .route("/foo", get(|| async { /* ... */ }));
|
||||||
|
///
|
||||||
|
/// #[derive(TypedPath)]
|
||||||
|
/// #[typed_path("/users")]
|
||||||
|
/// struct UsersCollection;
|
||||||
|
///
|
||||||
|
/// #[derive(Deserialize)]
|
||||||
|
/// struct UsersCreatePayload { /* ... */ }
|
||||||
|
///
|
||||||
|
/// async fn users_create(
|
||||||
|
/// _: UsersCollection,
|
||||||
|
/// // Our handlers can accept other extractors.
|
||||||
|
/// Json(payload): Json<Payload>,
|
||||||
|
/// ) {
|
||||||
|
/// // ...
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// async fn users_destroy(_: UsersCollection) { /* ... */ }
|
||||||
|
///
|
||||||
|
/// #
|
||||||
|
/// # let app: Router<axum::body::Body> = app;
|
||||||
|
/// ```
|
||||||
|
use super::sealed::Sealed;
|
||||||
|
|
||||||
|
pub trait TypedPath: std::fmt::Display {
|
||||||
|
const PATH: &'static str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Utility trait used with [`TypedRouter`] 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
|
||||||
|
/// implement [`TypedPath`].
|
||||||
|
///
|
||||||
|
/// You normally shouldn't have to use this trait directly.
|
||||||
|
///
|
||||||
|
/// It is sealed such that it cannot be implemented outside this crate.
|
||||||
|
pub trait FirstElementIs<P>: Sealed {}
|
||||||
|
|
||||||
|
macro_rules! impl_first_element_is {
|
||||||
|
( $($ty:ident),* $(,)? ) => {
|
||||||
|
impl<P, $($ty,)*> FirstElementIs<P> for (P, $($ty,)*) {}
|
||||||
|
|
||||||
|
impl<P, $($ty,)*> Sealed for (P, $($ty,)*) {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_first_element_is!();
|
||||||
|
impl_first_element_is!(T1);
|
||||||
|
impl_first_element_is!(T1, T2);
|
||||||
|
impl_first_element_is!(T1, T2, T3);
|
||||||
|
impl_first_element_is!(T1, T2, T3, T4);
|
||||||
|
impl_first_element_is!(T1, T2, T3, T4, T5);
|
||||||
|
impl_first_element_is!(T1, T2, T3, T4, T5, T6);
|
||||||
|
impl_first_element_is!(T1, T2, T3, T4, T5, T6, T7);
|
||||||
|
impl_first_element_is!(T1, T2, T3, T4, T5, T6, T7, T8);
|
||||||
|
impl_first_element_is!(T1, T2, T3, T4, T5, T6, T7, T8, T9);
|
||||||
|
impl_first_element_is!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
|
||||||
|
impl_first_element_is!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
|
||||||
|
impl_first_element_is!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
|
||||||
|
impl_first_element_is!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13);
|
||||||
|
impl_first_element_is!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14);
|
||||||
|
impl_first_element_is!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15);
|
||||||
|
impl_first_element_is!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16);
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
#![allow(missing_docs, missing_debug_implementations)]
|
|
||||||
|
|
||||||
use axum::{body::HttpBody, handler::Handler, routing, Router};
|
|
||||||
use std::{borrow::Cow, marker::PhantomData};
|
|
||||||
|
|
||||||
use super::HasRoutes;
|
|
||||||
|
|
||||||
/// ```rust
|
|
||||||
/// use axum_macros::TypedPath;
|
|
||||||
///
|
|
||||||
/// #[derive(TypedPath)]
|
|
||||||
/// #[typed_path("/users/:id")]
|
|
||||||
/// struct UsersShow {
|
|
||||||
/// id: u32,
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub trait TypedPath {
|
|
||||||
const PATH: &'static str;
|
|
||||||
fn path(&self) -> Cow<'static, str>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get<H, B, T, P>(handler: H) -> TypedPathRouter<P, B>
|
|
||||||
where
|
|
||||||
H: Handler<T, B>,
|
|
||||||
P: TypedPath,
|
|
||||||
T: FirstElementIs<P> + 'static,
|
|
||||||
B: HttpBody + Send + 'static,
|
|
||||||
{
|
|
||||||
TypedPathRouter {
|
|
||||||
router: Router::new().route(P::PATH, routing::get(handler)),
|
|
||||||
_path: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TypedPathRouter<P, B> {
|
|
||||||
router: Router<B>,
|
|
||||||
_path: PhantomData<P>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P, B> TypedPathRouter<P, B>
|
|
||||||
where
|
|
||||||
B: HttpBody + Send + 'static,
|
|
||||||
P: TypedPath,
|
|
||||||
{
|
|
||||||
pub fn post<H, T>(mut self, handler: H) -> Self
|
|
||||||
where
|
|
||||||
H: Handler<T, B>,
|
|
||||||
T: FirstElementIs<P> + 'static,
|
|
||||||
{
|
|
||||||
self.router = self.router.route(P::PATH, routing::post(handler));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P, B> HasRoutes<B> for TypedPathRouter<P, B> {
|
|
||||||
fn routes(self) -> Router<B> {
|
|
||||||
self.router
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait FirstElementIs<P> {}
|
|
||||||
impl<P> FirstElementIs<P> for (P,) {}
|
|
||||||
impl<P, T1> FirstElementIs<P> for (P, T1) {}
|
|
||||||
impl<P, T1, T2> FirstElementIs<P> for (P, T1, T2) {}
|
|
||||||
@@ -43,7 +43,14 @@ fn parse_attrs(attrs: &[syn::Attribute]) -> syn::Result<Attrs> {
|
|||||||
|
|
||||||
for attr in attrs {
|
for attr in attrs {
|
||||||
if attr.path.is_ident("typed_path") {
|
if attr.path.is_ident("typed_path") {
|
||||||
path = Some(attr.parse_args()?);
|
if path.is_some() {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
attr,
|
||||||
|
"`typed_path` specified more than once",
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
path = Some(attr.parse_args()?);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,10 +72,13 @@ fn expand_named_fields(ident: &syn::Ident, path: LitStr, segments: &[Segment]) -
|
|||||||
#[automatically_derived]
|
#[automatically_derived]
|
||||||
impl ::axum_extra::routing::TypedPath for #ident {
|
impl ::axum_extra::routing::TypedPath for #ident {
|
||||||
const PATH: &'static str = #path;
|
const PATH: &'static str = #path;
|
||||||
|
}
|
||||||
|
|
||||||
fn path(&self) -> ::std::borrow::Cow<'static, str> {
|
#[automatically_derived]
|
||||||
|
impl ::std::fmt::Display for #ident {
|
||||||
|
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||||
let Self { #(#captures,)* } = self;
|
let Self { #(#captures,)* } = self;
|
||||||
format!(#format_str, #(#captures = #captures,)*).into()
|
write!(f, #format_str, #(#captures = #captures,)*)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,10 +147,26 @@ fn expand_unnamed_fields(
|
|||||||
#[automatically_derived]
|
#[automatically_derived]
|
||||||
impl ::axum_extra::routing::TypedPath for #ident {
|
impl ::axum_extra::routing::TypedPath for #ident {
|
||||||
const PATH: &'static str = #path;
|
const PATH: &'static str = #path;
|
||||||
|
}
|
||||||
|
|
||||||
fn path(&self) -> ::std::borrow::Cow<'static, str> {
|
#[automatically_derived]
|
||||||
|
impl ::std::fmt::Display for #ident {
|
||||||
|
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||||
let Self { #(#destructure_self)* } = self;
|
let Self { #(#destructure_self)* } = self;
|
||||||
format!(#format_str, #(#captures = #captures,)*).into()
|
write!(f, #format_str, #(#captures = #captures,)*)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[::axum::async_trait]
|
||||||
|
#[automatically_derived]
|
||||||
|
impl<B> ::axum::extract::FromRequest<B> for #ident
|
||||||
|
where
|
||||||
|
B: Send,
|
||||||
|
{
|
||||||
|
type Rejection = <::axum::extract::Path<Self> as ::axum::extract::FromRequest<B>>::Rejection;
|
||||||
|
|
||||||
|
async fn from_request(req: &mut ::axum::extract::RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||||
|
::axum::extract::Path::from_request(req).await.map(|path| path.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -159,9 +185,12 @@ fn expand_unit_fields(ident: &syn::Ident, path: LitStr) -> TokenStream {
|
|||||||
#[automatically_derived]
|
#[automatically_derived]
|
||||||
impl ::axum_extra::routing::TypedPath for #ident {
|
impl ::axum_extra::routing::TypedPath for #ident {
|
||||||
const PATH: &'static str = #path;
|
const PATH: &'static str = #path;
|
||||||
|
}
|
||||||
|
|
||||||
fn path(&self) -> ::std::borrow::Cow<'static, str> {
|
#[automatically_derived]
|
||||||
#path.into()
|
impl ::std::fmt::Display for #ident {
|
||||||
|
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||||
|
write!(f, #path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-1
@@ -60,7 +60,6 @@ impl RouteId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The router type for composing handlers and services.
|
/// The router type for composing handlers and services.
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Router<B = Body> {
|
pub struct Router<B = Body> {
|
||||||
routes: HashMap<RouteId, Endpoint<B>>,
|
routes: HashMap<RouteId, Endpoint<B>>,
|
||||||
node: Node,
|
node: Node,
|
||||||
@@ -88,6 +87,17 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<B> fmt::Debug for Router<B> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Router")
|
||||||
|
.field("routes", &self.routes)
|
||||||
|
.field("node", &self.node)
|
||||||
|
.field("fallback", &self.fallback)
|
||||||
|
.field("nested_at_root", &self.nested_at_root)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) const NEST_TAIL_PARAM: &str = "axum_nest";
|
pub(crate) const NEST_TAIL_PARAM: &str = "axum_nest";
|
||||||
const NEST_TAIL_PARAM_CAPTURE: &str = "/*axum_nest";
|
const NEST_TAIL_PARAM_CAPTURE: &str = "/*axum_nest";
|
||||||
|
|
||||||
|
|||||||
@@ -7,15 +7,17 @@
|
|||||||
// Just using this file for manual testing. Will be cleaned up before an eventual merge
|
// Just using this file for manual testing. Will be cleaned up before an eventual merge
|
||||||
|
|
||||||
use axum::{response::IntoResponse, Router};
|
use axum::{response::IntoResponse, Router};
|
||||||
use axum_extra::routing::{typed_path, RouterExt};
|
use axum_extra::routing::RouterExt;
|
||||||
use axum_macros::TypedPath;
|
use axum_macros::TypedPath;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.with(typed_path::get(users_index).post(users_create))
|
.typed_get(users_index)
|
||||||
.with(typed_path::get(users_show));
|
.typed_post(users_create)
|
||||||
|
.typed_get(users_show)
|
||||||
|
.typed_get(users_edit);
|
||||||
|
|
||||||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||||
.serve(app.into_make_service())
|
.serve(app.into_make_service())
|
||||||
@@ -33,6 +35,10 @@ struct UsersMember {
|
|||||||
id: u32,
|
id: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, TypedPath)]
|
||||||
|
#[typed_path("/users/:id/edit")]
|
||||||
|
struct UsersEdit(u32);
|
||||||
|
|
||||||
async fn users_index(_: UsersCollection) -> impl IntoResponse {
|
async fn users_index(_: UsersCollection) -> impl IntoResponse {
|
||||||
"users#index"
|
"users#index"
|
||||||
}
|
}
|
||||||
@@ -44,3 +50,7 @@ async fn users_create(_: UsersCollection, _payload: String) -> impl IntoResponse
|
|||||||
async fn users_show(UsersMember { id }: UsersMember) -> impl IntoResponse {
|
async fn users_show(UsersMember { id }: UsersMember) -> impl IntoResponse {
|
||||||
format!("users#show: {}", id)
|
format!("users#show: {}", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn users_edit(UsersEdit(id): UsersEdit) -> impl IntoResponse {
|
||||||
|
format!("users#edit: {}", id)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user