From 911c4a788e35e74a6c210b82e15880b68fbb1efa Mon Sep 17 00:00:00 2001 From: David Pedersen <david.pdrsn@gmail.com> Date: Fri, 28 Jan 2022 10:54:38 +0100 Subject: [PATCH] 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> --- axum-macros/src/from_request.rs | 138 ++++------ axum-macros/src/from_request/attr.rs | 243 ++++++++++++++++++ axum-macros/src/lib.rs | 35 +++ ...opt_out_debug_and_display_without_error.rs | 9 + ...out_debug_and_display_without_error.stderr | 5 + .../derive_opt_out_debug_without_error.rs | 9 + .../derive_opt_out_debug_without_error.stderr | 5 + .../derive_opt_out_display_without_error.rs | 9 + ...erive_opt_out_display_without_error.stderr | 5 + .../fail/derive_opt_out_duplicate.rs | 9 + .../fail/derive_opt_out_duplicate.stderr | 5 + .../fail/rejection_derive_and_via.rs | 10 + .../fail/rejection_derive_and_via.stderr | 13 + .../fail/unknown_attr_container.rs | 7 + .../fail/unknown_attr_container.stderr | 5 + ...{unknown_attr.rs => unknown_attr_field.rs} | 0 ..._attr.stderr => unknown_attr_field.stderr} | 2 +- .../fail/via_and_rejection_derive.rs | 10 + .../fail/via_and_rejection_derive.stderr | 13 + .../tests/from_request/pass/derive_opt_out.rs | 37 +++ .../tests/from_request/pass/empty_named.rs | 12 + .../tests/from_request/pass/empty_tuple.rs | 12 + axum-macros/tests/from_request/pass/unit.rs | 2 +- 23 files changed, 504 insertions(+), 91 deletions(-) create mode 100644 axum-macros/src/from_request/attr.rs create mode 100644 axum-macros/tests/from_request/fail/derive_opt_out_debug_and_display_without_error.rs create mode 100644 axum-macros/tests/from_request/fail/derive_opt_out_debug_and_display_without_error.stderr create mode 100644 axum-macros/tests/from_request/fail/derive_opt_out_debug_without_error.rs create mode 100644 axum-macros/tests/from_request/fail/derive_opt_out_debug_without_error.stderr create mode 100644 axum-macros/tests/from_request/fail/derive_opt_out_display_without_error.rs create mode 100644 axum-macros/tests/from_request/fail/derive_opt_out_display_without_error.stderr create mode 100644 axum-macros/tests/from_request/fail/derive_opt_out_duplicate.rs create mode 100644 axum-macros/tests/from_request/fail/derive_opt_out_duplicate.stderr create mode 100644 axum-macros/tests/from_request/fail/rejection_derive_and_via.rs create mode 100644 axum-macros/tests/from_request/fail/rejection_derive_and_via.stderr create mode 100644 axum-macros/tests/from_request/fail/unknown_attr_container.rs create mode 100644 axum-macros/tests/from_request/fail/unknown_attr_container.stderr rename axum-macros/tests/from_request/fail/{unknown_attr.rs => unknown_attr_field.rs} (100%) rename axum-macros/tests/from_request/fail/{unknown_attr.stderr => unknown_attr_field.stderr} (67%) create mode 100644 axum-macros/tests/from_request/fail/via_and_rejection_derive.rs create mode 100644 axum-macros/tests/from_request/fail/via_and_rejection_derive.stderr create mode 100644 axum-macros/tests/from_request/pass/derive_opt_out.rs create mode 100644 axum-macros/tests/from_request/pass/empty_named.rs create mode 100644 axum-macros/tests/from_request/pass/empty_tuple.rs diff --git a/axum-macros/src/from_request.rs b/axum-macros/src/from_request.rs index 8e4fa1e8..158719d3 100644 --- a/axum-macros/src/from_request.rs +++ b/axum-macros/src/from_request.rs @@ -1,12 +1,13 @@ +use self::attr::{ + parse_container_attrs, parse_field_attrs, FromRequestContainerAttr, FromRequestFieldAttr, + RejectionDeriveOptOuts, +}; use heck::ToUpperCamelCase; use proc_macro2::TokenStream; use quote::{format_ident, quote, quote_spanned}; -use syn::{ - parse::{Parse, ParseStream}, - punctuated::Punctuated, - spanned::Spanned, - Token, -}; +use syn::{punctuated::Punctuated, spanned::Spanned, Token}; + +mod attr; 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)); } - let FromRequestAttrs { via } = parse_attrs(&attrs)?; + let FromRequestContainerAttr { + via, + rejection_derive, + } = parse_container_attrs(&attrs)?; if let Some((_, path)) = via { impl_by_extracting_all_at_once(ident, fields, path) } 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, fields: syn::Fields, vis: syn::Visibility, + rejection_derive_opt_outs: RejectionDeriveOptOuts, ) -> syn::Result<TokenStream> { let extract_fields = extract_fields(&fields)?; - let (rejection_ident, rejection) = if let syn::Fields::Unit = &fields { - (syn::parse_quote!(::std::convert::Infallible), quote! {}) + let (rejection_ident, rejection) = if has_no_fields(&fields) { + (syn::parse_quote!(::std::convert::Infallible), None) } else { let rejection_ident = rejection_ident(&ident); - let rejection = extract_each_field_rejection(&ident, &fields, &vis)?; - (rejection_ident, rejection) + let rejection = + extract_each_field_rejection(&ident, &fields, &vis, rejection_derive_opt_outs)?; + (rejection_ident, Some(rejection)) }; 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 { let ident = format_ident!("{}Rejection", ident); syn::parse_quote!(#ident) @@ -87,7 +104,7 @@ fn extract_fields(fields: &syn::Fields) -> syn::Result<Vec<TokenStream>> { .iter() .enumerate() .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 { quote! { #ident } @@ -211,13 +228,14 @@ 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 FromRequestAttrs { via } = parse_attrs(&field.attrs)?; + let FromRequestFieldAttr { via } = parse_field_attrs(&field.attrs)?; let field_ty = &field.ty; 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 .iter() .map(|field| { @@ -281,7 +299,7 @@ fn extract_each_field_rejection( }) .collect::<syn::Result<Vec<_>>>()?; - quote! { + Some(quote! { #[automatically_derived] impl ::std::fmt::Display for #rejection_ident { 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 .iter() .map(|field| { @@ -304,7 +324,7 @@ fn extract_each_field_rejection( }) .collect::<syn::Result<Vec<_>>>()?; - quote! { + Some(quote! { #[automatically_derived] impl ::std::error::Error for #rejection_ident { 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! { - #[derive(Debug)] + #impl_debug #vis enum #rejection_ident { #(#variants)* } @@ -381,7 +407,7 @@ fn rejection_variant_name(field: &syn::Field) -> syn::Result<syn::Ident> { let mut out = String::new(); 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 { let via_ident = &path.segments.last().unwrap().ident; Ok(format_ident!("{}{}", via_ident, out)) @@ -403,7 +429,7 @@ fn impl_by_extracting_all_at_once( }; for field in fields { - let FromRequestAttrs { via } = parse_attrs(&field.attrs)?; + let FromRequestFieldAttr { via } = parse_field_attrs(&field.attrs)?; if let Some((via, _)) = via { return Err(syn::Error::new_spanned( 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] fn ui() { #[rustversion::stable] diff --git a/axum-macros/src/from_request/attr.rs b/axum-macros/src/from_request/attr.rs new file mode 100644 index 00000000..35b63d79 --- /dev/null +++ b/axum-macros/src/from_request/attr.rs @@ -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, + }) + } +} diff --git a/axum-macros/src/lib.rs b/axum-macros/src/lib.rs index 0b39951e..ae826426 100644 --- a/axum-macros/src/lib.rs +++ b/axum-macros/src/lib.rs @@ -201,6 +201,41 @@ mod from_request; /// 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<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 /// /// By using `#[from_request(via(...))]` on the container you can extract the whole type at once, diff --git a/axum-macros/tests/from_request/fail/derive_opt_out_debug_and_display_without_error.rs b/axum-macros/tests/from_request/fail/derive_opt_out_debug_and_display_without_error.rs new file mode 100644 index 00000000..a8cb0818 --- /dev/null +++ b/axum-macros/tests/from_request/fail/derive_opt_out_debug_and_display_without_error.rs @@ -0,0 +1,9 @@ +use axum_macros::FromRequest; + +#[derive(FromRequest)] +#[from_request(rejection_derive(!Debug, !Display))] +struct Extractor { + body: String, +} + +fn main() {} diff --git a/axum-macros/tests/from_request/fail/derive_opt_out_debug_and_display_without_error.stderr b/axum-macros/tests/from_request/fail/derive_opt_out_debug_and_display_without_error.stderr new file mode 100644 index 00000000..656c8a54 --- /dev/null +++ b/axum-macros/tests/from_request/fail/derive_opt_out_debug_and_display_without_error.stderr @@ -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))] + | ^^^^^ diff --git a/axum-macros/tests/from_request/fail/derive_opt_out_debug_without_error.rs b/axum-macros/tests/from_request/fail/derive_opt_out_debug_without_error.rs new file mode 100644 index 00000000..dbc0aed8 --- /dev/null +++ b/axum-macros/tests/from_request/fail/derive_opt_out_debug_without_error.rs @@ -0,0 +1,9 @@ +use axum_macros::FromRequest; + +#[derive(FromRequest)] +#[from_request(rejection_derive(!Debug))] +struct Extractor { + body: String, +} + +fn main() {} diff --git a/axum-macros/tests/from_request/fail/derive_opt_out_debug_without_error.stderr b/axum-macros/tests/from_request/fail/derive_opt_out_debug_without_error.stderr new file mode 100644 index 00000000..1d8c2875 --- /dev/null +++ b/axum-macros/tests/from_request/fail/derive_opt_out_debug_without_error.stderr @@ -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))] + | ^^^^^ diff --git a/axum-macros/tests/from_request/fail/derive_opt_out_display_without_error.rs b/axum-macros/tests/from_request/fail/derive_opt_out_display_without_error.rs new file mode 100644 index 00000000..2f478768 --- /dev/null +++ b/axum-macros/tests/from_request/fail/derive_opt_out_display_without_error.rs @@ -0,0 +1,9 @@ +use axum_macros::FromRequest; + +#[derive(FromRequest)] +#[from_request(rejection_derive(!Display))] +struct Extractor { + body: String, +} + +fn main() {} diff --git a/axum-macros/tests/from_request/fail/derive_opt_out_display_without_error.stderr b/axum-macros/tests/from_request/fail/derive_opt_out_display_without_error.stderr new file mode 100644 index 00000000..0db03759 --- /dev/null +++ b/axum-macros/tests/from_request/fail/derive_opt_out_display_without_error.stderr @@ -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))] + | ^^^^^^^ diff --git a/axum-macros/tests/from_request/fail/derive_opt_out_duplicate.rs b/axum-macros/tests/from_request/fail/derive_opt_out_duplicate.rs new file mode 100644 index 00000000..0a0b2eb8 --- /dev/null +++ b/axum-macros/tests/from_request/fail/derive_opt_out_duplicate.rs @@ -0,0 +1,9 @@ +use axum_macros::FromRequest; + +#[derive(FromRequest)] +#[from_request(rejection_derive(!Error, !Error))] +struct Extractor { + body: String, +} + +fn main() {} diff --git a/axum-macros/tests/from_request/fail/derive_opt_out_duplicate.stderr b/axum-macros/tests/from_request/fail/derive_opt_out_duplicate.stderr new file mode 100644 index 00000000..7ae523d4 --- /dev/null +++ b/axum-macros/tests/from_request/fail/derive_opt_out_duplicate.stderr @@ -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))] + | ^^^^^ diff --git a/axum-macros/tests/from_request/fail/rejection_derive_and_via.rs b/axum-macros/tests/from_request/fail/rejection_derive_and_via.rs new file mode 100644 index 00000000..bb658f11 --- /dev/null +++ b/axum-macros/tests/from_request/fail/rejection_derive_and_via.rs @@ -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() {} diff --git a/axum-macros/tests/from_request/fail/rejection_derive_and_via.stderr b/axum-macros/tests/from_request/fail/rejection_derive_and_via.stderr new file mode 100644 index 00000000..59c6b0be --- /dev/null +++ b/axum-macros/tests/from_request/fail/rejection_derive_and_via.stderr @@ -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 diff --git a/axum-macros/tests/from_request/fail/unknown_attr_container.rs b/axum-macros/tests/from_request/fail/unknown_attr_container.rs new file mode 100644 index 00000000..f6d06e02 --- /dev/null +++ b/axum-macros/tests/from_request/fail/unknown_attr_container.rs @@ -0,0 +1,7 @@ +use axum_macros::FromRequest; + +#[derive(FromRequest)] +#[from_request(foo)] +struct Extractor; + +fn main() {} diff --git a/axum-macros/tests/from_request/fail/unknown_attr_container.stderr b/axum-macros/tests/from_request/fail/unknown_attr_container.stderr new file mode 100644 index 00000000..10608bb6 --- /dev/null +++ b/axum-macros/tests/from_request/fail/unknown_attr_container.stderr @@ -0,0 +1,5 @@ +error: expected `via` or `rejection_derive` + --> tests/from_request/fail/unknown_attr_container.rs:4:16 + | +4 | #[from_request(foo)] + | ^^^ diff --git a/axum-macros/tests/from_request/fail/unknown_attr.rs b/axum-macros/tests/from_request/fail/unknown_attr_field.rs similarity index 100% rename from axum-macros/tests/from_request/fail/unknown_attr.rs rename to axum-macros/tests/from_request/fail/unknown_attr_field.rs diff --git a/axum-macros/tests/from_request/fail/unknown_attr.stderr b/axum-macros/tests/from_request/fail/unknown_attr_field.stderr similarity index 67% rename from axum-macros/tests/from_request/fail/unknown_attr.stderr rename to axum-macros/tests/from_request/fail/unknown_attr_field.stderr index b6422fed..9b626a94 100644 --- a/axum-macros/tests/from_request/fail/unknown_attr.stderr +++ b/axum-macros/tests/from_request/fail/unknown_attr_field.stderr @@ -1,5 +1,5 @@ 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); | ^^^ diff --git a/axum-macros/tests/from_request/fail/via_and_rejection_derive.rs b/axum-macros/tests/from_request/fail/via_and_rejection_derive.rs new file mode 100644 index 00000000..8c183a60 --- /dev/null +++ b/axum-macros/tests/from_request/fail/via_and_rejection_derive.rs @@ -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() {} diff --git a/axum-macros/tests/from_request/fail/via_and_rejection_derive.stderr b/axum-macros/tests/from_request/fail/via_and_rejection_derive.stderr new file mode 100644 index 00000000..25f2011b --- /dev/null +++ b/axum-macros/tests/from_request/fail/via_and_rejection_derive.stderr @@ -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 diff --git a/axum-macros/tests/from_request/pass/derive_opt_out.rs b/axum-macros/tests/from_request/pass/derive_opt_out.rs new file mode 100644 index 00000000..0bf24c73 --- /dev/null +++ b/axum-macros/tests/from_request/pass/derive_opt_out.rs @@ -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() {} diff --git a/axum-macros/tests/from_request/pass/empty_named.rs b/axum-macros/tests/from_request/pass/empty_named.rs new file mode 100644 index 00000000..2cc5dda8 --- /dev/null +++ b/axum-macros/tests/from_request/pass/empty_named.rs @@ -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() {} diff --git a/axum-macros/tests/from_request/pass/empty_tuple.rs b/axum-macros/tests/from_request/pass/empty_tuple.rs new file mode 100644 index 00000000..bbb525fa --- /dev/null +++ b/axum-macros/tests/from_request/pass/empty_tuple.rs @@ -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() {} diff --git a/axum-macros/tests/from_request/pass/unit.rs b/axum-macros/tests/from_request/pass/unit.rs index bbf58c3c..57f774d1 100644 --- a/axum-macros/tests/from_request/pass/unit.rs +++ b/axum-macros/tests/from_request/pass/unit.rs @@ -5,7 +5,7 @@ struct Extractor; fn assert_from_request() where - Extractor: axum::extract::FromRequest<axum::body::Body>, + Extractor: axum::extract::FromRequest<axum::body::Body, Rejection = std::convert::Infallible>, { }