use actix_web::{http::StatusCode, HttpResponse, ResponseError};
use serde::Serialize;
use thiserror::Error;
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ErrorCode {
BadRequest,
Unauthorized,
Forbidden,
NotFound,
Conflict,
ValidationError,
InternalError,
ServiceUnavailable,
Custom(String),
}
impl std::fmt::Display for ErrorCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ErrorCode::Custom(s) => write!(f, "{}", s),
_ => write!(f, "{:?}", self),
}
}
}
#[derive(Debug, Serialize)]
pub struct ApiErrorResponse {
pub code: ErrorCode,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<serde_json::Value>,
}
#[derive(Debug, Error)]
pub struct ApiError {
code: ErrorCode,
message: String,
status: StatusCode,
details: Option<serde_json::Value>,
}
impl ApiError {
pub fn new(code: ErrorCode, message: impl Into<String>, status: StatusCode) -> Self {
Self {
code,
message: message.into(),
status,
details: None,
}
}
pub fn with_details(mut self, details: serde_json::Value) -> Self {
self.details = Some(details);
self
}
pub fn bad_request(msg: impl Into<String>) -> Self {
Self::new(ErrorCode::BadRequest, msg, StatusCode::BAD_REQUEST)
}
pub fn unauthorized(msg: impl Into<String>) -> Self {
Self::new(ErrorCode::Unauthorized, msg, StatusCode::UNAUTHORIZED)
}
pub fn forbidden(msg: impl Into<String>) -> Self {
Self::new(ErrorCode::Forbidden, msg, StatusCode::FORBIDDEN)
}
pub fn not_found(msg: impl Into<String>) -> Self {
Self::new(ErrorCode::NotFound, msg, StatusCode::NOT_FOUND)
}
pub fn internal(msg: impl Into<String>) -> Self {
Self::new(
ErrorCode::InternalError,
msg,
StatusCode::INTERNAL_SERVER_ERROR,
)
}
}
impl std::fmt::Display for ApiError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}] {}", self.code, self.message)
}
}
impl ResponseError for ApiError {
fn status_code(&self) -> StatusCode {
self.status
}
fn error_response(&self) -> HttpResponse<actix_web::body::BoxBody> {
let body = ApiErrorResponse {
code: self.code.clone(),
message: self.message.clone(),
details: self.details.clone(),
};
HttpResponse::build(self.status).json(body)
}
}
impl From<std::io::Error> for ApiError {
fn from(e: std::io::Error) -> Self {
Self::internal(e.to_string())
}
}
impl From<serde_json::Error> for ApiError {
fn from(e: serde_json::Error) -> Self {
Self::bad_request(e.to_string())
}
}