Allow specifying 'Status' in custom form errors.

Resolves #1694.
This commit is contained in:
Sergio Benitez 2023-04-05 09:56:49 -07:00
parent db96f670b7
commit 03433c10ea
4 changed files with 56 additions and 18 deletions

View File

@ -112,7 +112,7 @@ macro_rules! media_types {
docify!([
Returns @code{true} if the @[top-level] and sublevel types of
@code{self} are the same as those of @{"`MediaType::"}! $name
@{"`"}!.
@{"`"}!, i.e @{"`"} @{$t}! @[/]! @{$s}! $(; @{$k}! @[=]! @{$v}!)* @{"`"}!.
];
#[inline(always)]
pub fn $check(&self) -> bool {

View File

@ -160,6 +160,7 @@ pub struct Error<'v> {
/// * [`AddrParseError`] => [`ErrorKind::Addr`]
/// * [`io::Error`] => [`ErrorKind::Io`]
/// * `Box<dyn std::error::Error + Send` => [`ErrorKind::Custom`]
/// * `(Status, Box<dyn std::error::Error + Send)` => [`ErrorKind::Custom`]
#[derive(Debug)]
#[non_exhaustive]
pub enum ErrorKind<'v> {
@ -192,8 +193,9 @@ pub enum ErrorKind<'v> {
Unexpected,
/// An unknown entity was received.
Unknown,
/// A custom error occurred.
Custom(Box<dyn std::error::Error + Send>),
/// A custom error occurred. Status defaults to
/// [`Status::UnprocessableEntity`] if one is not directly specified.
Custom(Status, Box<dyn std::error::Error + Send>),
/// An error while parsing a multipart form occurred.
Multipart(multer::Error),
/// A string was invalid UTF-8.
@ -350,6 +352,9 @@ impl<'v> Errors<'v> {
/// status that is set by the [`Form`](crate::form::Form) data guard on
/// failure.
///
/// See [`Error::status()`] for the corresponding status code of each
/// [`Error`] variant.
///
/// # Example
///
/// ```rust
@ -677,17 +682,19 @@ impl<'v> Error<'v> {
.unwrap_or(false)
}
/// Returns the most reasonable `Status` associated with this error. These
/// are:
/// Returns the most reasonable [`Status`] associated with this error.
///
/// * **`PayloadTooLarge`** if the error kind is:
/// For an [`ErrorKind::Custom`], this is the variant's `Status`, which
/// defaults to [`Status::UnprocessableEntity`]. For all others, it is:
///
/// * **`PayloadTooLarge`** if the [error kind](ErrorKind) is:
/// - `InvalidLength` with min of `None`
/// - `Multpart(FieldSizeExceeded | StreamSizeExceeded)`
/// * **`InternalServerError`** if the error kind is:
/// - `Multipart(FieldSizeExceeded)` or `Multipart(StreamSizeExceeded)`
/// * **`InternalServerError`** if the [error kind](ErrorKind) is:
/// - `Unknown`
/// * **`BadRequest`** if the error kind is:
/// * **`BadRequest`** if the [error kind](ErrorKind) is:
/// - `Io` with an `entity` of `Form`
/// * **`UnprocessableEntity`** otherwise
/// * **`UnprocessableEntity`** for all other variants
///
/// # Example
///
@ -716,11 +723,12 @@ impl<'v> Error<'v> {
use multer::Error::*;
match self.kind {
InvalidLength { min: None, .. }
| InvalidLength { min: None, .. }
| Multipart(FieldSizeExceeded { .. })
| Multipart(StreamSizeExceeded { .. }) => Status::PayloadTooLarge,
Unknown => Status::InternalServerError,
Io(_) | _ if self.entity == Entity::Form => Status::BadRequest,
Custom(status, _) => status,
_ => Status::UnprocessableEntity
}
}
@ -846,7 +854,7 @@ impl fmt::Display for ErrorKind<'_> {
ErrorKind::Missing => "missing".fmt(f)?,
ErrorKind::Unexpected => "unexpected".fmt(f)?,
ErrorKind::Unknown => "unknown internal error".fmt(f)?,
ErrorKind::Custom(e) => e.fmt(f)?,
ErrorKind::Custom(_, e) => e.fmt(f)?,
ErrorKind::Multipart(e) => write!(f, "invalid multipart: {}", e)?,
ErrorKind::Utf8(e) => write!(f, "invalid UTF-8: {}", e)?,
ErrorKind::Int(e) => write!(f, "invalid integer: {}", e)?,
@ -874,7 +882,7 @@ impl crate::http::ext::IntoOwned for ErrorKind<'_> {
Missing => Missing,
Unexpected => Unexpected,
Unknown => Unknown,
Custom(e) => Custom(e),
Custom(s, e) => Custom(s, e),
Multipart(e) => Multipart(e),
Utf8(e) => Utf8(e),
Int(e) => Int(e),
@ -892,7 +900,6 @@ impl crate::http::ext::IntoOwned for ErrorKind<'_> {
}
}
impl<'a, 'b> PartialEq<ErrorKind<'b>> for ErrorKind<'a> {
fn eq(&self, other: &ErrorKind<'b>) -> bool {
use ErrorKind::*;
@ -904,7 +911,7 @@ impl<'a, 'b> PartialEq<ErrorKind<'b>> for ErrorKind<'a> {
(Duplicate, Duplicate) => true,
(Missing, Missing) => true,
(Unexpected, Unexpected) => true,
(Custom(_), Custom(_)) => true,
(Custom(a, _), Custom(b, _)) => a == b,
(Multipart(a), Multipart(b)) => a == b,
(Utf8(a), Utf8(b)) => a == b,
(Int(a), Int(b)) => a == b,
@ -954,6 +961,17 @@ impl<'a, 'v: 'a, const N: usize> From<&'static [Cow<'v, str>; N]> for ErrorKind<
}
}
impl<'a> From<Box<dyn std::error::Error + Send>> for ErrorKind<'a> {
fn from(e: Box<dyn std::error::Error + Send>) -> Self {
ErrorKind::Custom(Status::UnprocessableEntity, e)
}
}
impl<'a> From<(Status, Box<dyn std::error::Error + Send>)> for ErrorKind<'a> {
fn from((status, e): (Status, Box<dyn std::error::Error + Send>)) -> Self {
ErrorKind::Custom(status, e)
}
}
macro_rules! impl_from_for {
(<$l:lifetime> $T:ty => $V:ty as $variant:ident) => (
@ -971,7 +989,6 @@ impl_from_for!(<'a> ParseFloatError => ErrorKind<'a> as Float);
impl_from_for!(<'a> ParseBoolError => ErrorKind<'a> as Bool);
impl_from_for!(<'a> AddrParseError => ErrorKind<'a> as Addr);
impl_from_for!(<'a> io::Error => ErrorKind<'a> as Io);
impl_from_for!(<'a> Box<dyn std::error::Error + Send> => ErrorKind<'a> as Custom);
impl fmt::Display for Entity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@ -1010,7 +1027,7 @@ impl Entity {
| ErrorKind::Int(_)
| ErrorKind::Float(_)
| ErrorKind::Bool(_)
| ErrorKind::Custom(_)
| ErrorKind::Custom(..)
| ErrorKind::Addr(_) => Entity::Value,
| ErrorKind::Duplicate

View File

@ -56,6 +56,26 @@ use crate::form::prelude::*;
/// can access fields of `T` transparently through a `Form<T>`, as seen above
/// with `user_input.value`.
///
/// ## Errors
///
/// A `Form<T>` data guard may fail, forward, or succeed.
///
/// If a request's content-type is neither [`ContentType::Form`] nor
/// [`ContentType::FormData`], the guard **forwards**.
///
/// If the request `ContentType` _does_ identify as a form but the form data
/// does not parse as `T`, according to `T`'s [`FromForm`] implementation, the
/// guard **fails**. The `Failure` variant contains of the [`Errors`] emitted by
/// `T`'s `FromForm` parser. If the error is not caught by a
/// [`form::Result<T>`](Result) or `Option<Form<T>>` data guard, the status code
/// is set to [`Errors::status()`], and the corresponding error catcher is
/// called.
///
/// Otherwise the guard **succeeds**.
///
/// [`ContentType::Form`]: crate::http::ContentType::Form
/// [`ContentType::FormData`]: crate::http::ContentType::FormData
///
/// ## Data Limits
///
/// The total amount of data accepted by the `Form` data guard is limited by the

View File

@ -45,7 +45,8 @@
//! * `map[k:1]=Bob`
//! * `people[bob]nickname=Stan`
//!
//! See [`FromForm`] for full details on push-parsing and complete examples.
//! See [`FromForm`] for full details on push-parsing and complete examples, and
//! [`Form`] for how to accept forms in a request handler.
// ## Maps w/named Fields (`struct`)
//