mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-19 00:00:14 +02:00
checkpoint
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
#![allow(missing_docs)]
|
||||
#![allow(missing_docs, missing_debug_implementations)]
|
||||
|
||||
use axum::extract::{FromRequest, Path};
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::borrow::Cow;
|
||||
use axum::{body::HttpBody, handler::Handler, routing, Router};
|
||||
use std::{borrow::Cow, marker::PhantomData};
|
||||
|
||||
use super::HasRoutes;
|
||||
|
||||
/// ```rust
|
||||
/// use axum_macros::TypedPath;
|
||||
@@ -13,13 +14,51 @@ use std::borrow::Cow;
|
||||
/// id: u32,
|
||||
/// }
|
||||
/// ```
|
||||
pub trait TypedPath<B>: FromRequest<B> + DeserializeOwned {
|
||||
pub trait TypedPath {
|
||||
const PATH: &'static str;
|
||||
|
||||
fn path(&self) -> Cow<'static, str>;
|
||||
}
|
||||
|
||||
// pub trait FirstElementIsPath {}
|
||||
// impl<P> FirstElementIsPath for (Path<P>,) {}
|
||||
// impl<P, T1> FirstElementIsPath for (Path<P>, T1) {}
|
||||
// impl<P, T1, T2> FirstElementIsPath for (Path<P>, T1, T2) {}
|
||||
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) {}
|
||||
|
||||
@@ -11,18 +11,25 @@ pub(crate) fn expand(item_struct: ItemStruct) -> syn::Result<TokenStream> {
|
||||
..
|
||||
} = &item_struct;
|
||||
|
||||
if !generics.params.is_empty() || generics.where_clause.is_some() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
generics,
|
||||
"`#[derive(TypePath)]` doesn't support generics",
|
||||
));
|
||||
}
|
||||
|
||||
let Attrs { path } = parse_attrs(attrs)?;
|
||||
|
||||
match fields {
|
||||
syn::Fields::Named(_) => {
|
||||
let segments = parse_path(&path);
|
||||
Ok(expand_named_fields(ident, generics, path, &segments))
|
||||
Ok(expand_named_fields(ident, path, &segments))
|
||||
}
|
||||
syn::Fields::Unnamed(fields) => {
|
||||
let segments = parse_path(&path);
|
||||
expand_unnamed_fields(fields, ident, generics, path, &segments)
|
||||
expand_unnamed_fields(fields, ident, path, &segments)
|
||||
}
|
||||
syn::Fields::Unit => Ok(expand_unit_fields(ident, generics, path)),
|
||||
syn::Fields::Unit => Ok(expand_unit_fields(ident, path)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,22 +57,13 @@ fn parse_attrs(attrs: &[syn::Attribute]) -> syn::Result<Attrs> {
|
||||
})
|
||||
}
|
||||
|
||||
fn expand_named_fields(
|
||||
ident: &syn::Ident,
|
||||
generics: &syn::Generics,
|
||||
path: LitStr,
|
||||
segments: &[Segment],
|
||||
) -> TokenStream {
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
|
||||
fn expand_named_fields(ident: &syn::Ident, path: LitStr, segments: &[Segment]) -> TokenStream {
|
||||
let format_str = format_str_from_path(segments);
|
||||
let captures = captures_from_path(segments);
|
||||
|
||||
quote_spanned! {path.span()=>
|
||||
#[automatically_derived]
|
||||
impl #impl_generics ::axum_extra::routing::TypedPath for #ident
|
||||
#ty_generics #where_clause
|
||||
{
|
||||
impl ::axum_extra::routing::TypedPath for #ident {
|
||||
const PATH: &'static str = #path;
|
||||
|
||||
fn path(&self) -> ::std::borrow::Cow<'static, str> {
|
||||
@@ -73,18 +71,28 @@ fn expand_named_fields(
|
||||
format!(#format_str, #(#captures = #captures,)*).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[::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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_unnamed_fields(
|
||||
fields: &syn::FieldsUnnamed,
|
||||
ident: &syn::Ident,
|
||||
generics: &syn::Generics,
|
||||
path: LitStr,
|
||||
segments: &[Segment],
|
||||
) -> syn::Result<TokenStream> {
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
|
||||
let num_captures = segments
|
||||
.iter()
|
||||
.filter(|segment| match segment {
|
||||
@@ -127,9 +135,7 @@ fn expand_unnamed_fields(
|
||||
|
||||
Ok(quote_spanned! {path.span()=>
|
||||
#[automatically_derived]
|
||||
impl #impl_generics ::axum_extra::routing::TypedPath for #ident
|
||||
#ty_generics #where_clause
|
||||
{
|
||||
impl ::axum_extra::routing::TypedPath for #ident {
|
||||
const PATH: &'static str = #path;
|
||||
|
||||
fn path(&self) -> ::std::borrow::Cow<'static, str> {
|
||||
@@ -148,20 +154,33 @@ fn simple_pluralize(count: usize, word: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_unit_fields(ident: &syn::Ident, generics: &syn::Generics, path: LitStr) -> TokenStream {
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
|
||||
fn expand_unit_fields(ident: &syn::Ident, path: LitStr) -> TokenStream {
|
||||
quote_spanned! {path.span()=>
|
||||
#[automatically_derived]
|
||||
impl #impl_generics ::axum_extra::routing::TypedPath for #ident
|
||||
#ty_generics #where_clause
|
||||
{
|
||||
impl ::axum_extra::routing::TypedPath for #ident {
|
||||
const PATH: &'static str = #path;
|
||||
|
||||
fn path(&self) -> ::std::borrow::Cow<'static, str> {
|
||||
#path.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[::axum::async_trait]
|
||||
#[automatically_derived]
|
||||
impl<B> ::axum::extract::FromRequest<B> for #ident
|
||||
where
|
||||
B: Send,
|
||||
{
|
||||
type Rejection = ::axum::http::StatusCode;
|
||||
|
||||
async fn from_request(req: &mut ::axum::extract::RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
if req.uri().path() == <Self as ::axum_extra::routing::TypedPath>::PATH {
|
||||
Ok(Self)
|
||||
} else {
|
||||
Err(::axum::http::StatusCode::NOT_FOUND)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,9 +199,7 @@ fn captures_from_path(segments: &[Segment]) -> Vec<syn::Ident> {
|
||||
segments
|
||||
.iter()
|
||||
.filter_map(|segment| match segment {
|
||||
Segment::Capture(capture, span) => {
|
||||
Some(format_ident!("{}", capture, span = span.clone()))
|
||||
}
|
||||
Segment::Capture(capture, span) => Some(format_ident!("{}", capture, span = *span)),
|
||||
Segment::Static(_) => None,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
|
||||
@@ -4,40 +4,43 @@
|
||||
//! cargo run -p example-hello-world
|
||||
//! ```
|
||||
|
||||
use axum::{extract::Path, routing::get, Router};
|
||||
use axum_extra::routing::TypedPath;
|
||||
// Just using this file for manual testing. Will be cleaned up before an eventual merge
|
||||
|
||||
use axum::{response::IntoResponse, Router};
|
||||
use axum_extra::routing::{typed_path, RouterExt};
|
||||
use axum_macros::TypedPath;
|
||||
use serde::Deserialize;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
.route(UsersIndex::PATH, get(|_: Path<UsersIndex>| async {}))
|
||||
.route(UsersShow::PATH, get(|_: Path<UsersShow>| async {}))
|
||||
.route(UsersEdit::PATH, get(|_: Path<UsersEdit>| async {}));
|
||||
.with(typed_path::get(users_index).post(users_create))
|
||||
.with(typed_path::get(users_show));
|
||||
|
||||
// run it
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||
println!("listening on {}", addr);
|
||||
axum::Server::bind(&addr)
|
||||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
.serve(app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[derive(Deserialize, TypedPath)]
|
||||
#[derive(TypedPath)]
|
||||
#[typed_path("/users")]
|
||||
struct UsersIndex;
|
||||
struct UsersCollection;
|
||||
|
||||
// #[derive(Deserialize, TypedPath)]
|
||||
// #[typed_path("/users/:id/teams/:team_id")]
|
||||
// struct UsersShow {
|
||||
// id: u32,
|
||||
// team_id: u32,
|
||||
// }
|
||||
#[derive(Deserialize, TypedPath)]
|
||||
#[typed_path("/users/:id")]
|
||||
struct UsersMember {
|
||||
id: u32,
|
||||
}
|
||||
|
||||
// #[derive(Deserialize, TypedPath)]
|
||||
// #[typed_path("/users/:id/edit")]
|
||||
// struct UsersEdit(u32);
|
||||
async fn users_index(_: UsersCollection) -> impl IntoResponse {
|
||||
"users#index"
|
||||
}
|
||||
|
||||
async fn users_create(_: UsersCollection, _payload: String) -> impl IntoResponse {
|
||||
"users#create"
|
||||
}
|
||||
|
||||
async fn users_show(UsersMember { id }: UsersMember) -> impl IntoResponse {
|
||||
format!("users#show: {}", id)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user