use axum::async_trait; use axum::extract::{FromRequest, FromRequestParts}; use axum::response::IntoResponse; use http::request::Parts; use http::Request; 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`] /// /// /// # 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 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, MyRejection> /// ) { /* ... */ } /// # let _: axum::Router = axum::Router::new().route("/", axum::routing::get(handler)); /// ``` /// /// [`FromRequest`]: axum::extract::FromRequest /// [`IntoResponse`]: axum::response::IntoResponse /// [`From`]: std::convert::From pub struct WithRejection(pub E, pub PhantomData); impl WithRejection { /// Returns the wrapped extractor pub fn into_inner(self) -> E { self.0 } } impl Debug for WithRejection 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 Clone for WithRejection where E: Clone, { fn clone(&self) -> Self { Self(self.0.clone(), self.1) } } impl Copy for WithRejection where E: Copy {} impl Default for WithRejection { fn default() -> Self { Self(Default::default(), Default::default()) } } impl Deref for WithRejection { type Target = E; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for WithRejection { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } #[async_trait] impl FromRequest for WithRejection where B: Send + 'static, S: Send + Sync, E: FromRequest, R: From + IntoResponse, { type Rejection = R; async fn from_request(req: Request, state: &S) -> Result { let extractor = E::from_request(req, state).await?; Ok(WithRejection(extractor, PhantomData)) } } #[async_trait] impl FromRequestParts for WithRejection where S: Send + Sync, E: FromRequestParts, R: From + IntoResponse, { type Rejection = R; async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { let extractor = E::from_request_parts(parts, state).await?; Ok(WithRejection(extractor, PhantomData)) } } #[cfg(test)] mod tests { use axum::extract::FromRequestParts; use axum::http::Request; use axum::response::Response; use http::request::Parts; use super::*; #[tokio::test] async fn extractor_rejection_is_transformed() { struct TestExtractor; struct TestRejection; #[async_trait] impl FromRequestParts for TestExtractor { type Rejection = (); async fn from_request_parts( _parts: &mut Parts, _: &S, ) -> Result { Err(()) } } impl IntoResponse for TestRejection { fn into_response(self) -> Response { ().into_response() } } impl From<()> for TestRejection { fn from(_: ()) -> Self { TestRejection } } let req = Request::new(()); let result = WithRejection::::from_request(req, &()).await; assert!(matches!(result, Err(TestRejection))); let (mut parts, _) = Request::new(()).into_parts(); let result = WithRejection::::from_request_parts(&mut parts, &()) .await; assert!(matches!(result, Err(TestRejection))); } }