diff --git a/CHANGELOG.md b/CHANGELOG.md index 366306a82a4..ee23bcf3346 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # Unreleased - Make `FromRequest` default to being generic over `axum::body::Body` ([#146](/~https://github.com/tokio-rs/axum/pull/146)) +- Implement `std::error::Error` for all rejections ## Breaking changes diff --git a/src/extract/rejection.rs b/src/extract/rejection.rs index 44d1e0139a9..a2382db19b6 100644 --- a/src/extract/rejection.rs +++ b/src/extract/rejection.rs @@ -125,15 +125,24 @@ impl InvalidUrlParam { } } +impl std::fmt::Display for InvalidUrlParam { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Invalid URL param. Expected something of type `{}`", + self.type_name + ) + } +} + +impl std::error::Error for InvalidUrlParam {} + impl IntoResponse for InvalidUrlParam { type Body = Full; type BodyError = Infallible; fn into_response(self) -> http::Response { - let mut res = http::Response::new(Full::from(format!( - "Invalid URL param. Expected something of type `{}`", - self.type_name - ))); + let mut res = http::Response::new(Full::from(self.to_string())); *res.status_mut() = http::StatusCode::BAD_REQUEST; res } @@ -155,12 +164,20 @@ impl IntoResponse for InvalidPathParam { type BodyError = Infallible; fn into_response(self) -> http::Response { - let mut res = http::Response::new(Full::from(format!("Invalid URL param. {}", self.0))); + let mut res = http::Response::new(Full::from(self.to_string())); *res.status_mut() = http::StatusCode::BAD_REQUEST; res } } +impl std::fmt::Display for InvalidPathParam { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Invalid URL param. {}", self.0) + } +} + +impl std::error::Error for InvalidPathParam {} + /// Rejection type for extractors that deserialize query strings if the input /// couldn't be deserialized into the target type. #[derive(Debug)] @@ -186,15 +203,24 @@ impl IntoResponse for FailedToDeserializeQueryString { type BodyError = Infallible; fn into_response(self) -> http::Response { - let mut res = http::Response::new(Full::from(format!( - "Failed to deserialize query string. Expected something of type `{}`. Error: {}", - self.type_name, self.error, - ))); + let mut res = http::Response::new(Full::from(self.to_string())); *res.status_mut() = http::StatusCode::BAD_REQUEST; res } } +impl std::fmt::Display for FailedToDeserializeQueryString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Failed to deserialize query string. Expected something of type `{}`. Error: {}", + self.type_name, self.error, + ) + } +} + +impl std::error::Error for FailedToDeserializeQueryString {} + composite_rejection! { /// Rejection used for [`Query`](super::Query). /// @@ -321,6 +347,34 @@ where } } +impl std::fmt::Display for ContentLengthLimitRejection +where + T: std::fmt::Display, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::PayloadTooLarge(inner) => inner.fmt(f), + Self::LengthRequired(inner) => inner.fmt(f), + Self::HeadersAlreadyExtracted(inner) => inner.fmt(f), + Self::Inner(inner) => inner.fmt(f), + } + } +} + +impl std::error::Error for ContentLengthLimitRejection +where + T: std::error::Error + 'static, +{ + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::PayloadTooLarge(inner) => Some(inner), + Self::LengthRequired(inner) => Some(inner), + Self::HeadersAlreadyExtracted(inner) => Some(inner), + Self::Inner(inner) => Some(inner), + } + } +} + /// Rejection used for [`TypedHeader`](super::TypedHeader). #[cfg(feature = "headers")] #[cfg_attr(docsrs, doc(cfg(feature = "headers")))] @@ -337,8 +391,24 @@ impl IntoResponse for TypedHeaderRejection { type BodyError = Infallible; fn into_response(self) -> http::Response { - let mut res = format!("{} ({})", self.err, self.name).into_response(); + let mut res = self.to_string().into_response(); *res.status_mut() = http::StatusCode::BAD_REQUEST; res } } + +#[cfg(feature = "headers")] +#[cfg_attr(docsrs, doc(cfg(feature = "headers")))] +impl std::fmt::Display for TypedHeaderRejection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} ({})", self.err, self.name) + } +} + +#[cfg(feature = "headers")] +#[cfg_attr(docsrs, doc(cfg(feature = "headers")))] +impl std::error::Error for TypedHeaderRejection { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.err) + } +} diff --git a/src/macros.rs b/src/macros.rs index daf4d0dd9f5..8bed0eb3d63 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -58,6 +58,14 @@ macro_rules! define_rejection { res } } + + impl std::fmt::Display for $name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", $body) + } + } + + impl std::error::Error for $name {} }; ( @@ -90,6 +98,18 @@ macro_rules! define_rejection { res } } + + impl std::fmt::Display for $name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", $body) + } + } + + impl std::error::Error for $name { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.0) + } + } }; } @@ -132,5 +152,25 @@ macro_rules! composite_rejection { } } )+ + + impl std::fmt::Display for $name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + $( + Self::$variant(inner) => write!(f, "{}", inner), + )+ + } + } + } + + impl std::error::Error for $name { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + $( + Self::$variant(inner) => Some(inner), + )+ + } + } + } }; }