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:
David Pedersen
2022-08-22 12:23:20 +02:00
committed by GitHub
co-authored by Jonas Platte
parent f1769e5134
commit be624306f4
104 changed files with 1513 additions and 1936 deletions
+56 -56
View File
@@ -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:: });
}