mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-29 00:00:18 +02:00
Only allow last extractor to mutate the request (#1272)
* Only allow last extractor to mutate the request * Change `FromRequest` and add `FromRequestParts` trait (#1275) * Add `Once`/`Mut` type parameter for `FromRequest` and `RequestParts` * 🪄 * split traits * `FromRequest` for tuples * Remove `BodyAlreadyExtracted` * don't need fully qualified path * don't export `Once` and `Mut` * remove temp tests * depend on axum again Co-authored-by: Jonas Platte <[email protected]> * Port `Handler` and most extractors (#1277) * Port `Handler` and most extractors * Put `M` inside `Handler` impls, not trait itself * comment out tuples for now * fix lints * Reorder arguments to `Handler` (#1281) I think `Request<B>, Arc<S>` is better since its consistent with `FromRequest` and `FromRequestParts`. * Port most things in axum-extra (#1282) * Port `#[derive(TypedPath)]` and `#[debug_handler]` (#1283) * port #[derive(TypedPath)] * wip: #[debug_handler] * fix #[debug_handler] * don't need itertools * also require `Send` * update expected error * support fully qualified `self` * Implement FromRequest[Parts] for tuples (#1286) * Port docs for axum and axum-core (#1285) * Port axum-extra (#1287) * Port axum-extra * Update axum-core/Cargo.toml Co-authored-by: Jonas Platte <[email protected]> * remove `impl FromRequest for Either*` Co-authored-by: Jonas Platte <[email protected]> * New FromRequest[Parts] trait cleanup (#1288) * Make private module truly private again * Simplify tuple FromRequest implementation * Port `#[derive(FromRequest)]` (#1289) * fix tests * fix docs * revert examples * fix docs link * fix intra docs links * Port examples (#1291) * Document wrapping other extractors (#1292) * axum-extra doesn't need to depend on axum-core (#1294) Missed this in https://github.com/tokio-rs/axum/pull/1287 * Add `FromRequest` changes to changelogs (#1293) * Update changelog * Remove default type for `S` in `Handler` * Clarify which types have default types for `S` * Apply suggestions from code review Co-authored-by: Jonas Platte <[email protected]> Co-authored-by: Jonas Platte <[email protected]> * remove unused import * Rename `Mut` and `Once` (#1296) * fix trybuild expected output Co-authored-by: Jonas Platte <[email protected]>
This commit is contained in:
co-authored by
Jonas Platte
parent
f1769e5134
commit
be624306f4
+104
-283
@@ -1,10 +1,8 @@
|
||||
use self::attr::{
|
||||
parse_container_attrs, parse_field_attrs, FromRequestContainerAttr, FromRequestFieldAttr,
|
||||
RejectionDeriveOptOuts,
|
||||
};
|
||||
use heck::ToUpperCamelCase;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::{punctuated::Punctuated, spanned::Spanned, Ident, Token};
|
||||
|
||||
mod attr;
|
||||
@@ -18,7 +16,7 @@ pub(crate) fn expand(item: syn::Item) -> syn::Result<TokenStream> {
|
||||
generics,
|
||||
fields,
|
||||
semi_token: _,
|
||||
vis,
|
||||
vis: _,
|
||||
struct_token: _,
|
||||
} = item;
|
||||
|
||||
@@ -34,32 +32,15 @@ pub(crate) fn expand(item: syn::Item) -> syn::Result<TokenStream> {
|
||||
generic_ident,
|
||||
)
|
||||
}
|
||||
FromRequestContainerAttr::RejectionDerive(_, opt_outs) => {
|
||||
error_on_generic_ident(generic_ident)?;
|
||||
|
||||
impl_struct_by_extracting_each_field(ident, fields, vis, opt_outs, None)
|
||||
}
|
||||
FromRequestContainerAttr::Rejection(rejection) => {
|
||||
error_on_generic_ident(generic_ident)?;
|
||||
|
||||
impl_struct_by_extracting_each_field(
|
||||
ident,
|
||||
fields,
|
||||
vis,
|
||||
RejectionDeriveOptOuts::default(),
|
||||
Some(rejection),
|
||||
)
|
||||
impl_struct_by_extracting_each_field(ident, fields, Some(rejection))
|
||||
}
|
||||
FromRequestContainerAttr::None => {
|
||||
error_on_generic_ident(generic_ident)?;
|
||||
|
||||
impl_struct_by_extracting_each_field(
|
||||
ident,
|
||||
fields,
|
||||
vis,
|
||||
RejectionDeriveOptOuts::default(),
|
||||
None,
|
||||
)
|
||||
impl_struct_by_extracting_each_field(ident, fields, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,12 +69,6 @@ pub(crate) fn expand(item: syn::Item) -> syn::Result<TokenStream> {
|
||||
FromRequestContainerAttr::Via { path, rejection } => {
|
||||
impl_enum_by_extracting_all_at_once(ident, variants, path, rejection)
|
||||
}
|
||||
FromRequestContainerAttr::RejectionDerive(rejection_derive, _) => {
|
||||
Err(syn::Error::new_spanned(
|
||||
rejection_derive,
|
||||
"cannot use `rejection_derive` on enums",
|
||||
))
|
||||
}
|
||||
FromRequestContainerAttr::Rejection(rejection) => Err(syn::Error::new_spanned(
|
||||
rejection,
|
||||
"cannot use `rejection` without `via`",
|
||||
@@ -197,22 +172,16 @@ fn error_on_generic_ident(generic_ident: Option<Ident>) -> syn::Result<()> {
|
||||
fn impl_struct_by_extracting_each_field(
|
||||
ident: syn::Ident,
|
||||
fields: syn::Fields,
|
||||
vis: syn::Visibility,
|
||||
rejection_derive_opt_outs: RejectionDeriveOptOuts,
|
||||
rejection: Option<syn::Path>,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let extract_fields = extract_fields(&fields, &rejection)?;
|
||||
|
||||
let (rejection_ident, rejection) = if let Some(rejection) = rejection {
|
||||
let rejection_ident = syn::parse_quote!(#rejection);
|
||||
(rejection_ident, None)
|
||||
let rejection_ident = if let Some(rejection) = rejection {
|
||||
quote!(#rejection)
|
||||
} else if has_no_fields(&fields) {
|
||||
(syn::parse_quote!(::std::convert::Infallible), None)
|
||||
quote!(::std::convert::Infallible)
|
||||
} else {
|
||||
let rejection_ident = rejection_ident(&ident);
|
||||
let rejection =
|
||||
extract_each_field_rejection(&ident, &fields, &vis, rejection_derive_opt_outs)?;
|
||||
(rejection_ident, Some(rejection))
|
||||
quote!(::axum::response::Response)
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
@@ -228,15 +197,14 @@ fn impl_struct_by_extracting_each_field(
|
||||
type Rejection = #rejection_ident;
|
||||
|
||||
async fn from_request(
|
||||
req: &mut ::axum::extract::RequestParts<S, B>,
|
||||
mut req: axum::http::Request<B>,
|
||||
state: &S,
|
||||
) -> ::std::result::Result<Self, Self::Rejection> {
|
||||
::std::result::Result::Ok(Self {
|
||||
#(#extract_fields)*
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#rejection
|
||||
})
|
||||
}
|
||||
|
||||
@@ -248,11 +216,6 @@ fn has_no_fields(fields: &syn::Fields) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn rejection_ident(ident: &syn::Ident) -> syn::Type {
|
||||
let ident = format_ident!("{}Rejection", ident);
|
||||
syn::parse_quote!(#ident)
|
||||
}
|
||||
|
||||
fn extract_fields(
|
||||
fields: &syn::Fields,
|
||||
rejection: &Option<syn::Path>,
|
||||
@@ -261,6 +224,8 @@ fn extract_fields(
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, field)| {
|
||||
let is_last = fields.len() - 1 == index;
|
||||
|
||||
let FromRequestFieldAttr { via } = parse_field_attrs(&field.attrs)?;
|
||||
|
||||
let member = if let Some(ident) = &field.ident {
|
||||
@@ -286,40 +251,79 @@ fn extract_fields(
|
||||
}
|
||||
};
|
||||
|
||||
let rejection_variant_name = rejection_variant_name(field)?;
|
||||
|
||||
if peel_option(&field.ty).is_some() {
|
||||
Ok(quote_spanned! {ty_span=>
|
||||
#member: {
|
||||
::axum::extract::FromRequest::from_request(req)
|
||||
.await
|
||||
.ok()
|
||||
.map(#into_inner)
|
||||
},
|
||||
})
|
||||
if is_last {
|
||||
Ok(quote_spanned! {ty_span=>
|
||||
#member: {
|
||||
::axum::extract::FromRequest::from_request(req, state)
|
||||
.await
|
||||
.ok()
|
||||
.map(#into_inner)
|
||||
},
|
||||
})
|
||||
} else {
|
||||
Ok(quote_spanned! {ty_span=>
|
||||
#member: {
|
||||
let (mut parts, body) = req.into_parts();
|
||||
let value = ::axum::extract::FromRequestParts::from_request_parts(&mut parts, state)
|
||||
.await
|
||||
.ok()
|
||||
.map(#into_inner);
|
||||
req = ::axum::http::Request::from_parts(parts, body);
|
||||
value
|
||||
},
|
||||
})
|
||||
}
|
||||
} else if peel_result_ok(&field.ty).is_some() {
|
||||
Ok(quote_spanned! {ty_span=>
|
||||
#member: {
|
||||
::axum::extract::FromRequest::from_request(req)
|
||||
.await
|
||||
.map(#into_inner)
|
||||
},
|
||||
})
|
||||
if is_last {
|
||||
Ok(quote_spanned! {ty_span=>
|
||||
#member: {
|
||||
::axum::extract::FromRequest::from_request(req, state)
|
||||
.await
|
||||
.map(#into_inner)
|
||||
},
|
||||
})
|
||||
} else {
|
||||
Ok(quote_spanned! {ty_span=>
|
||||
#member: {
|
||||
let (mut parts, body) = req.into_parts();
|
||||
let value = ::axum::extract::FromRequestParts::from_request_parts(&mut parts, state)
|
||||
.await
|
||||
.map(#into_inner);
|
||||
req = ::axum::http::Request::from_parts(parts, body);
|
||||
value
|
||||
},
|
||||
})
|
||||
}
|
||||
} else {
|
||||
let map_err = if let Some(rejection) = rejection {
|
||||
quote! { <#rejection as ::std::convert::From<_>>::from }
|
||||
} else {
|
||||
quote! { Self::Rejection::#rejection_variant_name }
|
||||
quote! { ::axum::response::IntoResponse::into_response }
|
||||
};
|
||||
|
||||
Ok(quote_spanned! {ty_span=>
|
||||
#member: {
|
||||
::axum::extract::FromRequest::from_request(req)
|
||||
.await
|
||||
.map(#into_inner)
|
||||
.map_err(#map_err)?
|
||||
},
|
||||
})
|
||||
if is_last {
|
||||
Ok(quote_spanned! {ty_span=>
|
||||
#member: {
|
||||
::axum::extract::FromRequest::from_request(req, state)
|
||||
.await
|
||||
.map(#into_inner)
|
||||
.map_err(#map_err)?
|
||||
},
|
||||
})
|
||||
} else {
|
||||
Ok(quote_spanned! {ty_span=>
|
||||
#member: {
|
||||
let (mut parts, body) = req.into_parts();
|
||||
let value = ::axum::extract::FromRequestParts::from_request_parts(&mut parts, state)
|
||||
.await
|
||||
.map(#into_inner)
|
||||
.map_err(#map_err)?;
|
||||
req = ::axum::http::Request::from_parts(parts, body);
|
||||
value
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
@@ -387,199 +391,6 @@ fn peel_result_ok(ty: &syn::Type) -> Option<&syn::Type> {
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_each_field_rejection(
|
||||
ident: &syn::Ident,
|
||||
fields: &syn::Fields,
|
||||
vis: &syn::Visibility,
|
||||
rejection_derive_opt_outs: RejectionDeriveOptOuts,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let rejection_ident = rejection_ident(ident);
|
||||
|
||||
let variants = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let FromRequestFieldAttr { via } = parse_field_attrs(&field.attrs)?;
|
||||
|
||||
let field_ty = &field.ty;
|
||||
let ty_span = field_ty.span();
|
||||
|
||||
let variant_name = rejection_variant_name(field)?;
|
||||
|
||||
let extractor_ty = if let Some((_, path)) = via {
|
||||
if let Some(inner) = peel_option(field_ty) {
|
||||
quote_spanned! {ty_span=>
|
||||
::std::option::Option<#path<#inner>>
|
||||
}
|
||||
} else if let Some(inner) = peel_result_ok(field_ty) {
|
||||
quote_spanned! {ty_span=>
|
||||
::std::result::Result<#path<#inner>, TypedHeaderRejection>
|
||||
}
|
||||
} else {
|
||||
quote_spanned! {ty_span=> #path<#field_ty> }
|
||||
}
|
||||
} else {
|
||||
quote_spanned! {ty_span=> #field_ty }
|
||||
};
|
||||
|
||||
Ok(quote_spanned! {ty_span=>
|
||||
#[allow(non_camel_case_types)]
|
||||
#variant_name(<#extractor_ty as ::axum::extract::FromRequest<(), ::axum::body::Body>>::Rejection),
|
||||
})
|
||||
})
|
||||
.collect::<syn::Result<Vec<_>>>()?;
|
||||
|
||||
let impl_into_response = {
|
||||
let arms = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let variant_name = rejection_variant_name(field)?;
|
||||
Ok(quote! {
|
||||
Self::#variant_name(inner) => inner.into_response(),
|
||||
})
|
||||
})
|
||||
.collect::<syn::Result<Vec<_>>>()?;
|
||||
|
||||
quote! {
|
||||
#[automatically_derived]
|
||||
impl ::axum::response::IntoResponse for #rejection_ident {
|
||||
fn into_response(self) -> ::axum::response::Response {
|
||||
match self {
|
||||
#(#arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let impl_display = if rejection_derive_opt_outs.derive_display() {
|
||||
let arms = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let variant_name = rejection_variant_name(field)?;
|
||||
Ok(quote! {
|
||||
Self::#variant_name(inner) => inner.fmt(f),
|
||||
})
|
||||
})
|
||||
.collect::<syn::Result<Vec<_>>>()?;
|
||||
|
||||
Some(quote! {
|
||||
#[automatically_derived]
|
||||
impl ::std::fmt::Display for #rejection_ident {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
match self {
|
||||
#(#arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let impl_error = if rejection_derive_opt_outs.derive_error() {
|
||||
let arms = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let variant_name = rejection_variant_name(field)?;
|
||||
Ok(quote! {
|
||||
Self::#variant_name(inner) => Some(inner),
|
||||
})
|
||||
})
|
||||
.collect::<syn::Result<Vec<_>>>()?;
|
||||
|
||||
Some(quote! {
|
||||
#[automatically_derived]
|
||||
impl ::std::error::Error for #rejection_ident {
|
||||
fn source(&self) -> ::std::option::Option<&(dyn ::std::error::Error + 'static)> {
|
||||
match self {
|
||||
#(#arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let impl_debug = rejection_derive_opt_outs.derive_debug().then(|| {
|
||||
quote! { #[derive(Debug)] }
|
||||
});
|
||||
|
||||
Ok(quote! {
|
||||
#impl_debug
|
||||
#vis enum #rejection_ident {
|
||||
#(#variants)*
|
||||
}
|
||||
|
||||
#impl_into_response
|
||||
#impl_display
|
||||
#impl_error
|
||||
})
|
||||
}
|
||||
|
||||
fn rejection_variant_name(field: &syn::Field) -> syn::Result<syn::Ident> {
|
||||
fn rejection_variant_name_for_type(out: &mut String, ty: &syn::Type) -> syn::Result<()> {
|
||||
if let syn::Type::Path(type_path) = ty {
|
||||
let segment = type_path
|
||||
.path
|
||||
.segments
|
||||
.last()
|
||||
.ok_or_else(|| syn::Error::new_spanned(ty, "Empty type path"))?;
|
||||
|
||||
out.push_str(&segment.ident.to_string());
|
||||
|
||||
match &segment.arguments {
|
||||
syn::PathArguments::AngleBracketed(args) => {
|
||||
let ty = if args.args.len() == 1 {
|
||||
args.args.last().unwrap()
|
||||
} else if args.args.len() == 2 {
|
||||
if segment.ident == "Result" {
|
||||
args.args.first().unwrap()
|
||||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
segment,
|
||||
"Only `Result<T, E>` is supported with two generics type paramters",
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
&args.args,
|
||||
"Expected exactly one or two type paramters",
|
||||
));
|
||||
};
|
||||
|
||||
if let syn::GenericArgument::Type(ty) = ty {
|
||||
rejection_variant_name_for_type(out, ty)
|
||||
} else {
|
||||
Err(syn::Error::new_spanned(ty, "Expected type path"))
|
||||
}
|
||||
}
|
||||
syn::PathArguments::Parenthesized(args) => {
|
||||
Err(syn::Error::new_spanned(args, "Unsupported"))
|
||||
}
|
||||
syn::PathArguments::None => Ok(()),
|
||||
}
|
||||
} else {
|
||||
Err(syn::Error::new_spanned(ty, "Expected type path"))
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ident) = &field.ident {
|
||||
Ok(format_ident!("{}", ident.to_string().to_upper_camel_case()))
|
||||
} else {
|
||||
let mut out = String::new();
|
||||
rejection_variant_name_for_type(&mut out, &field.ty)?;
|
||||
|
||||
let FromRequestFieldAttr { via } = parse_field_attrs(&field.attrs)?;
|
||||
if let Some((_, path)) = via {
|
||||
let via_ident = &path.segments.last().unwrap().ident;
|
||||
Ok(format_ident!("{}{}", via_ident, out))
|
||||
} else {
|
||||
Ok(format_ident!("{}", out))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_struct_by_extracting_all_at_once(
|
||||
ident: syn::Ident,
|
||||
fields: syn::Fields,
|
||||
@@ -606,12 +417,16 @@ fn impl_struct_by_extracting_all_at_once(
|
||||
|
||||
let path_span = path.span();
|
||||
|
||||
let associated_rejection_type = if let Some(rejection) = &rejection {
|
||||
quote! { #rejection }
|
||||
let (associated_rejection_type, map_err) = if let Some(rejection) = &rejection {
|
||||
let rejection = quote! { #rejection };
|
||||
let map_err = quote! { ::std::convert::From::from };
|
||||
(rejection, map_err)
|
||||
} else {
|
||||
quote! {
|
||||
<#path<Self> as ::axum::extract::FromRequest<S, B>>::Rejection
|
||||
}
|
||||
let rejection = quote! {
|
||||
::axum::response::Response
|
||||
};
|
||||
let map_err = quote! { ::axum::response::IntoResponse::into_response };
|
||||
(rejection, map_err)
|
||||
};
|
||||
|
||||
let rejection_bound = rejection.as_ref().map(|rejection| {
|
||||
@@ -658,18 +473,19 @@ fn impl_struct_by_extracting_all_at_once(
|
||||
where
|
||||
#path<#via_type_generics>: ::axum::extract::FromRequest<S, B>,
|
||||
#rejection_bound
|
||||
B: ::std::marker::Send,
|
||||
B: ::std::marker::Send + 'static,
|
||||
S: ::std::marker::Send + ::std::marker::Sync,
|
||||
{
|
||||
type Rejection = #associated_rejection_type;
|
||||
|
||||
async fn from_request(
|
||||
req: &mut ::axum::extract::RequestParts<S, B>,
|
||||
req: ::axum::http::Request<B>,
|
||||
state: &S
|
||||
) -> ::std::result::Result<Self, Self::Rejection> {
|
||||
::axum::extract::FromRequest::<S, B>::from_request(req)
|
||||
::axum::extract::FromRequest::from_request(req, state)
|
||||
.await
|
||||
.map(|#path(value)| #value_to_self)
|
||||
.map_err(::std::convert::From::from)
|
||||
.map_err(#map_err)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -707,12 +523,16 @@ fn impl_enum_by_extracting_all_at_once(
|
||||
}
|
||||
}
|
||||
|
||||
let associated_rejection_type = if let Some(rejection) = rejection {
|
||||
quote! { #rejection }
|
||||
let (associated_rejection_type, map_err) = if let Some(rejection) = &rejection {
|
||||
let rejection = quote! { #rejection };
|
||||
let map_err = quote! { ::std::convert::From::from };
|
||||
(rejection, map_err)
|
||||
} else {
|
||||
quote! {
|
||||
<#path<Self> as ::axum::extract::FromRequest<S, B>>::Rejection
|
||||
}
|
||||
let rejection = quote! {
|
||||
::axum::response::Response
|
||||
};
|
||||
let map_err = quote! { ::axum::response::IntoResponse::into_response };
|
||||
(rejection, map_err)
|
||||
};
|
||||
|
||||
let path_span = path.span();
|
||||
@@ -730,12 +550,13 @@ fn impl_enum_by_extracting_all_at_once(
|
||||
type Rejection = #associated_rejection_type;
|
||||
|
||||
async fn from_request(
|
||||
req: &mut ::axum::extract::RequestParts<S, B>,
|
||||
req: ::axum::http::Request<B>,
|
||||
state: &S
|
||||
) -> ::std::result::Result<Self, Self::Rejection> {
|
||||
::axum::extract::FromRequest::<S, B>::from_request(req)
|
||||
::axum::extract::FromRequest::from_request(req, state)
|
||||
.await
|
||||
.map(|#path(inner)| inner)
|
||||
.map_err(::std::convert::From::from)
|
||||
.map_err(#map_err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user