use axum::{ http::StatusCode, response::{IntoResponse, Response}, Json, }; use serde::Serialize; use std::fmt::Display; pub type AppResult = Result; #[derive(Debug)] pub struct AppError { status: StatusCode, message: String, code: Option, } impl AppError { pub fn new(status: StatusCode, message: impl Into) -> Self { Self { status, message: message.into(), code: None, } } pub fn bad_request(message: impl Into) -> Self { Self::new(StatusCode::BAD_REQUEST, message) } pub fn conflict(message: impl Into) -> 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(error: E) -> Self { Self::new(StatusCode::INTERNAL_SERVER_ERROR, error.to_string()) } pub fn with_code(mut self, code: impl Into) -> Self { self.code = Some(code.into()); self } } impl IntoResponse for AppError { fn into_response(self) -> Response { let status = self.status; let body = Json(ErrorResponse { error: self.message, code: self.code, }); (status, body).into_response() } } #[derive(Serialize)] struct ErrorResponse { error: String, #[serde(skip_serializing_if = "Option::is_none")] code: Option, } impl From for AppError { fn from(value: diesel::result::Error) -> Self { match value { diesel::result::Error::NotFound => AppError::not_found(), _ => AppError::internal(value), } } } impl From for AppError { fn from(value: jsonwebtoken::errors::Error) -> Self { AppError::internal(value) } } impl From for AppError { fn from(value: anyhow::Error) -> Self { AppError::internal(value) } } impl From for AppError { fn from(value: std::io::Error) -> Self { AppError::internal(value) } } impl From for AppError { fn from(value: serde_json::Error) -> Self { AppError::internal(value) } }