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
@@ -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:: });
|
||||
}
|
||||
|
||||
+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)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -16,13 +16,11 @@ pub(crate) enum FromRequestContainerAttr {
|
||||
rejection: Option<syn::Path>,
|
||||
},
|
||||
Rejection(syn::Path),
|
||||
RejectionDerive(kw::rejection_derive, RejectionDeriveOptOuts),
|
||||
None,
|
||||
}
|
||||
|
||||
pub(crate) mod kw {
|
||||
syn::custom_keyword!(via);
|
||||
syn::custom_keyword!(rejection_derive);
|
||||
syn::custom_keyword!(rejection);
|
||||
syn::custom_keyword!(Display);
|
||||
syn::custom_keyword!(Debug);
|
||||
@@ -55,7 +53,6 @@ pub(crate) fn parse_container_attrs(
|
||||
let attrs = parse_attrs::<ContainerAttr>(attrs)?;
|
||||
|
||||
let mut out_via = None;
|
||||
let mut out_rejection_derive = None;
|
||||
let mut out_rejection = None;
|
||||
|
||||
// we track the index of the attribute to know which comes last
|
||||
@@ -69,16 +66,6 @@ pub(crate) fn parse_container_attrs(
|
||||
out_via = Some((idx, via, path));
|
||||
}
|
||||
}
|
||||
ContainerAttr::RejectionDerive {
|
||||
rejection_derive,
|
||||
opt_outs,
|
||||
} => {
|
||||
if out_rejection_derive.is_some() {
|
||||
return Err(double_attr_error("rejection_derive", rejection_derive));
|
||||
} else {
|
||||
out_rejection_derive = Some((idx, rejection_derive, opt_outs));
|
||||
}
|
||||
}
|
||||
ContainerAttr::Rejection { rejection, path } => {
|
||||
if out_rejection.is_some() {
|
||||
return Err(double_attr_error("rejection", rejection));
|
||||
@@ -89,55 +76,20 @@ pub(crate) fn parse_container_attrs(
|
||||
}
|
||||
}
|
||||
|
||||
match (out_via, out_rejection_derive, out_rejection) {
|
||||
(Some((via_idx, via, _)), Some((rejection_derive_idx, rejection_derive, _)), _) => {
|
||||
if via_idx > rejection_derive_idx {
|
||||
Err(syn::Error::new_spanned(
|
||||
via,
|
||||
"cannot use both `rejection_derive` and `via`",
|
||||
))
|
||||
} else {
|
||||
Err(syn::Error::new_spanned(
|
||||
rejection_derive,
|
||||
"cannot use both `via` and `rejection_derive`",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
_,
|
||||
Some((rejection_derive_idx, rejection_derive, _)),
|
||||
Some((rejection_idx, rejection, _)),
|
||||
) => {
|
||||
if rejection_idx > rejection_derive_idx {
|
||||
Err(syn::Error::new_spanned(
|
||||
rejection,
|
||||
"cannot use both `rejection_derive` and `rejection`",
|
||||
))
|
||||
} else {
|
||||
Err(syn::Error::new_spanned(
|
||||
rejection_derive,
|
||||
"cannot use both `rejection` and `rejection_derive`",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
(Some((_, _, path)), None, None) => Ok(FromRequestContainerAttr::Via {
|
||||
match (out_via, out_rejection) {
|
||||
(Some((_, _, path)), None) => Ok(FromRequestContainerAttr::Via {
|
||||
path,
|
||||
rejection: None,
|
||||
}),
|
||||
(Some((_, _, path)), None, Some((_, _, rejection))) => Ok(FromRequestContainerAttr::Via {
|
||||
|
||||
(Some((_, _, path)), Some((_, _, rejection))) => Ok(FromRequestContainerAttr::Via {
|
||||
path,
|
||||
rejection: Some(rejection),
|
||||
}),
|
||||
|
||||
(None, Some((_, rejection_derive, opt_outs)), _) => Ok(
|
||||
FromRequestContainerAttr::RejectionDerive(rejection_derive, opt_outs),
|
||||
),
|
||||
(None, Some((_, _, rejection))) => Ok(FromRequestContainerAttr::Rejection(rejection)),
|
||||
|
||||
(None, None, Some((_, _, rejection))) => Ok(FromRequestContainerAttr::Rejection(rejection)),
|
||||
|
||||
(None, None, None) => Ok(FromRequestContainerAttr::None),
|
||||
(None, None) => Ok(FromRequestContainerAttr::None),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,10 +124,6 @@ enum ContainerAttr {
|
||||
rejection: kw::rejection,
|
||||
path: syn::Path,
|
||||
},
|
||||
RejectionDerive {
|
||||
rejection_derive: kw::rejection_derive,
|
||||
opt_outs: RejectionDeriveOptOuts,
|
||||
},
|
||||
}
|
||||
|
||||
impl Parse for ContainerAttr {
|
||||
@@ -186,14 +134,6 @@ impl Parse for ContainerAttr {
|
||||
let content;
|
||||
syn::parenthesized!(content in input);
|
||||
content.parse().map(|path| Self::Via { via, path })
|
||||
} else if lh.peek(kw::rejection_derive) {
|
||||
let rejection_derive = input.parse::<kw::rejection_derive>()?;
|
||||
let content;
|
||||
syn::parenthesized!(content in input);
|
||||
content.parse().map(|opt_outs| Self::RejectionDerive {
|
||||
rejection_derive,
|
||||
opt_outs,
|
||||
})
|
||||
} else if lh.peek(kw::rejection) {
|
||||
let rejection = input.parse::<kw::rejection>()?;
|
||||
let content;
|
||||
@@ -224,82 +164,3 @@ impl Parse for FieldAttr {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct RejectionDeriveOptOuts {
|
||||
debug: Option<kw::Debug>,
|
||||
display: Option<kw::Display>,
|
||||
error: Option<kw::Error>,
|
||||
}
|
||||
|
||||
impl RejectionDeriveOptOuts {
|
||||
pub(crate) fn derive_debug(&self) -> bool {
|
||||
self.debug.is_none()
|
||||
}
|
||||
|
||||
pub(crate) fn derive_display(&self) -> bool {
|
||||
self.display.is_none()
|
||||
}
|
||||
|
||||
pub(crate) fn derive_error(&self) -> bool {
|
||||
self.error.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for RejectionDeriveOptOuts {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
fn parse_opt_out<T>(out: &mut Option<T>, ident: &str, input: ParseStream) -> syn::Result<()>
|
||||
where
|
||||
T: Parse,
|
||||
{
|
||||
if out.is_some() {
|
||||
Err(input.error(format!("`{}` opt out specified more than once", ident)))
|
||||
} else {
|
||||
*out = Some(input.parse()?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let mut debug = None::<kw::Debug>;
|
||||
let mut display = None::<kw::Display>;
|
||||
let mut error = None::<kw::Error>;
|
||||
|
||||
while !input.is_empty() {
|
||||
input.parse::<Token![!]>()?;
|
||||
|
||||
let lh = input.lookahead1();
|
||||
if lh.peek(kw::Debug) {
|
||||
parse_opt_out(&mut debug, "Debug", input)?;
|
||||
} else if lh.peek(kw::Display) {
|
||||
parse_opt_out(&mut display, "Display", input)?;
|
||||
} else if lh.peek(kw::Error) {
|
||||
parse_opt_out(&mut error, "Error", input)?;
|
||||
} else {
|
||||
return Err(lh.error());
|
||||
}
|
||||
|
||||
input.parse::<Token![,]>().ok();
|
||||
}
|
||||
|
||||
if error.is_none() {
|
||||
match (debug, display) {
|
||||
(Some(debug), Some(_)) => {
|
||||
return Err(syn::Error::new_spanned(debug, "opt out of `Debug` and `Display` requires also opting out of `Error`. Use `#[from_request(rejection_derive(!Debug, !Display, !Error))]`"));
|
||||
}
|
||||
(Some(debug), None) => {
|
||||
return Err(syn::Error::new_spanned(debug, "opt out of `Debug` requires also opting out of `Error`. Use `#[from_request(rejection_derive(!Debug, !Error))]`"));
|
||||
}
|
||||
(None, Some(display)) => {
|
||||
return Err(syn::Error::new_spanned(display, "opt out of `Display` requires also opting out of `Error`. Use `#[from_request(rejection_derive(!Display, !Error))]`"));
|
||||
}
|
||||
(None, None) => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
debug,
|
||||
display,
|
||||
error,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+18
-83
@@ -86,6 +86,20 @@ mod typed_path;
|
||||
///
|
||||
/// This requires that each field is an extractor (i.e. implements [`FromRequest`]).
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// use axum_macros::FromRequest;
|
||||
/// use axum::body::Bytes;
|
||||
///
|
||||
/// #[derive(FromRequest)]
|
||||
/// struct MyExtractor {
|
||||
/// // only the last field can implement `FromRequest`
|
||||
/// // other fields must only implement `FromRequestParts`
|
||||
/// bytes: Bytes,
|
||||
/// string: String,
|
||||
/// }
|
||||
/// ```
|
||||
/// Note that only the last field can consume the request body. Therefore this doesn't compile:
|
||||
///
|
||||
/// ## Extracting via another extractor
|
||||
///
|
||||
/// You can use `#[from_request(via(...))]` to extract a field via another extractor, meaning the
|
||||
@@ -157,95 +171,15 @@ mod typed_path;
|
||||
///
|
||||
/// ## The rejection
|
||||
///
|
||||
/// A rejection enum is also generated. It has a variant for each field:
|
||||
///
|
||||
/// ```
|
||||
/// use axum_macros::FromRequest;
|
||||
/// use axum::{
|
||||
/// extract::{Extension, TypedHeader},
|
||||
/// headers::ContentType,
|
||||
/// body::Bytes,
|
||||
/// };
|
||||
///
|
||||
/// #[derive(FromRequest)]
|
||||
/// struct MyExtractor {
|
||||
/// #[from_request(via(Extension))]
|
||||
/// state: State,
|
||||
/// #[from_request(via(TypedHeader))]
|
||||
/// content_type: ContentType,
|
||||
/// request_body: Bytes,
|
||||
/// }
|
||||
///
|
||||
/// // also generates
|
||||
/// //
|
||||
/// // #[derive(Debug)]
|
||||
/// // enum MyExtractorRejection {
|
||||
/// // State(ExtensionRejection),
|
||||
/// // ContentType(TypedHeaderRejection),
|
||||
/// // RequestBody(BytesRejection),
|
||||
/// // }
|
||||
/// //
|
||||
/// // impl axum::response::IntoResponse for MyExtractor { ... }
|
||||
/// //
|
||||
/// // impl std::fmt::Display for MyExtractor { ... }
|
||||
/// //
|
||||
/// // impl std::error::Error for MyExtractor { ... }
|
||||
///
|
||||
/// #[derive(Clone)]
|
||||
/// struct State {
|
||||
/// // ...
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The rejection's `std::error::Error::source` implementation returns the inner rejection. This
|
||||
/// can be used to access source errors for example to customize rejection responses. Note this
|
||||
/// means the inner rejection types must themselves implement `std::error::Error`. All extractors
|
||||
/// in axum does this.
|
||||
///
|
||||
/// You can opt out of this using `#[from_request(rejection_derive(...))]`:
|
||||
///
|
||||
/// ```
|
||||
/// use axum_macros::FromRequest;
|
||||
/// use axum::{
|
||||
/// extract::{FromRequest, RequestParts},
|
||||
/// http::StatusCode,
|
||||
/// headers::ContentType,
|
||||
/// body::Bytes,
|
||||
/// async_trait,
|
||||
/// };
|
||||
///
|
||||
/// #[derive(FromRequest)]
|
||||
/// #[from_request(rejection_derive(!Display, !Error))]
|
||||
/// struct MyExtractor {
|
||||
/// other: OtherExtractor,
|
||||
/// }
|
||||
///
|
||||
/// struct OtherExtractor;
|
||||
///
|
||||
/// #[async_trait]
|
||||
/// impl<S, B> FromRequest<S, B> for OtherExtractor
|
||||
/// where
|
||||
/// B: Send,
|
||||
/// S: Send + Sync,
|
||||
/// {
|
||||
/// // this rejection doesn't implement `Display` and `Error`
|
||||
/// type Rejection = (StatusCode, String);
|
||||
///
|
||||
/// async fn from_request(_req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
/// // ...
|
||||
/// # unimplemented!()
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// You can also use your own rejection type with `#[from_request(rejection(YourType))]`:
|
||||
/// By default [`axum::response::Response`] will be used as the rejection. You can also use your own
|
||||
/// rejection type with `#[from_request(rejection(YourType))]`:
|
||||
///
|
||||
/// ```
|
||||
/// use axum_macros::FromRequest;
|
||||
/// use axum::{
|
||||
/// extract::{
|
||||
/// rejection::{ExtensionRejection, StringRejection},
|
||||
/// FromRequest, RequestParts,
|
||||
/// FromRequest,
|
||||
/// },
|
||||
/// Extension,
|
||||
/// response::{Response, IntoResponse},
|
||||
@@ -414,6 +348,7 @@ mod typed_path;
|
||||
/// ```
|
||||
///
|
||||
/// [`FromRequest`]: https://docs.rs/axum/latest/axum/extract/trait.FromRequest.html
|
||||
/// [`axum::response::Response`]: https://docs.rs/axum/0.6/axum/response/type.Response.html
|
||||
/// [`axum::extract::rejection::ExtensionRejection`]: https://docs.rs/axum/latest/axum/extract/rejection/enum.ExtensionRejection.html
|
||||
#[proc_macro_derive(FromRequest, attributes(from_request))]
|
||||
pub fn derive_from_request(item: TokenStream) -> TokenStream {
|
||||
|
||||
@@ -127,15 +127,17 @@ fn expand_named_fields(
|
||||
let from_request_impl = quote! {
|
||||
#[::axum::async_trait]
|
||||
#[automatically_derived]
|
||||
impl<S, B> ::axum::extract::FromRequest<S, B> for #ident
|
||||
impl<S> ::axum::extract::FromRequestParts<S> for #ident
|
||||
where
|
||||
B: Send,
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = #rejection_assoc_type;
|
||||
|
||||
async fn from_request(req: &mut ::axum::extract::RequestParts<S, B>) -> ::std::result::Result<Self, Self::Rejection> {
|
||||
::axum::extract::Path::from_request(req)
|
||||
async fn from_request_parts(
|
||||
parts: &mut ::axum::http::request::Parts,
|
||||
state: &S,
|
||||
) -> ::std::result::Result<Self, Self::Rejection> {
|
||||
::axum::extract::Path::from_request_parts(parts, state)
|
||||
.await
|
||||
.map(|path| path.0)
|
||||
#map_err_rejection
|
||||
@@ -230,15 +232,17 @@ fn expand_unnamed_fields(
|
||||
let from_request_impl = quote! {
|
||||
#[::axum::async_trait]
|
||||
#[automatically_derived]
|
||||
impl<S, B> ::axum::extract::FromRequest<S, B> for #ident
|
||||
impl<S> ::axum::extract::FromRequestParts<S> for #ident
|
||||
where
|
||||
B: Send,
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = #rejection_assoc_type;
|
||||
|
||||
async fn from_request(req: &mut ::axum::extract::RequestParts<S, B>) -> ::std::result::Result<Self, Self::Rejection> {
|
||||
::axum::extract::Path::from_request(req)
|
||||
async fn from_request_parts(
|
||||
parts: &mut ::axum::http::request::Parts,
|
||||
state: &S,
|
||||
) -> ::std::result::Result<Self, Self::Rejection> {
|
||||
::axum::extract::Path::from_request_parts(parts, state)
|
||||
.await
|
||||
.map(|path| path.0)
|
||||
#map_err_rejection
|
||||
@@ -312,15 +316,17 @@ fn expand_unit_fields(
|
||||
let from_request_impl = quote! {
|
||||
#[::axum::async_trait]
|
||||
#[automatically_derived]
|
||||
impl<S, B> ::axum::extract::FromRequest<S, B> for #ident
|
||||
impl<S> ::axum::extract::FromRequestParts<S> for #ident
|
||||
where
|
||||
B: Send,
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = #rejection_assoc_type;
|
||||
|
||||
async fn from_request(req: &mut ::axum::extract::RequestParts<S, B>) -> ::std::result::Result<Self, Self::Rejection> {
|
||||
if req.uri().path() == <Self as ::axum_extra::routing::TypedPath>::PATH {
|
||||
async fn from_request_parts(
|
||||
parts: &mut ::axum::http::request::Parts,
|
||||
_state: &S,
|
||||
) -> ::std::result::Result<Self, Self::Rejection> {
|
||||
if parts.uri.path() == <Self as ::axum_extra::routing::TypedPath>::PATH {
|
||||
Ok(Self)
|
||||
} else {
|
||||
#create_rejection
|
||||
@@ -390,7 +396,7 @@ enum Segment {
|
||||
|
||||
fn path_rejection() -> TokenStream {
|
||||
quote! {
|
||||
<::axum::extract::Path<Self> as ::axum::extract::FromRequest<S, B>>::Rejection
|
||||
<::axum::extract::Path<Self> as ::axum::extract::FromRequestParts<S>>::Rejection
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user