use actix_web::{
error::{PayloadError, ResponseError},
http::StatusCode,
HttpResponse,
};
pub(crate) trait ResultExt<T, E> {
fn or_raise(self, kind: ErrorKind) -> Result<T, Error>
where
Self: Sized,
E: std::error::Error + Send + Sync + 'static;
}
impl<T, E> ResultExt<T, E> for Result<T, E> {
fn or_raise(self, kind: ErrorKind) -> Result<T, Error>
where
Self: Sized,
E: std::error::Error + Send + Sync + 'static,
{
self.map_err(|error| Error::new_with(kind, error))
}
}
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
}
impl Error {
pub(crate) fn new(kind: ErrorKind) -> Self {
Self { kind, source: None }
}
pub(crate) fn new_with<E>(kind: ErrorKind, error: E) -> Self
where
E: std::error::Error + Send + Sync + 'static,
{
Self {
kind,
source: Some(Box::new(error)),
}
}
pub(crate) fn new_with_unsend<E>(kind: ErrorKind, error: E) -> Self
where
E: std::error::Error + 'static,
{
Self {
kind,
source: Some(Box::new(StringError::new(&error))),
}
}
pub fn kind(&self) -> ErrorKind {
self.kind
}
}
#[derive(Copy, Clone, Debug)]
pub enum ErrorKind {
Multipart,
ParseField,
ParseInt,
ParseFloat,
ContentDisposition,
Field,
FieldCount,
FieldSize,
FieldType,
Filename,
FileCount,
FileSize,
Panicked,
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.kind {
ErrorKind::Multipart => f.write_str("Failed to read multipart body"),
ErrorKind::ParseField => f.write_str("Failed to parse multipart field"),
ErrorKind::ParseInt => f.write_str("Failed to parse int"),
ErrorKind::ParseFloat => f.write_str("Failed to parse float"),
ErrorKind::ContentDisposition => f.write_str("Failed to parse Content-Disposition"),
ErrorKind::Field => f.write_str("Failed to parse field name"),
ErrorKind::FieldCount => f.write_str("Too many fields in request"),
ErrorKind::FieldSize => f.write_str("Field too large"),
ErrorKind::FieldType => f.write_str("Found field with unexpected name or type"),
ErrorKind::Filename => f.write_str("Filename is missing"),
ErrorKind::FileCount => f.write_str("Too many files in request"),
ErrorKind::FileSize => f.write_str("File too large"),
ErrorKind::Panicked => f.write_str("Task panicked"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source.as_ref().map(|e| e.as_ref() as _)
}
}
#[derive(Debug)]
pub enum MultipartError {
NoContentDisposition,
NoContentType,
ParseContentType,
Boundary,
Nested,
Incomplete,
NotConsumed,
DuplicateField(String),
MissingField(String),
UnsupportedField(String),
}
impl std::fmt::Display for MultipartError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NoContentDisposition => f.write_str("No Content-Disposition `form-data` header"),
Self::NoContentType => f.write_str("No Content-Type `form-data` header"),
Self::ParseContentType => {
f.write_str("Failed to parse Content-Type `form-data` header")
}
Self::Boundary => f.write_str("Multipart boundary is missing"),
Self::Nested => f.write_str("Nested multipart is not supported"),
Self::Incomplete => f.write_str("Multipart stream is incomplete"),
Self::NotConsumed => f.write_str("Multipart stream was not consumed"),
Self::DuplicateField(field) => write!(f, "Duplicate field with name `{field}` found"),
Self::MissingField(field) => write!(f, "Field with name `{field}` is required"),
Self::UnsupportedField(field) => {
write!(f, "Found unsupported field with name `{field}`")
}
}
}
}
impl std::error::Error for MultipartError {}
#[derive(Debug)]
pub(crate) struct StringError {
display: String,
source: Option<Box<StringError>>,
}
impl StringError {
fn new(error: &(dyn std::error::Error + 'static)) -> Self {
let display = format!("{error}");
let source = error.source().map(StringError::new).map(Box::new);
Self { display, source }
}
}
impl std::fmt::Display for StringError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.display)
}
}
impl std::error::Error for StringError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source.as_ref().map(|e| e as _)
}
}
#[derive(Debug)]
pub struct FieldError {
name: String,
source: StringError,
}
impl FieldError {
fn new(name: String, source: actix_web::error::Error) -> Self {
Self {
name,
source: StringError::new(&source),
}
}
}
impl std::fmt::Display for FieldError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Failed processing field with name `{}`", self.name)
}
}
impl std::error::Error for FieldError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.source)
}
}
impl From<actix_multipart::MultipartError> for Error {
fn from(value: actix_multipart::MultipartError) -> Self {
match value {
actix_multipart::MultipartError::ContentDispositionMissing => {
Error::new_with(ErrorKind::Multipart, MultipartError::NoContentDisposition)
}
actix_multipart::MultipartError::ContentTypeMissing => {
Error::new_with(ErrorKind::Multipart, MultipartError::NoContentType)
}
actix_multipart::MultipartError::ContentTypeParse => {
Error::new_with(ErrorKind::Multipart, MultipartError::ParseContentType)
}
actix_multipart::MultipartError::BoundaryMissing => {
Error::new_with(ErrorKind::Multipart, MultipartError::Boundary)
}
actix_multipart::MultipartError::Nested => {
Error::new_with(ErrorKind::Multipart, MultipartError::Nested)
}
actix_multipart::MultipartError::Incomplete => {
Error::new_with(ErrorKind::Multipart, MultipartError::Incomplete)
}
actix_multipart::MultipartError::Parse(e) => Error::new_with(ErrorKind::Multipart, e),
actix_multipart::MultipartError::Payload(e) => Error::new_with(ErrorKind::Multipart, e),
actix_multipart::MultipartError::NotConsumed => {
Error::new_with(ErrorKind::Multipart, MultipartError::NotConsumed)
}
actix_multipart::MultipartError::Field { name, source } => {
Error::new_with(ErrorKind::Multipart, FieldError::new(name, source))
}
actix_multipart::MultipartError::DuplicateField(s) => {
Error::new_with(ErrorKind::Multipart, MultipartError::DuplicateField(s))
}
actix_multipart::MultipartError::MissingField(s) => {
Error::new_with(ErrorKind::Multipart, MultipartError::MissingField(s))
}
actix_multipart::MultipartError::UnknownField(s) => {
Error::new_with(ErrorKind::Multipart, MultipartError::UnsupportedField(s))
}
e => Error::new_with_unsend(ErrorKind::Multipart, e),
}
}
}
impl ResponseError for Error {
fn status_code(&self) -> StatusCode {
if let Some(source) = &self.source {
if let Some(payload) = source.downcast_ref::<PayloadError>() {
return payload.status_code();
}
}
StatusCode::BAD_REQUEST
}
fn error_response(&self) -> HttpResponse {
if let Some(source) = &self.source {
if source.is::<tokio::task::JoinError>() {
return HttpResponse::InternalServerError().finish();
}
if let Some(payload) = source.downcast_ref::<PayloadError>() {
return payload.error_response();
}
}
HttpResponse::BadRequest().finish()
}
}
#[cfg(test)]
mod tests {
use super::Error;
#[test]
fn assert_send() {
fn is_send<E: Send>() {}
is_send::<Error>();
}
#[test]
fn assert_sync() {
fn is_sync<E: Sync>() {}
is_sync::<Error>()
}
#[test]
fn assert_error() {
fn is_error<E: std::error::Error>() {}
is_error::<Error>()
}
}