Files
papercrate/backend/src/error.rs
T
2025-11-10 23:06:53 +01:00

118 lines
2.8 KiB
Rust

use axum::{
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use serde::Serialize;
use serde_json::Value;
use std::fmt::Display;
use utoipa::ToSchema;
pub type AppResult<T> = Result<T, AppError>;
#[derive(Debug)]
pub struct AppError {
status: StatusCode,
message: String,
code: Option<String>,
details: Option<Value>,
}
impl AppError {
pub fn new(status: StatusCode, message: impl Into<String>) -> Self {
Self {
status,
message: message.into(),
code: None,
details: None,
}
}
pub fn bad_request(message: impl Into<String>) -> Self {
Self::new(StatusCode::BAD_REQUEST, message)
}
pub fn conflict(message: impl Into<String>) -> Self {
Self::new(StatusCode::CONFLICT, message)
}
pub fn unauthorized() -> Self {
Self::new(StatusCode::UNAUTHORIZED, "unauthorized")
}
pub fn not_found() -> Self {
Self::new(StatusCode::NOT_FOUND, "resource not found")
}
pub fn internal<E: Display>(error: E) -> Self {
Self::new(StatusCode::INTERNAL_SERVER_ERROR, error.to_string())
}
pub fn with_code(mut self, code: impl Into<String>) -> Self {
self.code = Some(code.into());
self
}
pub fn with_details(mut self, details: Value) -> Self {
self.details = Some(details);
self
}
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let status = self.status;
let body = Json(ApiErrorResponse {
error: self.message,
code: self.code,
details: self.details,
});
(status, body).into_response()
}
}
#[derive(Serialize, ToSchema)]
pub struct ApiErrorResponse {
error: String,
#[serde(skip_serializing_if = "Option::is_none")]
code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
details: Option<Value>,
}
impl From<diesel::result::Error> for AppError {
fn from(value: diesel::result::Error) -> Self {
match value {
diesel::result::Error::NotFound => AppError::not_found(),
other => {
tracing::error!(error = ?other, "database operation failed");
AppError::internal("database operation failed")
}
}
}
}
impl From<jsonwebtoken::errors::Error> for AppError {
fn from(value: jsonwebtoken::errors::Error) -> Self {
AppError::internal(value)
}
}
impl From<anyhow::Error> for AppError {
fn from(value: anyhow::Error) -> Self {
AppError::internal(value)
}
}
impl From<std::io::Error> for AppError {
fn from(value: std::io::Error) -> Self {
AppError::internal(value)
}
}
impl From<serde_json::Error> for AppError {
fn from(value: serde_json::Error) -> Self {
AppError::internal(value)
}
}