Support using a different rejection for #[derive(FromRequest)] (#1256)

This commit is contained in:
David Pedersen 2022-08-12 18:05:27 +02:00 committed by GitHub
parent a8e80bcb97
commit ac7037d282
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 750 additions and 45 deletions

View file

@ -8,8 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
# Unreleased
- **change:** axum-macro's MSRV is now 1.60 ([#1239])
- **added:** Support using a different rejection for `#[derive(FromRequest)]`
with `#[from_request(rejection(MyRejection))]` ([#1256])
[#1239]: https://github.com/tokio-rs/axum/pull/1239
[#1256]: https://github.com/tokio-rs/axum/pull/1256
# 0.2.3 (27. June, 2022)

View file

@ -25,6 +25,7 @@ axum = { path = "../axum", version = "0.5", features = ["headers"] }
axum-extra = { path = "../axum-extra", version = "0.3", features = ["typed-routing"] }
rustversion = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
syn = { version = "1.0", features = ["full", "extra-traits"] }
tokio = { version = "1.0", features = ["full"] }
trybuild = "1.0.63"

View file

@ -5,7 +5,7 @@ use self::attr::{
use heck::ToUpperCamelCase;
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote, quote_spanned};
use syn::{punctuated::Punctuated, spanned::Spanned, Token};
use syn::{punctuated::Punctuated, spanned::Spanned, Ident, Token};
mod attr;
@ -22,21 +22,45 @@ pub(crate) fn expand(item: syn::Item) -> syn::Result<TokenStream> {
struct_token: _,
} = item;
error_on_generics(generics)?;
let generic_ident = parse_single_generic_type_on_struct(generics, &fields)?;
match parse_container_attrs(&attrs)? {
FromRequestContainerAttr::Via(path) => {
impl_struct_by_extracting_all_at_once(ident, fields, path)
FromRequestContainerAttr::Via { path, rejection } => {
impl_struct_by_extracting_all_at_once(
ident,
fields,
path,
rejection,
generic_ident,
)
}
FromRequestContainerAttr::RejectionDerive(_, opt_outs) => {
impl_struct_by_extracting_each_field(ident, fields, vis, 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),
)
}
FromRequestContainerAttr::None => {
error_on_generic_ident(generic_ident)?;
impl_struct_by_extracting_each_field(
ident,
fields,
vis,
RejectionDeriveOptOuts::default(),
None,
)
}
FromRequestContainerAttr::None => impl_struct_by_extracting_each_field(
ident,
fields,
vis,
RejectionDeriveOptOuts::default(),
),
}
}
syn::Item::Enum(item) => {
@ -50,11 +74,19 @@ pub(crate) fn expand(item: syn::Item) -> syn::Result<TokenStream> {
variants,
} = item;
error_on_generics(generics)?;
const GENERICS_ERROR: &str = "`#[derive(FromRequest)] on enums don't support generics";
if !generics.params.is_empty() {
return Err(syn::Error::new_spanned(generics, GENERICS_ERROR));
}
if let Some(where_clause) = generics.where_clause {
return Err(syn::Error::new_spanned(where_clause, GENERICS_ERROR));
}
match parse_container_attrs(&attrs)? {
FromRequestContainerAttr::Via(path) => {
impl_enum_by_extracting_all_at_once(ident, variants, path)
FromRequestContainerAttr::Via { path, rejection } => {
impl_enum_by_extracting_all_at_once(ident, variants, path, rejection)
}
FromRequestContainerAttr::RejectionDerive(rejection_derive, _) => {
Err(syn::Error::new_spanned(
@ -62,6 +94,10 @@ pub(crate) fn expand(item: syn::Item) -> syn::Result<TokenStream> {
"cannot use `rejection_derive` on enums",
))
}
FromRequestContainerAttr::Rejection(rejection) => Err(syn::Error::new_spanned(
rejection,
"cannot use `rejection` without `via`",
)),
FromRequestContainerAttr::None => Err(syn::Error::new(
Span::call_site(),
"missing `#[from_request(via(...))]`",
@ -72,18 +108,90 @@ pub(crate) fn expand(item: syn::Item) -> syn::Result<TokenStream> {
}
}
fn error_on_generics(generics: syn::Generics) -> syn::Result<()> {
const GENERICS_ERROR: &str = "`#[derive(FromRequest)] doesn't support generics";
if !generics.params.is_empty() {
return Err(syn::Error::new_spanned(generics, GENERICS_ERROR));
}
fn parse_single_generic_type_on_struct(
generics: syn::Generics,
fields: &syn::Fields,
) -> syn::Result<Option<Ident>> {
if let Some(where_clause) = generics.where_clause {
return Err(syn::Error::new_spanned(where_clause, GENERICS_ERROR));
return Err(syn::Error::new_spanned(
where_clause,
"#[derive(FromRequest)] doesn't support structs with `where` clauses",
));
}
Ok(())
match generics.params.len() {
0 => Ok(None),
1 => {
let param = generics.params.first().unwrap();
let ty_ident = match param {
syn::GenericParam::Type(ty) => &ty.ident,
syn::GenericParam::Lifetime(lifetime) => {
return Err(syn::Error::new_spanned(
lifetime,
"#[derive(FromRequest)] doesn't support structs that are generic over lifetimes",
));
}
syn::GenericParam::Const(konst) => {
return Err(syn::Error::new_spanned(
konst,
"#[derive(FromRequest)] doesn't support structs that have const generics",
));
}
};
match fields {
syn::Fields::Named(fields_named) => {
return Err(syn::Error::new_spanned(
fields_named,
"#[derive(FromRequest)] doesn't support named fields for generic structs. Use a tuple struct instead",
));
}
syn::Fields::Unnamed(fields_unnamed) => {
if fields_unnamed.unnamed.len() != 1 {
return Err(syn::Error::new_spanned(
fields_unnamed,
"#[derive(FromRequest)] only supports generics on tuple structs that have exactly one field",
));
}
let field = fields_unnamed.unnamed.first().unwrap();
if let syn::Type::Path(type_path) = &field.ty {
if type_path
.path
.get_ident()
.map_or(true, |field_type_ident| field_type_ident != ty_ident)
{
return Err(syn::Error::new_spanned(
type_path,
"#[derive(FromRequest)] only supports generics on tuple structs that have exactly one field of the generic type",
));
}
} else {
return Err(syn::Error::new_spanned(&field.ty, "Expected type path"));
}
}
syn::Fields::Unit => return Ok(None),
}
Ok(Some(ty_ident.clone()))
}
_ => Err(syn::Error::new_spanned(
generics,
"#[derive(FromRequest)] only supports 0 or 1 generic type parameters",
)),
}
}
fn error_on_generic_ident(generic_ident: Option<Ident>) -> syn::Result<()> {
if let Some(generic_ident) = generic_ident {
Err(syn::Error::new_spanned(
generic_ident,
"#[derive(FromRequest)] only supports generics when used with #[from_request(via)]",
))
} else {
Ok(())
}
}
fn impl_struct_by_extracting_each_field(
@ -91,10 +199,14 @@ fn impl_struct_by_extracting_each_field(
fields: syn::Fields,
vis: syn::Visibility,
rejection_derive_opt_outs: RejectionDeriveOptOuts,
rejection: Option<syn::Path>,
) -> syn::Result<TokenStream> {
let extract_fields = extract_fields(&fields)?;
let extract_fields = extract_fields(&fields, &rejection)?;
let (rejection_ident, rejection) = if has_no_fields(&fields) {
let (rejection_ident, rejection) = if let Some(rejection) = rejection {
let rejection_ident = syn::parse_quote!(#rejection);
(rejection_ident, None)
} else if has_no_fields(&fields) {
(syn::parse_quote!(::std::convert::Infallible), None)
} else {
let rejection_ident = rejection_ident(&ident);
@ -140,7 +252,10 @@ fn rejection_ident(ident: &syn::Ident) -> syn::Type {
syn::parse_quote!(#ident)
}
fn extract_fields(fields: &syn::Fields) -> syn::Result<Vec<TokenStream>> {
fn extract_fields(
fields: &syn::Fields,
rejection: &Option<syn::Path>,
) -> syn::Result<Vec<TokenStream>> {
fields
.iter()
.enumerate()
@ -190,12 +305,18 @@ fn extract_fields(fields: &syn::Fields) -> syn::Result<Vec<TokenStream>> {
},
})
} else {
let map_err = if let Some(rejection) = rejection {
quote! { <#rejection as ::std::convert::From<_>>::from }
} else {
quote! { Self::Rejection::#rejection_variant_name }
};
Ok(quote_spanned! {ty_span=>
#member: {
::axum::extract::FromRequest::from_request(req)
.await
.map(#into_inner)
.map_err(Self::Rejection::#rejection_variant_name)?
.map_err(#map_err)?
},
})
}
@ -462,6 +583,8 @@ fn impl_struct_by_extracting_all_at_once(
ident: syn::Ident,
fields: syn::Fields,
path: syn::Path,
rejection: Option<syn::Path>,
generic_ident: Option<Ident>,
) -> syn::Result<TokenStream> {
let fields = match fields {
syn::Fields::Named(fields) => fields.named.into_iter(),
@ -482,23 +605,69 @@ fn impl_struct_by_extracting_all_at_once(
let path_span = path.span();
let associated_rejection_type = if let Some(rejection) = &rejection {
quote! { #rejection }
} else {
quote! {
<#path<Self> as ::axum::extract::FromRequest<B>>::Rejection
}
};
let rejection_bound = rejection.as_ref().map(|rejection| {
if generic_ident.is_some() {
quote! {
#rejection: ::std::convert::From<<#path<T> as ::axum::extract::FromRequest<B>>::Rejection>,
}
} else {
quote! {
#rejection: ::std::convert::From<<#path<Self> as ::axum::extract::FromRequest<B>>::Rejection>,
}
}
}).unwrap_or_default();
let impl_generics = if generic_ident.is_some() {
quote! { B, T }
} else {
quote! { B }
};
let type_generics = generic_ident
.is_some()
.then(|| quote! { <T> })
.unwrap_or_default();
let via_type_generics = if generic_ident.is_some() {
quote! { T }
} else {
quote! { Self }
};
let value_to_self = if generic_ident.is_some() {
quote! {
#ident(value)
}
} else {
quote! { value }
};
Ok(quote_spanned! {path_span=>
#[::axum::async_trait]
#[automatically_derived]
impl<B> ::axum::extract::FromRequest<B> for #ident
impl<#impl_generics> ::axum::extract::FromRequest<B> for #ident #type_generics
where
B: ::axum::body::HttpBody + ::std::marker::Send + 'static,
B::Data: ::std::marker::Send,
B::Error: ::std::convert::Into<::axum::BoxError>,
#path<#via_type_generics>: ::axum::extract::FromRequest<B>,
#rejection_bound
B: ::std::marker::Send,
{
type Rejection = <#path<Self> as ::axum::extract::FromRequest<B>>::Rejection;
type Rejection = #associated_rejection_type;
async fn from_request(
req: &mut ::axum::extract::RequestParts<B>,
) -> ::std::result::Result<Self, Self::Rejection> {
::axum::extract::FromRequest::<B>::from_request(req)
.await
.map(|#path(inner)| inner)
.map(|#path(value)| #value_to_self)
.map_err(::std::convert::From::from)
}
}
})
@ -508,6 +677,7 @@ fn impl_enum_by_extracting_all_at_once(
ident: syn::Ident,
variants: Punctuated<syn::Variant, Token![,]>,
path: syn::Path,
rejection: Option<syn::Path>,
) -> syn::Result<TokenStream> {
for variant in variants {
let FromRequestFieldAttr { via } = parse_field_attrs(&variant.attrs)?;
@ -535,6 +705,14 @@ fn impl_enum_by_extracting_all_at_once(
}
}
let associated_rejection_type = if let Some(rejection) = rejection {
quote! { #rejection }
} else {
quote! {
<#path<Self> as ::axum::extract::FromRequest<B>>::Rejection
}
};
let path_span = path.span();
Ok(quote_spanned! {path_span=>
@ -546,7 +724,7 @@ fn impl_enum_by_extracting_all_at_once(
B::Data: ::std::marker::Send,
B::Error: ::std::convert::Into<::axum::BoxError>,
{
type Rejection = <#path<Self> as ::axum::extract::FromRequest<B>>::Rejection;
type Rejection = #associated_rejection_type;
async fn from_request(
req: &mut ::axum::extract::RequestParts<B>,
@ -554,6 +732,7 @@ fn impl_enum_by_extracting_all_at_once(
::axum::extract::FromRequest::<B>::from_request(req)
.await
.map(|#path(inner)| inner)
.map_err(::std::convert::From::from)
}
}
})

View file

@ -11,7 +11,11 @@ pub(crate) struct FromRequestFieldAttr {
}
pub(crate) enum FromRequestContainerAttr {
Via(syn::Path),
Via {
path: syn::Path,
rejection: Option<syn::Path>,
},
Rejection(syn::Path),
RejectionDerive(kw::rejection_derive, RejectionDeriveOptOuts),
None,
}
@ -19,6 +23,7 @@ pub(crate) enum FromRequestContainerAttr {
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);
syn::custom_keyword!(Error);
@ -51,6 +56,7 @@ pub(crate) fn parse_container_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
// used to give more accurate error messages
@ -73,11 +79,18 @@ pub(crate) fn parse_container_attrs(
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));
} else {
out_rejection = Some((idx, rejection, path));
}
}
}
}
match (out_via, out_rejection_derive) {
(Some((via_idx, via, _)), Some((rejection_derive_idx, rejection_derive, _))) => {
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,
@ -90,11 +103,41 @@ pub(crate) fn parse_container_attrs(
))
}
}
(Some((_, _, path)), None) => Ok(FromRequestContainerAttr::Via(path)),
(None, Some((_, rejection_derive, opt_outs))) => Ok(
(
_,
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 {
path,
rejection: None,
}),
(Some((_, _, path)), None, Some((_, _, rejection))) => Ok(FromRequestContainerAttr::Via {
path,
rejection: Some(rejection),
}),
(None, Some((_, rejection_derive, opt_outs)), _) => Ok(
FromRequestContainerAttr::RejectionDerive(rejection_derive, opt_outs),
),
(None, None) => Ok(FromRequestContainerAttr::None),
(None, None, Some((_, _, rejection))) => Ok(FromRequestContainerAttr::Rejection(rejection)),
(None, None, None) => Ok(FromRequestContainerAttr::None),
}
}
@ -125,6 +168,10 @@ enum ContainerAttr {
via: kw::via,
path: syn::Path,
},
Rejection {
rejection: kw::rejection,
path: syn::Path,
},
RejectionDerive {
rejection_derive: kw::rejection_derive,
opt_outs: RejectionDeriveOptOuts,
@ -147,6 +194,13 @@ impl Parse for ContainerAttr {
rejection_derive,
opt_outs,
})
} else if lh.peek(kw::rejection) {
let rejection = input.parse::<kw::rejection>()?;
let content;
syn::parenthesized!(content in input);
content
.parse()
.map(|path| Self::Rejection { rejection, path })
} else {
Err(lh.error())
}

View file

@ -237,6 +237,56 @@ mod typed_path;
/// }
/// ```
///
/// 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,
/// },
/// Extension,
/// response::{Response, IntoResponse},
/// http::StatusCode,
/// headers::ContentType,
/// body::Bytes,
/// async_trait,
/// };
///
/// #[derive(FromRequest)]
/// #[from_request(rejection(MyRejection))]
/// struct MyExtractor {
/// state: Extension<String>,
/// body: String,
/// }
///
/// struct MyRejection(Response);
///
/// // This tells axum how to convert `Extension`'s rejections into `MyRejection`
/// impl From<ExtensionRejection> for MyRejection {
/// fn from(rejection: ExtensionRejection) -> Self {
/// // ...
/// # todo!()
/// }
/// }
///
/// // This tells axum how to convert `String`'s rejections into `MyRejection`
/// impl From<StringRejection> for MyRejection {
/// fn from(rejection: StringRejection) -> Self {
/// // ...
/// # todo!()
/// }
/// }
///
/// // All rejections must implement `IntoResponse`
/// impl IntoResponse for MyRejection {
/// fn into_response(self) -> Response {
/// self.0
/// }
/// }
/// ```
///
/// # The whole type at once
///
/// By using `#[from_request(via(...))]` on the container you can extract the whole type at once,
@ -259,9 +309,101 @@ mod typed_path;
/// The rejection will be the "via extractors"'s rejection. For the previous example that would be
/// [`axum::extract::rejection::ExtensionRejection`].
///
/// You can use a different rejection type with `#[from_request(rejection(YourType))]`:
///
/// ```
/// use axum_macros::FromRequest;
/// use axum::{
/// extract::{Extension, rejection::ExtensionRejection},
/// response::{IntoResponse, Response},
/// Json,
/// http::StatusCode,
/// };
/// use serde_json::json;
///
/// // This will extracted via `Extension::<State>::from_request`
/// #[derive(Clone, FromRequest)]
/// #[from_request(
/// via(Extension),
/// // Use your own rejection type
/// rejection(MyRejection),
/// )]
/// struct State {
/// // ...
/// }
///
/// struct MyRejection(Response);
///
/// // This tells axum how to convert `Extension`'s rejections into `MyRejection`
/// impl From<ExtensionRejection> for MyRejection {
/// fn from(rejection: ExtensionRejection) -> Self {
/// let response = (
/// StatusCode::INTERNAL_SERVER_ERROR,
/// Json(json!({ "error": "Something went wrong..." })),
/// ).into_response();
///
/// MyRejection(response)
/// }
/// }
///
/// // All rejections must implement `IntoResponse`
/// impl IntoResponse for MyRejection {
/// fn into_response(self) -> Response {
/// self.0
/// }
/// }
///
/// async fn handler(state: State) {}
/// ```
///
/// This allows you to wrap other extractors and easily customize the rejection:
///
/// ```
/// use axum_macros::FromRequest;
/// use axum::{
/// extract::{Extension, rejection::JsonRejection},
/// response::{IntoResponse, Response},
/// http::StatusCode,
/// };
/// use serde_json::json;
/// use serde::Deserialize;
///
/// // create an extractor that internally uses `axum::Json` but has a custom rejection
/// #[derive(FromRequest)]
/// #[from_request(via(axum::Json), rejection(MyRejection))]
/// struct MyJson<T>(T);
///
/// struct MyRejection(Response);
///
/// impl From<JsonRejection> for MyRejection {
/// fn from(rejection: JsonRejection) -> Self {
/// let response = (
/// StatusCode::INTERNAL_SERVER_ERROR,
/// axum::Json(json!({ "error": rejection.to_string() })),
/// ).into_response();
///
/// MyRejection(response)
/// }
/// }
///
/// impl IntoResponse for MyRejection {
/// fn into_response(self) -> Response {
/// self.0
/// }
/// }
///
/// #[derive(Deserialize)]
/// struct Payload {}
///
/// async fn handler(
/// // make sure to use `MyJson` and not `axum::Json`
/// MyJson(payload): MyJson<Payload>,
/// ) {}
/// ```
///
/// # Known limitations
///
/// Generics are currently not supported:
/// Generics are only supported on tuple structs with exactly on field. Thus this doesn't work
///
/// ```compile_fail
/// #[derive(axum_macros::FromRequest)]

View file

@ -1,5 +1,5 @@
error: `#[derive(FromRequest)] doesn't support generics
--> tests/from_request/fail/generic.rs:4:17
error: #[derive(FromRequest)] only supports generics on tuple structs that have exactly one field of the generic type
--> tests/from_request/fail/generic.rs:4:21
|
4 | struct Extractor<T>(Option<T>);
| ^^^
| ^^^^^^^^^

View file

@ -0,0 +1,11 @@
use axum::{body::Body, routing::get, Extension, Router};
use axum_macros::FromRequest;
#[derive(FromRequest, Clone)]
struct Extractor<T>(T);
async fn foo(_: Extractor<()>) {}
fn main() {
Router::<Body>::new().route("/", get(foo));
}

View file

@ -0,0 +1,29 @@
error: #[derive(FromRequest)] only supports generics when used with #[from_request(via)]
--> tests/from_request/fail/generic_without_via.rs:5:18
|
5 | struct Extractor<T>(T);
| ^
warning: unused import: `Extension`
--> tests/from_request/fail/generic_without_via.rs:1:38
|
1 | use axum::{body::Body, routing::get, Extension, Router};
| ^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
error[E0277]: the trait bound `fn(Extractor<()>) -> impl Future<Output = ()> {foo}: Handler<_, _>` is not satisfied
--> tests/from_request/fail/generic_without_via.rs:10:42
|
10 | Router::<Body>::new().route("/", get(foo));
| --- ^^^ the trait `Handler<_, _>` is not implemented for `fn(Extractor<()>) -> impl Future<Output = ()> {foo}`
| |
| required by a bound introduced by this call
|
= help: the trait `Handler<T, ReqBody>` is implemented for `Layered<S, T>`
note: required by a bound in `axum::routing::get`
--> $WORKSPACE/axum/src/routing/method_routing.rs
|
| top_level_handler_fn!(get, GET);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `axum::routing::get`
= note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,12 @@
use axum::{body::Body, routing::get, Extension, Router};
use axum_macros::FromRequest;
#[derive(FromRequest, Clone)]
#[from_request(rejection(Foo))]
struct Extractor<T>(T);
async fn foo(_: Extractor<()>) {}
fn main() {
Router::<Body>::new().route("/", get(foo));
}

View file

@ -0,0 +1,29 @@
error: #[derive(FromRequest)] only supports generics when used with #[from_request(via)]
--> tests/from_request/fail/generic_without_via_rejection.rs:6:18
|
6 | struct Extractor<T>(T);
| ^
warning: unused import: `Extension`
--> tests/from_request/fail/generic_without_via_rejection.rs:1:38
|
1 | use axum::{body::Body, routing::get, Extension, Router};
| ^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
error[E0277]: the trait bound `fn(Extractor<()>) -> impl Future<Output = ()> {foo}: Handler<_, _>` is not satisfied
--> tests/from_request/fail/generic_without_via_rejection.rs:11:42
|
11 | Router::<Body>::new().route("/", get(foo));
| --- ^^^ the trait `Handler<_, _>` is not implemented for `fn(Extractor<()>) -> impl Future<Output = ()> {foo}`
| |
| required by a bound introduced by this call
|
= help: the trait `Handler<T, ReqBody>` is implemented for `Layered<S, T>`
note: required by a bound in `axum::routing::get`
--> $WORKSPACE/axum/src/routing/method_routing.rs
|
| top_level_handler_fn!(get, GET);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `axum::routing::get`
= note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,12 @@
use axum::{body::Body, routing::get, Extension, Router};
use axum_macros::FromRequest;
#[derive(FromRequest, Clone)]
#[from_request(rejection_derive(!Error))]
struct Extractor<T>(T);
async fn foo(_: Extractor<()>) {}
fn main() {
Router::<Body>::new().route("/", get(foo));
}

View file

@ -0,0 +1,29 @@
error: #[derive(FromRequest)] only supports generics when used with #[from_request(via)]
--> tests/from_request/fail/generic_without_via_rejection_derive.rs:6:18
|
6 | struct Extractor<T>(T);
| ^
warning: unused import: `Extension`
--> tests/from_request/fail/generic_without_via_rejection_derive.rs:1:38
|
1 | use axum::{body::Body, routing::get, Extension, Router};
| ^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
error[E0277]: the trait bound `fn(Extractor<()>) -> impl Future<Output = ()> {foo}: Handler<_, _>` is not satisfied
--> tests/from_request/fail/generic_without_via_rejection_derive.rs:11:42
|
11 | Router::<Body>::new().route("/", get(foo));
| --- ^^^ the trait `Handler<_, _>` is not implemented for `fn(Extractor<()>) -> impl Future<Output = ()> {foo}`
| |
| required by a bound introduced by this call
|
= help: the trait `Handler<T, ReqBody>` is implemented for `Layered<S, T>`
note: required by a bound in `axum::routing::get`
--> $WORKSPACE/axum/src/routing/method_routing.rs
|
| top_level_handler_fn!(get, GET);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `axum::routing::get`
= note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,33 @@
use axum::{
extract::rejection::ExtensionRejection,
response::{IntoResponse, Response},
routing::get,
Router,
};
use axum_macros::FromRequest;
fn main() {
let _: Router = Router::new().route("/", get(handler).post(handler_result));
}
async fn handler(_: MyExtractor) {}
async fn handler_result(_: Result<MyExtractor, MyRejection>) {}
#[derive(FromRequest, Clone)]
#[from_request(rejection(MyRejection))]
enum MyExtractor {}
struct MyRejection {}
impl From<ExtensionRejection> for MyRejection {
fn from(_: ExtensionRejection) -> Self {
todo!()
}
}
impl IntoResponse for MyRejection {
fn into_response(self) -> Response {
todo!()
}
}

View file

@ -0,0 +1,37 @@
error: cannot use `rejection` without `via`
--> tests/from_request/fail/override_rejection_on_enum_without_via.rs:18:26
|
18 | #[from_request(rejection(MyRejection))]
| ^^^^^^^^^^^
error[E0277]: the trait bound `fn(MyExtractor) -> impl Future<Output = ()> {handler}: Handler<_, _>` is not satisfied
--> tests/from_request/fail/override_rejection_on_enum_without_via.rs:10:50
|
10 | let _: Router = Router::new().route("/", get(handler).post(handler_result));
| --- ^^^^^^^ the trait `Handler<_, _>` is not implemented for `fn(MyExtractor) -> impl Future<Output = ()> {handler}`
| |
| required by a bound introduced by this call
|
= help: the trait `Handler<T, ReqBody>` is implemented for `Layered<S, T>`
note: required by a bound in `axum::routing::get`
--> $WORKSPACE/axum/src/routing/method_routing.rs
|
| top_level_handler_fn!(get, GET);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `axum::routing::get`
= note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `fn(Result<MyExtractor, MyRejection>) -> impl Future<Output = ()> {handler_result}: Handler<_, _>` is not satisfied
--> tests/from_request/fail/override_rejection_on_enum_without_via.rs:10:64
|
10 | let _: Router = Router::new().route("/", get(handler).post(handler_result));
| ---- ^^^^^^^^^^^^^^ the trait `Handler<_, _>` is not implemented for `fn(Result<MyExtractor, MyRejection>) -> impl Future<Output = ()> {handler_result}`
| |
| required by a bound introduced by this call
|
= help: the trait `Handler<T, ReqBody>` is implemented for `Layered<S, T>`
note: required by a bound in `MethodRouter::<B>::post`
--> $WORKSPACE/axum/src/routing/method_routing.rs
|
| chained_handler_fn!(post, POST);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `MethodRouter::<B>::post`
= note: this error originates in the macro `chained_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,4 +1,4 @@
error: expected `via` or `rejection_derive`
error: expected one of: `via`, `rejection_derive`, `rejection`
--> tests/from_request/fail/unknown_attr_container.rs:4:16
|
4 | #[from_request(foo)]

View file

@ -0,0 +1,61 @@
use axum::{
async_trait,
extract::{rejection::ExtensionRejection, FromRequest, RequestParts},
http::StatusCode,
response::{IntoResponse, Response},
routing::get,
Extension, Router,
};
use axum_macros::FromRequest;
fn main() {
let _: Router = Router::new().route("/", get(handler).post(handler_result));
}
async fn handler(_: MyExtractor) {}
async fn handler_result(_: Result<MyExtractor, MyRejection>) {}
#[derive(FromRequest)]
#[from_request(rejection(MyRejection))]
struct MyExtractor {
one: Extension<String>,
#[from_request(via(Extension))]
two: String,
three: 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> {
todo!()
}
}
struct MyRejection {}
impl From<ExtensionRejection> for MyRejection {
fn from(_: ExtensionRejection) -> Self {
todo!()
}
}
impl From<(StatusCode, String)> for MyRejection {
fn from(_: (StatusCode, String)) -> Self {
todo!()
}
}
impl IntoResponse for MyRejection {
fn into_response(self) -> Response {
todo!()
}
}

View file

@ -0,0 +1,33 @@
use axum::{
extract::rejection::ExtensionRejection,
response::{IntoResponse, Response},
routing::get,
Router,
};
use axum_macros::FromRequest;
fn main() {
let _: Router = Router::new().route("/", get(handler).post(handler_result));
}
async fn handler(_: MyExtractor) {}
async fn handler_result(_: Result<MyExtractor, MyRejection>) {}
#[derive(FromRequest, Clone)]
#[from_request(via(axum::Extension), rejection(MyRejection))]
enum MyExtractor {}
struct MyRejection {}
impl From<ExtensionRejection> for MyRejection {
fn from(_: ExtensionRejection) -> Self {
todo!()
}
}
impl IntoResponse for MyRejection {
fn into_response(self) -> Response {
todo!()
}
}

View file

@ -0,0 +1,40 @@
use axum::{
extract::rejection::JsonRejection,
response::{IntoResponse, Response},
routing::get,
Router,
};
use axum_macros::FromRequest;
use serde::Deserialize;
fn main() {
let _: Router = Router::new().route("/", get(handler).post(handler_result));
}
#[derive(Deserialize)]
struct Payload {}
async fn handler(_: MyJson<Payload>) {}
async fn handler_result(_: Result<MyJson<Payload>, MyJsonRejection>) {}
#[derive(FromRequest)]
#[from_request(
via(axum::Json),
rejection(MyJsonRejection),
)]
struct MyJson<T>(T);
struct MyJsonRejection {}
impl From<JsonRejection> for MyJsonRejection {
fn from(_: JsonRejection) -> Self {
todo!()
}
}
impl IntoResponse for MyJsonRejection {
fn into_response(self) -> Response {
todo!()
}
}