feat: better error handling

This commit is contained in:
Zomatree
2025-07-01 19:06:38 +01:00
parent ed22b3a5ce
commit 3d6f39a0eb
17 changed files with 336 additions and 55 deletions

View File

@@ -2,10 +2,9 @@ use axum::{http::StatusCode, response::IntoResponse, Json};
use crate::{Error, ErrorType};
/// HTTP response builder for Error enum
impl IntoResponse for Error {
fn into_response(self) -> axum::response::Response {
let status = match self.error_type {
impl Error {
pub fn axum_status(&self) -> StatusCode {
match self.error_type {
ErrorType::LabelMe => StatusCode::INTERNAL_SERVER_ERROR,
ErrorType::AlreadyOnboarded => StatusCode::FORBIDDEN,
@@ -74,7 +73,10 @@ impl IntoResponse for Error {
ErrorType::VosoUnavailable => StatusCode::BAD_REQUEST,
ErrorType::NotFound => StatusCode::NOT_FOUND,
ErrorType::NoEffect => StatusCode::OK,
ErrorType::FailedValidation { .. } => StatusCode::BAD_REQUEST,
ErrorType::IOError => StatusCode::BAD_REQUEST,
ErrorType::UnprocessableEntity => StatusCode::UNPROCESSABLE_ENTITY,
ErrorType::DeserializationError { .. } => StatusCode::UNPROCESSABLE_ENTITY,
ErrorType::FailedValidation { .. } => StatusCode::UNPROCESSABLE_ENTITY,
ErrorType::InvalidFlagValue => StatusCode::BAD_REQUEST,
ErrorType::FeatureDisabled { .. } => StatusCode::BAD_REQUEST,
@@ -84,8 +86,13 @@ impl IntoResponse for Error {
ErrorType::FileTypeNotAllowed => StatusCode::BAD_REQUEST,
ErrorType::ImageProcessingFailed => StatusCode::INTERNAL_SERVER_ERROR,
ErrorType::NoEmbedData => StatusCode::BAD_REQUEST,
};
(status, Json(&self)).into_response()
}
}
}
/// HTTP response builder for Error enum
impl IntoResponse for Error {
fn into_response(self) -> axum::response::Response {
(self.axum_status(), Json(&self)).into_response()
}
}

View File

@@ -1,3 +1,4 @@
use std::panic::Location;
use std::fmt::Display;
#[cfg(feature = "serde")]
@@ -154,6 +155,11 @@ pub enum ErrorType {
DuplicateNonce,
NotFound,
NoEffect,
IOError,
UnprocessableEntity,
DeserializationError {
error: String,
},
FailedValidation {
error: String,
},
@@ -197,6 +203,53 @@ macro_rules! create_database_error {
};
}
pub trait ToRevoltError<T>: Sized {
fn capture_error(self) -> Self;
#[track_caller]
fn to_internal_error(self) -> Result<T, Error>;
}
impl<T, E: std::error::Error> ToRevoltError<T> for Result<T, E> {
fn capture_error(self) -> Self {
self.inspect_err(|e| { sentry::capture_error(e); })
}
#[track_caller]
fn to_internal_error(self) -> Result<T, Error> {
let loc = Location::caller();
self
.capture_error()
.map_err(|_| {
Error {
error_type: ErrorType::InternalError,
location: format!("{}:{}:{}", loc.file(), loc.line(), loc.column())
}
})
}
}
impl<T: std::error::Error> ToRevoltError<T> for Option<T> {
fn capture_error(self) -> Self {
self.inspect(|e| { sentry::capture_error(e); })
}
#[track_caller]
fn to_internal_error(self) -> Result<T, Error> {
let loc = Location::caller();
self
.capture_error()
.ok_or_else(|| {
Error {
error_type: ErrorType::InternalError,
location: format!("{}:{}:{}", loc.file(), loc.line(), loc.column())
}
})
}
}
#[cfg(test)]
mod tests {
use crate::ErrorType;

View File

@@ -8,10 +8,9 @@ use rocket::{
use crate::{Error, ErrorType};
/// HTTP response builder for Error enum
impl<'r> Responder<'r, 'static> for Error {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
let status = match self.error_type {
impl Error {
pub fn rocket_status(&self) -> Status {
match self.error_type {
ErrorType::LabelMe => Status::InternalServerError,
ErrorType::AlreadyOnboarded => Status::Forbidden,
@@ -81,7 +80,10 @@ impl<'r> Responder<'r, 'static> for Error {
ErrorType::VosoUnavailable => Status::BadRequest,
ErrorType::NotFound => Status::NotFound,
ErrorType::NoEffect => Status::Ok,
ErrorType::FailedValidation { .. } => Status::BadRequest,
ErrorType::IOError => Status::BadRequest,
ErrorType::UnprocessableEntity => Status::UnprocessableEntity,
ErrorType::DeserializationError { .. } => Status::UnprocessableEntity,
ErrorType::FailedValidation { .. } => Status::UnprocessableEntity,
ErrorType::FeatureDisabled { .. } => Status::BadRequest,
ErrorType::ProxyError => Status::BadRequest,
@@ -90,8 +92,13 @@ impl<'r> Responder<'r, 'static> for Error {
ErrorType::FileTypeNotAllowed => Status::BadRequest,
ErrorType::ImageProcessingFailed => Status::InternalServerError,
ErrorType::NoEmbedData => Status::BadRequest,
};
}
}
}
/// HTTP response builder for Error enum
impl<'r> Responder<'r, 'static> for Error {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
// Serialize the error data structure into JSON.
let string = serde_json::to_string(&self).unwrap();
@@ -99,7 +106,7 @@ impl<'r> Responder<'r, 'static> for Error {
Response::build()
.sized_body(string.len(), Cursor::new(string))
.header(ContentType::new("application", "json"))
.status(status)
.status(self.rocket_status())
.ok()
}
}