//! Rejection response types. use super::IntoResponse; use crate::body::Body; use tower::BoxError; macro_rules! define_rejection { ( #[status = $status:ident] #[body = $body:expr] $(#[$m:meta])* pub struct $name:ident; ) => { $(#[$m])* #[derive(Debug)] #[non_exhaustive] pub struct $name; impl IntoResponse for $name { fn into_response(self) -> http::Response { let mut res = http::Response::new(Body::from($body)); *res.status_mut() = http::StatusCode::$status; res } } }; ( #[status = $status:ident] #[body = $body:expr] $(#[$m:meta])* pub struct $name:ident (BoxError); ) => { $(#[$m])* #[derive(Debug)] pub struct $name(pub(super) tower::BoxError); impl $name { pub(super) fn from_err(err: E) -> Self where E: Into, { Self(err.into()) } } impl IntoResponse for $name { fn into_response(self) -> http::Response { let mut res = http::Response::new(Body::from(format!(concat!($body, ": {}"), self.0))); *res.status_mut() = http::StatusCode::$status; res } } }; } define_rejection! { #[status = BAD_REQUEST] #[body = "Query string was invalid or missing"] /// Rejection type for [`Query`](super::Query). pub struct QueryStringMissing; } define_rejection! { #[status = BAD_REQUEST] #[body = "Failed to parse the response body as JSON"] /// Rejection type for [`Json`](super::Json). pub struct InvalidJsonBody(BoxError); } define_rejection! { #[status = BAD_REQUEST] #[body = "Expected request with `Content-Type: application/json`"] /// Rejection type for [`Json`](super::Json) used if the `Content-Type` /// header is missing. pub struct MissingJsonContentType; } define_rejection! { #[status = INTERNAL_SERVER_ERROR] #[body = "Missing request extension"] /// Rejection type for [`Extension`](super::Extension) if an expected /// request extension was not found. pub struct MissingExtension; } define_rejection! { #[status = BAD_REQUEST] #[body = "Failed to buffer the request body"] /// Rejection type for extractors that buffer the request body. Used if the /// request body cannot be buffered due to an error. pub struct FailedToBufferBody(BoxError); } define_rejection! { #[status = BAD_REQUEST] #[body = "Response body didn't contain valid UTF-8"] /// Rejection type used when buffering the request into a [`String`] if the /// body doesn't contain valid UTF-8. pub struct InvalidUtf8(BoxError); } define_rejection! { #[status = PAYLOAD_TOO_LARGE] #[body = "Request payload is too large"] /// Rejection type for [`ContentLengthLimit`](super::ContentLengthLimit) if /// the request body is too large. pub struct PayloadTooLarge; } define_rejection! { #[status = LENGTH_REQUIRED] #[body = "Content length header is required"] /// Rejection type for [`ContentLengthLimit`](super::ContentLengthLimit) if /// the request is missing the `Content-Length` header or it is invalid. pub struct LengthRequired; } define_rejection! { #[status = INTERNAL_SERVER_ERROR] #[body = "No url params found for matched route. This is a bug in axum. Please open an issue"] /// Rejection type for [`UrlParamsMap`](super::UrlParamsMap) and /// [`UrlParams`](super::UrlParams) if you try and extract the URL params /// more than once. pub struct MissingRouteParams; } define_rejection! { #[status = INTERNAL_SERVER_ERROR] #[body = "Cannot have two request body extractors for a single handler"] /// Rejection type used if you try and extract the request body more than /// once. pub struct BodyAlreadyExtracted; } define_rejection! { #[status = INTERNAL_SERVER_ERROR] #[body = "Cannot have two `Request<_>` extractors for a single handler"] /// Rejection type used if you try and extract the request more than once. pub struct RequestAlreadyExtracted; } define_rejection! { #[status = BAD_REQUEST] #[body = "Form requests must have `Content-Type: x-www-form-urlencoded`"] /// Rejection type used if you try and extract the request more than once. pub struct InvalidFormContentType; } /// Rejection type for [`UrlParams`](super::UrlParams) if the capture route /// param didn't have the expected type. #[derive(Debug)] pub struct InvalidUrlParam { type_name: &'static str, } impl InvalidUrlParam { pub(super) fn new() -> Self { InvalidUrlParam { type_name: std::any::type_name::(), } } } impl IntoResponse for InvalidUrlParam { fn into_response(self) -> http::Response { let mut res = http::Response::new(Body::from(format!( "Invalid URL param. Expected something of type `{}`", self.type_name ))); *res.status_mut() = http::StatusCode::BAD_REQUEST; res } } /// Rejection type for extractors that deserialize query strings if the input /// couldn't be deserialized into the target type. #[derive(Debug)] pub struct FailedToDeserializeQueryString { error: BoxError, type_name: &'static str, } impl FailedToDeserializeQueryString { pub(super) fn new(error: E) -> Self where E: Into, { FailedToDeserializeQueryString { error: error.into(), type_name: std::any::type_name::(), } } } impl IntoResponse for FailedToDeserializeQueryString { fn into_response(self) -> http::Response { let mut res = http::Response::new(Body::from(format!( "Failed to deserialize query string. Expected something of type `{}`. Error: {}", self.type_name, self.error, ))); *res.status_mut() = http::StatusCode::BAD_REQUEST; res } } macro_rules! composite_rejection { ( $(#[$m:meta])* pub enum $name:ident { $($variant:ident),+ $(,)? } ) => { $(#[$m])* #[derive(Debug)] #[non_exhaustive] pub enum $name { $( #[allow(missing_docs)] $variant($variant) ),+ } impl IntoResponse for $name { fn into_response(self) -> http::Response { match self { $( Self::$variant(inner) => inner.into_response(), )+ } } } $( impl From<$variant> for $name { fn from(inner: $variant) -> Self { Self::$variant(inner) } } )+ }; } composite_rejection! { /// Rejection used for [`Query`](super::Query). /// /// Contains one variant for each way the [`Query`](super::Query) extractor /// can fail. pub enum QueryRejection { QueryStringMissing, FailedToDeserializeQueryString, } } composite_rejection! { /// Rejection used for [`Form`](super::Form). /// /// Contains one variant for each way the [`Form`](super::Form) extractor /// can fail. pub enum FormRejection { InvalidFormContentType, QueryStringMissing, FailedToDeserializeQueryString, FailedToBufferBody, BodyAlreadyExtracted, } } composite_rejection! { /// Rejection used for [`Json`](super::Json). /// /// Contains one variant for each way the [`Json`](super::Json) extractor /// can fail. pub enum JsonRejection { InvalidJsonBody, MissingJsonContentType, BodyAlreadyExtracted, } } composite_rejection! { /// Rejection used for [`UrlParams`](super::UrlParams). /// /// Contains one variant for each way the [`UrlParams`](super::UrlParams) extractor /// can fail. pub enum UrlParamsRejection { InvalidUrlParam, MissingRouteParams, } } composite_rejection! { /// Rejection used for [`Bytes`](bytes::Bytes). /// /// Contains one variant for each way the [`Bytes`](bytes::Bytes) extractor /// can fail. pub enum BytesRejection { BodyAlreadyExtracted, FailedToBufferBody, } } composite_rejection! { /// Rejection used for [`String`]. /// /// Contains one variant for each way the [`String`] extractor can fail. pub enum StringRejection { BodyAlreadyExtracted, FailedToBufferBody, InvalidUtf8, } } /// Rejection used for [`ContentLengthLimit`](super::ContentLengthLimit). /// /// Contains one variant for each way the /// [`ContentLengthLimit`](super::ContentLengthLimit) extractor can fail. #[derive(Debug)] #[non_exhaustive] pub enum ContentLengthLimitRejection { #[allow(missing_docs)] PayloadTooLarge(PayloadTooLarge), #[allow(missing_docs)] LengthRequired(LengthRequired), #[allow(missing_docs)] Inner(T), } impl IntoResponse for ContentLengthLimitRejection where T: IntoResponse, { fn into_response(self) -> http::Response { match self { Self::PayloadTooLarge(inner) => inner.into_response(), Self::LengthRequired(inner) => inner.into_response(), Self::Inner(inner) => inner.into_response(), } } } /// Rejection used for [`TypedHeader`](super::TypedHeader). #[cfg(feature = "headers")] #[cfg_attr(docsrs, doc(cfg(feature = "headers")))] #[derive(Debug)] pub struct TypedHeaderRejection { pub(super) name: &'static http::header::HeaderName, pub(super) err: headers::Error, } #[cfg(feature = "headers")] #[cfg_attr(docsrs, doc(cfg(feature = "headers")))] impl IntoResponse for TypedHeaderRejection { fn into_response(self) -> http::Response { let mut res = format!("{} ({})", self.err, self.name).into_response(); *res.status_mut() = http::StatusCode::BAD_REQUEST; res } }