mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-24 00:00:16 +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
@@ -1,14 +1,11 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use std::collections::HashSet;
|
||||
use syn::{parse::Parse, spanned::Spanned, FnArg, ItemFn, Token, Type};
|
||||
|
||||
pub(crate) fn expand(mut attr: Attrs, item_fn: ItemFn) -> TokenStream {
|
||||
let check_extractor_count = check_extractor_count(&item_fn);
|
||||
let check_request_last_extractor = check_request_last_extractor(&item_fn);
|
||||
let check_path_extractor = check_path_extractor(&item_fn);
|
||||
let check_multiple_body_extractors = check_multiple_body_extractors(&item_fn);
|
||||
let check_output_impls_into_response = check_output_impls_into_response(&item_fn);
|
||||
|
||||
// If the function is generic, we can't reliably check its inputs or whether the future it
|
||||
@@ -39,9 +36,7 @@ pub(crate) fn expand(mut attr: Attrs, item_fn: ItemFn) -> TokenStream {
|
||||
quote! {
|
||||
#item_fn
|
||||
#check_extractor_count
|
||||
#check_request_last_extractor
|
||||
#check_path_extractor
|
||||
#check_multiple_body_extractors
|
||||
#check_output_impls_into_response
|
||||
#check_inputs_and_future_send
|
||||
}
|
||||
@@ -135,22 +130,6 @@ fn extractor_idents(item_fn: &ItemFn) -> impl Iterator<Item = (usize, &syn::FnAr
|
||||
})
|
||||
}
|
||||
|
||||
fn check_request_last_extractor(item_fn: &ItemFn) -> Option<TokenStream> {
|
||||
let request_extractor_ident =
|
||||
extractor_idents(item_fn).find(|(_, _, ident)| *ident == "Request");
|
||||
|
||||
if let Some((idx, fn_arg, _)) = request_extractor_ident {
|
||||
if idx != item_fn.sig.inputs.len() - 1 {
|
||||
return Some(
|
||||
syn::Error::new_spanned(fn_arg, "`Request` extractor should always be last")
|
||||
.to_compile_error(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn check_path_extractor(item_fn: &ItemFn) -> TokenStream {
|
||||
let path_extractors = extractor_idents(item_fn)
|
||||
.filter(|(_, _, ident)| *ident == "Path")
|
||||
@@ -174,30 +153,14 @@ fn check_path_extractor(item_fn: &ItemFn) -> TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_multiple_body_extractors(item_fn: &ItemFn) -> TokenStream {
|
||||
let body_extractors = extractor_idents(item_fn)
|
||||
.filter(|(_, _, ident)| {
|
||||
*ident == "String"
|
||||
|| *ident == "Bytes"
|
||||
|| *ident == "Json"
|
||||
|| *ident == "RawBody"
|
||||
|| *ident == "BodyStream"
|
||||
|| *ident == "Multipart"
|
||||
|| *ident == "Request"
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if body_extractors.len() > 1 {
|
||||
body_extractors
|
||||
.into_iter()
|
||||
.map(|(_, arg, _)| {
|
||||
syn::Error::new_spanned(arg, "Only one body extractor can be applied")
|
||||
.to_compile_error()
|
||||
})
|
||||
.collect()
|
||||
fn is_self_pat_type(typed: &syn::PatType) -> bool {
|
||||
let ident = if let syn::Pat::Ident(ident) = &*typed.pat {
|
||||
&ident.ident
|
||||
} else {
|
||||
quote! {}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
ident == "self"
|
||||
}
|
||||
|
||||
fn check_inputs_impls_from_request(
|
||||
@@ -205,6 +168,11 @@ fn check_inputs_impls_from_request(
|
||||
body_ty: &Type,
|
||||
state_ty: Type,
|
||||
) -> TokenStream {
|
||||
let takes_self = item_fn.sig.inputs.first().map_or(false, |arg| match arg {
|
||||
FnArg::Receiver(_) => true,
|
||||
FnArg::Typed(typed) => is_self_pat_type(typed),
|
||||
});
|
||||
|
||||
item_fn
|
||||
.sig
|
||||
.inputs
|
||||
@@ -227,21 +195,53 @@ fn check_inputs_impls_from_request(
|
||||
FnArg::Typed(typed) => {
|
||||
let ty = &typed.ty;
|
||||
let span = ty.span();
|
||||
(span, ty.clone())
|
||||
|
||||
if is_self_pat_type(typed) {
|
||||
(span, syn::parse_quote!(Self))
|
||||
} else {
|
||||
(span, ty.clone())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let name = format_ident!(
|
||||
"__axum_macros_check_{}_{}_from_request",
|
||||
let check_fn = format_ident!(
|
||||
"__axum_macros_check_{}_{}_from_request_check",
|
||||
item_fn.sig.ident,
|
||||
idx
|
||||
idx,
|
||||
span = span,
|
||||
);
|
||||
|
||||
let call_check_fn = format_ident!(
|
||||
"__axum_macros_check_{}_{}_from_request_call_check",
|
||||
item_fn.sig.ident,
|
||||
idx,
|
||||
span = span,
|
||||
);
|
||||
|
||||
let call_check_fn_body = if takes_self {
|
||||
quote_spanned! {span=>
|
||||
Self::#check_fn();
|
||||
}
|
||||
} else {
|
||||
quote_spanned! {span=>
|
||||
#check_fn();
|
||||
}
|
||||
};
|
||||
|
||||
quote_spanned! {span=>
|
||||
#[allow(warnings)]
|
||||
fn #name()
|
||||
fn #check_fn<M>()
|
||||
where
|
||||
#ty: ::axum::extract::FromRequest<#state_ty, #body_ty> + Send,
|
||||
#ty: ::axum::extract::FromRequest<#state_ty, #body_ty, M> + Send,
|
||||
{}
|
||||
|
||||
// we have to call the function to actually trigger a compile error
|
||||
// since the function is generic, just defining it is not enough
|
||||
#[allow(warnings)]
|
||||
fn #call_check_fn()
|
||||
{
|
||||
#call_check_fn_body
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>()
|
||||
@@ -380,11 +380,11 @@ fn check_future_send(item_fn: &ItemFn) -> TokenStream {
|
||||
}
|
||||
|
||||
fn self_receiver(item_fn: &ItemFn) -> Option<TokenStream> {
|
||||
let takes_self = item_fn
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.any(|arg| matches!(arg, syn::FnArg::Receiver(_)));
|
||||
let takes_self = item_fn.sig.inputs.iter().any(|arg| match arg {
|
||||
FnArg::Receiver(_) => true,
|
||||
FnArg::Typed(typed) => is_self_pat_type(typed),
|
||||
});
|
||||
|
||||
if takes_self {
|
||||
return Some(quote! { Self:: });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user