mirror of
https://github.com/tokio-rs/axum.git
synced 2025-10-03 07:44:52 +00:00
Support opt-out of extra derived traits for rejections for #[derive(FromRequest)]
(#729)
* Handle structs without fields * Support opt-out of derived rejection traits * Handle duplicate opt outs * Improve error if opting out of `Display` or `Debug` but not `Error` * document `rejection_derive` * Handle using both `via` and `rejection_derive` * don't derive debug for `RejectionDeriveOptOuts` * Update axum-macros/src/from_request.rs Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com> Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com>
This commit is contained in:
parent
f6fc5ed80c
commit
911c4a788e
@ -1,12 +1,13 @@
|
|||||||
|
use self::attr::{
|
||||||
|
parse_container_attrs, parse_field_attrs, FromRequestContainerAttr, FromRequestFieldAttr,
|
||||||
|
RejectionDeriveOptOuts,
|
||||||
|
};
|
||||||
use heck::ToUpperCamelCase;
|
use heck::ToUpperCamelCase;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::{format_ident, quote, quote_spanned};
|
use quote::{format_ident, quote, quote_spanned};
|
||||||
use syn::{
|
use syn::{punctuated::Punctuated, spanned::Spanned, Token};
|
||||||
parse::{Parse, ParseStream},
|
|
||||||
punctuated::Punctuated,
|
mod attr;
|
||||||
spanned::Spanned,
|
|
||||||
Token,
|
|
||||||
};
|
|
||||||
|
|
||||||
const GENERICS_ERROR: &str = "`#[derive(FromRequest)] doesn't support generics";
|
const GENERICS_ERROR: &str = "`#[derive(FromRequest)] doesn't support generics";
|
||||||
|
|
||||||
@ -29,12 +30,18 @@ pub(crate) fn expand(item: syn::ItemStruct) -> syn::Result<TokenStream> {
|
|||||||
return Err(syn::Error::new_spanned(where_clause, GENERICS_ERROR));
|
return Err(syn::Error::new_spanned(where_clause, GENERICS_ERROR));
|
||||||
}
|
}
|
||||||
|
|
||||||
let FromRequestAttrs { via } = parse_attrs(&attrs)?;
|
let FromRequestContainerAttr {
|
||||||
|
via,
|
||||||
|
rejection_derive,
|
||||||
|
} = parse_container_attrs(&attrs)?;
|
||||||
|
|
||||||
if let Some((_, path)) = via {
|
if let Some((_, path)) = via {
|
||||||
impl_by_extracting_all_at_once(ident, fields, path)
|
impl_by_extracting_all_at_once(ident, fields, path)
|
||||||
} else {
|
} else {
|
||||||
impl_by_extracting_each_field(ident, fields, vis)
|
let rejection_derive_opt_outs = rejection_derive
|
||||||
|
.map(|(_, opt_outs)| opt_outs)
|
||||||
|
.unwrap_or_default();
|
||||||
|
impl_by_extracting_each_field(ident, fields, vis, rejection_derive_opt_outs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,15 +49,17 @@ fn impl_by_extracting_each_field(
|
|||||||
ident: syn::Ident,
|
ident: syn::Ident,
|
||||||
fields: syn::Fields,
|
fields: syn::Fields,
|
||||||
vis: syn::Visibility,
|
vis: syn::Visibility,
|
||||||
|
rejection_derive_opt_outs: RejectionDeriveOptOuts,
|
||||||
) -> syn::Result<TokenStream> {
|
) -> syn::Result<TokenStream> {
|
||||||
let extract_fields = extract_fields(&fields)?;
|
let extract_fields = extract_fields(&fields)?;
|
||||||
|
|
||||||
let (rejection_ident, rejection) = if let syn::Fields::Unit = &fields {
|
let (rejection_ident, rejection) = if has_no_fields(&fields) {
|
||||||
(syn::parse_quote!(::std::convert::Infallible), quote! {})
|
(syn::parse_quote!(::std::convert::Infallible), None)
|
||||||
} else {
|
} else {
|
||||||
let rejection_ident = rejection_ident(&ident);
|
let rejection_ident = rejection_ident(&ident);
|
||||||
let rejection = extract_each_field_rejection(&ident, &fields, &vis)?;
|
let rejection =
|
||||||
(rejection_ident, rejection)
|
extract_each_field_rejection(&ident, &fields, &vis, rejection_derive_opt_outs)?;
|
||||||
|
(rejection_ident, Some(rejection))
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
@ -77,6 +86,14 @@ fn impl_by_extracting_each_field(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn has_no_fields(fields: &syn::Fields) -> bool {
|
||||||
|
match fields {
|
||||||
|
syn::Fields::Named(fields) => fields.named.is_empty(),
|
||||||
|
syn::Fields::Unnamed(fields) => fields.unnamed.is_empty(),
|
||||||
|
syn::Fields::Unit => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn rejection_ident(ident: &syn::Ident) -> syn::Type {
|
fn rejection_ident(ident: &syn::Ident) -> syn::Type {
|
||||||
let ident = format_ident!("{}Rejection", ident);
|
let ident = format_ident!("{}Rejection", ident);
|
||||||
syn::parse_quote!(#ident)
|
syn::parse_quote!(#ident)
|
||||||
@ -87,7 +104,7 @@ fn extract_fields(fields: &syn::Fields) -> syn::Result<Vec<TokenStream>> {
|
|||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(index, field)| {
|
.map(|(index, field)| {
|
||||||
let FromRequestAttrs { via } = parse_attrs(&field.attrs)?;
|
let FromRequestFieldAttr { via } = parse_field_attrs(&field.attrs)?;
|
||||||
|
|
||||||
let member = if let Some(ident) = &field.ident {
|
let member = if let Some(ident) = &field.ident {
|
||||||
quote! { #ident }
|
quote! { #ident }
|
||||||
@ -211,13 +228,14 @@ fn extract_each_field_rejection(
|
|||||||
ident: &syn::Ident,
|
ident: &syn::Ident,
|
||||||
fields: &syn::Fields,
|
fields: &syn::Fields,
|
||||||
vis: &syn::Visibility,
|
vis: &syn::Visibility,
|
||||||
|
rejection_derive_opt_outs: RejectionDeriveOptOuts,
|
||||||
) -> syn::Result<TokenStream> {
|
) -> syn::Result<TokenStream> {
|
||||||
let rejection_ident = rejection_ident(ident);
|
let rejection_ident = rejection_ident(ident);
|
||||||
|
|
||||||
let variants = fields
|
let variants = fields
|
||||||
.iter()
|
.iter()
|
||||||
.map(|field| {
|
.map(|field| {
|
||||||
let FromRequestAttrs { via } = parse_attrs(&field.attrs)?;
|
let FromRequestFieldAttr { via } = parse_field_attrs(&field.attrs)?;
|
||||||
|
|
||||||
let field_ty = &field.ty;
|
let field_ty = &field.ty;
|
||||||
let ty_span = field_ty.span();
|
let ty_span = field_ty.span();
|
||||||
@ -270,7 +288,7 @@ fn extract_each_field_rejection(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let impl_display = {
|
let impl_display = if rejection_derive_opt_outs.derive_display() {
|
||||||
let arms = fields
|
let arms = fields
|
||||||
.iter()
|
.iter()
|
||||||
.map(|field| {
|
.map(|field| {
|
||||||
@ -281,7 +299,7 @@ fn extract_each_field_rejection(
|
|||||||
})
|
})
|
||||||
.collect::<syn::Result<Vec<_>>>()?;
|
.collect::<syn::Result<Vec<_>>>()?;
|
||||||
|
|
||||||
quote! {
|
Some(quote! {
|
||||||
#[automatically_derived]
|
#[automatically_derived]
|
||||||
impl ::std::fmt::Display for #rejection_ident {
|
impl ::std::fmt::Display for #rejection_ident {
|
||||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||||
@ -290,10 +308,12 @@ fn extract_each_field_rejection(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let impl_error = {
|
let impl_error = if rejection_derive_opt_outs.derive_error() {
|
||||||
let arms = fields
|
let arms = fields
|
||||||
.iter()
|
.iter()
|
||||||
.map(|field| {
|
.map(|field| {
|
||||||
@ -304,7 +324,7 @@ fn extract_each_field_rejection(
|
|||||||
})
|
})
|
||||||
.collect::<syn::Result<Vec<_>>>()?;
|
.collect::<syn::Result<Vec<_>>>()?;
|
||||||
|
|
||||||
quote! {
|
Some(quote! {
|
||||||
#[automatically_derived]
|
#[automatically_derived]
|
||||||
impl ::std::error::Error for #rejection_ident {
|
impl ::std::error::Error for #rejection_ident {
|
||||||
fn source(&self) -> ::std::option::Option<&(dyn ::std::error::Error + 'static)> {
|
fn source(&self) -> ::std::option::Option<&(dyn ::std::error::Error + 'static)> {
|
||||||
@ -313,11 +333,17 @@ fn extract_each_field_rejection(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let impl_debug = rejection_derive_opt_outs.derive_debug().then(|| {
|
||||||
|
quote! { #[derive(Debug)] }
|
||||||
|
});
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
#[derive(Debug)]
|
#impl_debug
|
||||||
#vis enum #rejection_ident {
|
#vis enum #rejection_ident {
|
||||||
#(#variants)*
|
#(#variants)*
|
||||||
}
|
}
|
||||||
@ -381,7 +407,7 @@ fn rejection_variant_name(field: &syn::Field) -> syn::Result<syn::Ident> {
|
|||||||
let mut out = String::new();
|
let mut out = String::new();
|
||||||
rejection_variant_name_for_type(&mut out, &field.ty)?;
|
rejection_variant_name_for_type(&mut out, &field.ty)?;
|
||||||
|
|
||||||
let FromRequestAttrs { via } = parse_attrs(&field.attrs)?;
|
let FromRequestFieldAttr { via } = parse_field_attrs(&field.attrs)?;
|
||||||
if let Some((_, path)) = via {
|
if let Some((_, path)) = via {
|
||||||
let via_ident = &path.segments.last().unwrap().ident;
|
let via_ident = &path.segments.last().unwrap().ident;
|
||||||
Ok(format_ident!("{}{}", via_ident, out))
|
Ok(format_ident!("{}{}", via_ident, out))
|
||||||
@ -403,7 +429,7 @@ fn impl_by_extracting_all_at_once(
|
|||||||
};
|
};
|
||||||
|
|
||||||
for field in fields {
|
for field in fields {
|
||||||
let FromRequestAttrs { via } = parse_attrs(&field.attrs)?;
|
let FromRequestFieldAttr { via } = parse_field_attrs(&field.attrs)?;
|
||||||
if let Some((via, _)) = via {
|
if let Some((via, _)) = via {
|
||||||
return Err(syn::Error::new_spanned(
|
return Err(syn::Error::new_spanned(
|
||||||
via,
|
via,
|
||||||
@ -437,72 +463,6 @@ fn impl_by_extracting_all_at_once(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct FromRequestAttrs {
|
|
||||||
via: Option<(kw::via, syn::Path)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
mod kw {
|
|
||||||
syn::custom_keyword!(via);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_attrs(attrs: &[syn::Attribute]) -> syn::Result<FromRequestAttrs> {
|
|
||||||
enum Attr {
|
|
||||||
FromRequest(Punctuated<FromRequestAttr, Token![,]>),
|
|
||||||
}
|
|
||||||
|
|
||||||
enum FromRequestAttr {
|
|
||||||
Via { via: kw::via, path: syn::Path },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for FromRequestAttr {
|
|
||||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
|
||||||
let lh = input.lookahead1();
|
|
||||||
if lh.peek(kw::via) {
|
|
||||||
let via = input.parse::<kw::via>()?;
|
|
||||||
let content;
|
|
||||||
syn::parenthesized!(content in input);
|
|
||||||
content.parse().map(|path| Self::Via { via, path })
|
|
||||||
} else {
|
|
||||||
Err(lh.error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let attrs = attrs
|
|
||||||
.iter()
|
|
||||||
.filter(|attr| attr.path.is_ident("from_request"))
|
|
||||||
.map(|attr| {
|
|
||||||
attr.parse_args_with(Punctuated::parse_terminated)
|
|
||||||
.map(Attr::FromRequest)
|
|
||||||
})
|
|
||||||
.collect::<syn::Result<Vec<_>>>()?;
|
|
||||||
|
|
||||||
let mut out = FromRequestAttrs::default();
|
|
||||||
for attr in attrs {
|
|
||||||
match attr {
|
|
||||||
Attr::FromRequest(from_request_attrs) => {
|
|
||||||
for from_request_attr in from_request_attrs {
|
|
||||||
match from_request_attr {
|
|
||||||
FromRequestAttr::Via { via, path } => {
|
|
||||||
if out.via.is_some() {
|
|
||||||
return Err(syn::Error::new_spanned(
|
|
||||||
via,
|
|
||||||
"`via` specified more than once",
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
out.via = Some((via, path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ui() {
|
fn ui() {
|
||||||
#[rustversion::stable]
|
#[rustversion::stable]
|
||||||
|
243
axum-macros/src/from_request/attr.rs
Normal file
243
axum-macros/src/from_request/attr.rs
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
use quote::ToTokens;
|
||||||
|
use syn::{
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
punctuated::Punctuated,
|
||||||
|
Token,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(crate) struct FromRequestFieldAttr {
|
||||||
|
pub(crate) via: Option<(kw::via, syn::Path)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(crate) struct FromRequestContainerAttr {
|
||||||
|
pub(crate) via: Option<(kw::via, syn::Path)>,
|
||||||
|
pub(crate) rejection_derive: Option<(kw::rejection_derive, RejectionDeriveOptOuts)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) mod kw {
|
||||||
|
syn::custom_keyword!(via);
|
||||||
|
syn::custom_keyword!(rejection_derive);
|
||||||
|
syn::custom_keyword!(Display);
|
||||||
|
syn::custom_keyword!(Debug);
|
||||||
|
syn::custom_keyword!(Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn parse_field_attrs(attrs: &[syn::Attribute]) -> syn::Result<FromRequestFieldAttr> {
|
||||||
|
let attrs = parse_attrs(attrs)?;
|
||||||
|
|
||||||
|
let mut out = FromRequestFieldAttr::default();
|
||||||
|
|
||||||
|
for from_request_attr in attrs {
|
||||||
|
match from_request_attr {
|
||||||
|
FieldAttr::Via { via, path } => {
|
||||||
|
if out.via.is_some() {
|
||||||
|
return Err(double_attr_error("via", via));
|
||||||
|
} else {
|
||||||
|
out.via = Some((via, path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn parse_container_attrs(
|
||||||
|
attrs: &[syn::Attribute],
|
||||||
|
) -> syn::Result<FromRequestContainerAttr> {
|
||||||
|
let attrs = parse_attrs(attrs)?;
|
||||||
|
|
||||||
|
let mut out = FromRequestContainerAttr::default();
|
||||||
|
|
||||||
|
for from_request_attr in attrs {
|
||||||
|
match from_request_attr {
|
||||||
|
ContainerAttr::Via { via, path } => {
|
||||||
|
if out.rejection_derive.is_some() {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
via,
|
||||||
|
"cannot use both `rejection_derive` and `via`",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.via.is_some() {
|
||||||
|
return Err(double_attr_error("via", via));
|
||||||
|
} else {
|
||||||
|
out.via = Some((via, path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ContainerAttr::RejectionDerive {
|
||||||
|
rejection_derive,
|
||||||
|
opt_outs,
|
||||||
|
} => {
|
||||||
|
if out.via.is_some() {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
rejection_derive,
|
||||||
|
"cannot use both `via` and `rejection_derive`",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.rejection_derive.is_some() {
|
||||||
|
return Err(double_attr_error("rejection_derive", rejection_derive));
|
||||||
|
} else {
|
||||||
|
out.rejection_derive = Some((rejection_derive, opt_outs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn parse_attrs<T>(attrs: &[syn::Attribute]) -> syn::Result<Punctuated<T, Token![,]>>
|
||||||
|
where
|
||||||
|
T: Parse,
|
||||||
|
{
|
||||||
|
let attrs = attrs
|
||||||
|
.iter()
|
||||||
|
.filter(|attr| attr.path.is_ident("from_request"))
|
||||||
|
.map(|attr| attr.parse_args_with(Punctuated::<T, Token![,]>::parse_terminated))
|
||||||
|
.collect::<syn::Result<Vec<_>>>()?
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Punctuated<T, Token![,]>>();
|
||||||
|
Ok(attrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn double_attr_error<T>(ident: &str, spanned: T) -> syn::Error
|
||||||
|
where
|
||||||
|
T: ToTokens,
|
||||||
|
{
|
||||||
|
syn::Error::new_spanned(spanned, format!("`{}` specified more than once", ident))
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ContainerAttr {
|
||||||
|
Via {
|
||||||
|
via: kw::via,
|
||||||
|
path: syn::Path,
|
||||||
|
},
|
||||||
|
RejectionDerive {
|
||||||
|
rejection_derive: kw::rejection_derive,
|
||||||
|
opt_outs: RejectionDeriveOptOuts,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for ContainerAttr {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
let lh = input.lookahead1();
|
||||||
|
if lh.peek(kw::via) {
|
||||||
|
let via = input.parse::<kw::via>()?;
|
||||||
|
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 {
|
||||||
|
Err(lh.error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FieldAttr {
|
||||||
|
Via { via: kw::via, path: syn::Path },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for FieldAttr {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
let lh = input.lookahead1();
|
||||||
|
if lh.peek(kw::via) {
|
||||||
|
let via = input.parse::<kw::via>()?;
|
||||||
|
let content;
|
||||||
|
syn::parenthesized!(content in input);
|
||||||
|
content.parse().map(|path| Self::Via { via, path })
|
||||||
|
} else {
|
||||||
|
Err(lh.error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -201,6 +201,41 @@ mod from_request;
|
|||||||
/// means the inner rejection types must themselves implement `std::error::Error`. All extractors
|
/// means the inner rejection types must themselves implement `std::error::Error`. All extractors
|
||||||
/// in axum does this.
|
/// 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<B> FromRequest<B> for OtherExtractor
|
||||||
|
/// where
|
||||||
|
/// B: Send + 'static,
|
||||||
|
/// {
|
||||||
|
/// // this rejection doesn't implement `Display` and `Error`
|
||||||
|
/// type Rejection = (StatusCode, String);
|
||||||
|
///
|
||||||
|
/// async fn from_request(_req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||||
|
/// // ...
|
||||||
|
/// # unimplemented!()
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
/// # The whole type at once
|
/// # The whole type at once
|
||||||
///
|
///
|
||||||
/// By using `#[from_request(via(...))]` on the container you can extract the whole type at once,
|
/// By using `#[from_request(via(...))]` on the container you can extract the whole type at once,
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
use axum_macros::FromRequest;
|
||||||
|
|
||||||
|
#[derive(FromRequest)]
|
||||||
|
#[from_request(rejection_derive(!Debug, !Display))]
|
||||||
|
struct Extractor {
|
||||||
|
body: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
@ -0,0 +1,5 @@
|
|||||||
|
error: opt out of `Debug` and `Display` requires also opting out of `Error`. Use `#[from_request(rejection_derive(!Debug, !Display, !Error))]`
|
||||||
|
--> tests/from_request/fail/derive_opt_out_debug_and_display_without_error.rs:4:34
|
||||||
|
|
|
||||||
|
4 | #[from_request(rejection_derive(!Debug, !Display))]
|
||||||
|
| ^^^^^
|
@ -0,0 +1,9 @@
|
|||||||
|
use axum_macros::FromRequest;
|
||||||
|
|
||||||
|
#[derive(FromRequest)]
|
||||||
|
#[from_request(rejection_derive(!Debug))]
|
||||||
|
struct Extractor {
|
||||||
|
body: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
@ -0,0 +1,5 @@
|
|||||||
|
error: opt out of `Debug` requires also opting out of `Error`. Use `#[from_request(rejection_derive(!Debug, !Error))]`
|
||||||
|
--> tests/from_request/fail/derive_opt_out_debug_without_error.rs:4:34
|
||||||
|
|
|
||||||
|
4 | #[from_request(rejection_derive(!Debug))]
|
||||||
|
| ^^^^^
|
@ -0,0 +1,9 @@
|
|||||||
|
use axum_macros::FromRequest;
|
||||||
|
|
||||||
|
#[derive(FromRequest)]
|
||||||
|
#[from_request(rejection_derive(!Display))]
|
||||||
|
struct Extractor {
|
||||||
|
body: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
@ -0,0 +1,5 @@
|
|||||||
|
error: opt out of `Display` requires also opting out of `Error`. Use `#[from_request(rejection_derive(!Display, !Error))]`
|
||||||
|
--> tests/from_request/fail/derive_opt_out_display_without_error.rs:4:34
|
||||||
|
|
|
||||||
|
4 | #[from_request(rejection_derive(!Display))]
|
||||||
|
| ^^^^^^^
|
@ -0,0 +1,9 @@
|
|||||||
|
use axum_macros::FromRequest;
|
||||||
|
|
||||||
|
#[derive(FromRequest)]
|
||||||
|
#[from_request(rejection_derive(!Error, !Error))]
|
||||||
|
struct Extractor {
|
||||||
|
body: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
@ -0,0 +1,5 @@
|
|||||||
|
error: `Error` opt out specified more than once
|
||||||
|
--> tests/from_request/fail/derive_opt_out_duplicate.rs:4:42
|
||||||
|
|
|
||||||
|
4 | #[from_request(rejection_derive(!Error, !Error))]
|
||||||
|
| ^^^^^
|
@ -0,0 +1,10 @@
|
|||||||
|
use axum_macros::FromRequest;
|
||||||
|
use axum::extract::Extension;
|
||||||
|
|
||||||
|
#[derive(FromRequest, Clone)]
|
||||||
|
#[from_request(rejection_derive(!Error), via(Extension))]
|
||||||
|
struct Extractor {
|
||||||
|
config: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
@ -0,0 +1,13 @@
|
|||||||
|
error: cannot use both `rejection_derive` and `via`
|
||||||
|
--> tests/from_request/fail/rejection_derive_and_via.rs:5:42
|
||||||
|
|
|
||||||
|
5 | #[from_request(rejection_derive(!Error), via(Extension))]
|
||||||
|
| ^^^
|
||||||
|
|
||||||
|
warning: unused import: `axum::extract::Extension`
|
||||||
|
--> tests/from_request/fail/rejection_derive_and_via.rs:2:5
|
||||||
|
|
|
||||||
|
2 | use axum::extract::Extension;
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: `#[warn(unused_imports)]` on by default
|
@ -0,0 +1,7 @@
|
|||||||
|
use axum_macros::FromRequest;
|
||||||
|
|
||||||
|
#[derive(FromRequest)]
|
||||||
|
#[from_request(foo)]
|
||||||
|
struct Extractor;
|
||||||
|
|
||||||
|
fn main() {}
|
@ -0,0 +1,5 @@
|
|||||||
|
error: expected `via` or `rejection_derive`
|
||||||
|
--> tests/from_request/fail/unknown_attr_container.rs:4:16
|
||||||
|
|
|
||||||
|
4 | #[from_request(foo)]
|
||||||
|
| ^^^
|
@ -1,5 +1,5 @@
|
|||||||
error: expected `via`
|
error: expected `via`
|
||||||
--> tests/from_request/fail/unknown_attr.rs:4:33
|
--> tests/from_request/fail/unknown_attr_field.rs:4:33
|
||||||
|
|
|
|
||||||
4 | struct Extractor(#[from_request(foo)] String);
|
4 | struct Extractor(#[from_request(foo)] String);
|
||||||
| ^^^
|
| ^^^
|
@ -0,0 +1,10 @@
|
|||||||
|
use axum_macros::FromRequest;
|
||||||
|
use axum::extract::Extension;
|
||||||
|
|
||||||
|
#[derive(FromRequest, Clone)]
|
||||||
|
#[from_request(via(Extension), rejection_derive(!Error))]
|
||||||
|
struct Extractor {
|
||||||
|
config: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
@ -0,0 +1,13 @@
|
|||||||
|
error: cannot use both `via` and `rejection_derive`
|
||||||
|
--> tests/from_request/fail/via_and_rejection_derive.rs:5:32
|
||||||
|
|
|
||||||
|
5 | #[from_request(via(Extension), rejection_derive(!Error))]
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: unused import: `axum::extract::Extension`
|
||||||
|
--> tests/from_request/fail/via_and_rejection_derive.rs:2:5
|
||||||
|
|
|
||||||
|
2 | use axum::extract::Extension;
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: `#[warn(unused_imports)]` on by default
|
37
axum-macros/tests/from_request/pass/derive_opt_out.rs
Normal file
37
axum-macros/tests/from_request/pass/derive_opt_out.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
use axum::{
|
||||||
|
async_trait,
|
||||||
|
extract::{FromRequest, RequestParts},
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
};
|
||||||
|
use axum_macros::FromRequest;
|
||||||
|
|
||||||
|
#[derive(FromRequest)]
|
||||||
|
#[from_request(rejection_derive(!Display, !Error))]
|
||||||
|
struct Extractor {
|
||||||
|
other: OtherExtractor,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OtherExtractor;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<B> FromRequest<B> for OtherExtractor
|
||||||
|
where
|
||||||
|
B: Send + 'static,
|
||||||
|
{
|
||||||
|
type Rejection = OtherExtractorRejection;
|
||||||
|
|
||||||
|
async fn from_request(_req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct OtherExtractorRejection;
|
||||||
|
|
||||||
|
impl IntoResponse for OtherExtractorRejection {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
12
axum-macros/tests/from_request/pass/empty_named.rs
Normal file
12
axum-macros/tests/from_request/pass/empty_named.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use axum_macros::FromRequest;
|
||||||
|
|
||||||
|
#[derive(FromRequest)]
|
||||||
|
struct Extractor {}
|
||||||
|
|
||||||
|
fn assert_from_request()
|
||||||
|
where
|
||||||
|
Extractor: axum::extract::FromRequest<axum::body::Body, Rejection = std::convert::Infallible>,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
12
axum-macros/tests/from_request/pass/empty_tuple.rs
Normal file
12
axum-macros/tests/from_request/pass/empty_tuple.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use axum_macros::FromRequest;
|
||||||
|
|
||||||
|
#[derive(FromRequest)]
|
||||||
|
struct Extractor();
|
||||||
|
|
||||||
|
fn assert_from_request()
|
||||||
|
where
|
||||||
|
Extractor: axum::extract::FromRequest<axum::body::Body, Rejection = std::convert::Infallible>,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
@ -5,7 +5,7 @@ struct Extractor;
|
|||||||
|
|
||||||
fn assert_from_request()
|
fn assert_from_request()
|
||||||
where
|
where
|
||||||
Extractor: axum::extract::FromRequest<axum::body::Body>,
|
Extractor: axum::extract::FromRequest<axum::body::Body, Rejection = std::convert::Infallible>,
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user