-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* new(axum-extra): Added `WithRejection` base impl Based on @jplatte's version (#1116 (comment)), with slight changes - Using `From<E::Rejection>` to define the trait bound on a more concise way - Renamed variables to something more meaningfull * revert(axum-extra): Removed `with_rejection` feat * ref(axum-extra): Replaced `match` with `?` * tests(axum-extra): Added test for `WithRejection` * examples: Replaced custom `Json` extractor with `WithRejection` * docs(axum-extra): Added doc to `WithRejection` * fmt(cargo-check): removed whitespaces * fmt(customize-extractor-error): missing fmt * docs(axum-extra): doctest includes `Handler` test Co-authored-by: David Pedersen <[email protected]> * docs(axum-extra):` _ `-> `rejection` Co-authored-by: David Pedersen <[email protected]> * docs(axum-extra): fixed suggestions * fix(axum-extra): `WithRejection` manual trait impl * revert(customize-extractor-error): Undo example changes refs: d878eed , f9200bf * example(customize-extractor-error): Added reference to `WithRejection` * docs(axum-extra): Removed `customize-extractor-error` reference * fmt(axum-extra): cargo fmt * docs(axum-extra): Added `WithRejection` to CHANGELOG.md Co-authored-by: David Pedersen <[email protected]>
- Loading branch information
1 parent
01630cf
commit fb21561
Showing
4 changed files
with
174 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
use axum::async_trait; | ||
use axum::extract::{FromRequest, RequestParts}; | ||
use axum::response::IntoResponse; | ||
use std::fmt::Debug; | ||
use std::marker::PhantomData; | ||
use std::ops::{Deref, DerefMut}; | ||
|
||
/// Extractor for customizing extractor rejections | ||
/// | ||
/// `WithRejection` wraps another extractor and gives you the result. If the | ||
/// extraction fails, the `Rejection` is transformed into `R` and returned as a | ||
/// response | ||
/// | ||
/// `E` is expected to implement [`FromRequest`] | ||
/// | ||
/// `R` is expected to implement [`IntoResponse`] and [`From<E::Rejection>`] | ||
/// | ||
/// | ||
/// # Example | ||
/// | ||
/// ```rust | ||
/// use axum::extract::rejection::JsonRejection; | ||
/// use axum::response::{Response, IntoResponse}; | ||
/// use axum::Json; | ||
/// use axum_extra::extract::WithRejection; | ||
/// use serde::Deserialize; | ||
/// | ||
/// struct MyRejection { /* ... */ } | ||
/// | ||
/// impl From<JsonRejection> for MyRejection { | ||
/// fn from(rejection: JsonRejection) -> MyRejection { | ||
/// // ... | ||
/// # todo!() | ||
/// } | ||
/// } | ||
/// | ||
/// impl IntoResponse for MyRejection { | ||
/// fn into_response(self) -> Response { | ||
/// // ... | ||
/// # todo!() | ||
/// } | ||
/// } | ||
/// #[derive(Debug, Deserialize)] | ||
/// struct Person { /* ... */ } | ||
/// | ||
/// async fn handler( | ||
/// // If the `Json` extractor ever fails, `MyRejection` will be sent to the | ||
/// // client using the `IntoResponse` impl | ||
/// WithRejection(Json(Person), _): WithRejection<Json<Person>, MyRejection> | ||
/// ) { /* ... */ } | ||
/// # let _: axum::Router = axum::Router::new().route("/", axum::routing::get(handler)); | ||
/// ``` | ||
/// | ||
/// [`FromRequest`]: axum::extract::FromRequest | ||
/// [`IntoResponse`]: axum::response::IntoResponse | ||
/// [`From<E::Rejection>`]: std::convert::From | ||
pub struct WithRejection<E, R>(pub E, pub PhantomData<R>); | ||
|
||
impl<E, R> WithRejection<E, R> { | ||
/// Returns the wrapped extractor | ||
fn into_inner(self) -> E { | ||
self.0 | ||
} | ||
} | ||
|
||
impl<E, R> Debug for WithRejection<E, R> | ||
where | ||
E: Debug, | ||
{ | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
f.debug_tuple("WithRejection") | ||
.field(&self.0) | ||
.field(&self.1) | ||
.finish() | ||
} | ||
} | ||
|
||
impl<E, R> Clone for WithRejection<E, R> | ||
where | ||
E: Clone, | ||
{ | ||
fn clone(&self) -> Self { | ||
Self(self.0.clone(), self.1.clone()) | ||
} | ||
} | ||
|
||
impl<E, R> Copy for WithRejection<E, R> where E: Copy {} | ||
|
||
impl<E: Default, R> Default for WithRejection<E, R> { | ||
fn default() -> Self { | ||
Self(Default::default(), Default::default()) | ||
} | ||
} | ||
|
||
impl<E, R> Deref for WithRejection<E, R> { | ||
type Target = E; | ||
|
||
fn deref(&self) -> &Self::Target { | ||
&self.0 | ||
} | ||
} | ||
|
||
impl<E, R> DerefMut for WithRejection<E, R> { | ||
fn deref_mut(&mut self) -> &mut Self::Target { | ||
&mut self.0 | ||
} | ||
} | ||
|
||
#[async_trait] | ||
impl<B, E, R> FromRequest<B> for WithRejection<E, R> | ||
where | ||
B: Send, | ||
E: FromRequest<B>, | ||
R: From<E::Rejection> + IntoResponse, | ||
{ | ||
type Rejection = R; | ||
|
||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> { | ||
let extractor = req.extract::<E>().await?; | ||
Ok(WithRejection(extractor, PhantomData)) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use axum::http::Request; | ||
use axum::response::Response; | ||
|
||
use super::*; | ||
|
||
#[tokio::test] | ||
async fn extractor_rejection_is_transformed() { | ||
struct TestExtractor; | ||
struct TestRejection; | ||
|
||
#[async_trait] | ||
impl<B: Send> FromRequest<B> for TestExtractor { | ||
type Rejection = (); | ||
|
||
async fn from_request(_: &mut RequestParts<B>) -> Result<Self, Self::Rejection> { | ||
Err(()) | ||
} | ||
} | ||
|
||
impl IntoResponse for TestRejection { | ||
fn into_response(self) -> Response { | ||
().into_response() | ||
} | ||
} | ||
|
||
impl From<()> for TestRejection { | ||
fn from(_: ()) -> Self { | ||
TestRejection | ||
} | ||
} | ||
|
||
let mut req = RequestParts::new(Request::new(())); | ||
|
||
let result = req | ||
.extract::<WithRejection<TestExtractor, TestRejection>>() | ||
.await; | ||
|
||
assert!(matches!(result, Err(TestRejection))) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters