brewlog/src/application/errors.rs
2025-11-25 21:40:39 +00:00

86 lines
2.3 KiB
Rust

use axum::Json;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tracing::error;
use crate::domain::RepositoryError;
#[derive(Debug, Serialize, Deserialize)]
pub struct ErrorResponse {
pub message: String,
}
impl ErrorResponse {
pub fn new<T: ToString>(message: T) -> Self {
Self {
message: message.to_string(),
}
}
}
pub struct ApiError(AppError);
impl From<AppError> for ApiError {
fn from(value: AppError) -> Self {
Self(value)
}
}
impl IntoResponse for ApiError {
fn into_response(self) -> Response {
let (status, message) = match self.0 {
AppError::Validation(message) => (StatusCode::BAD_REQUEST, message),
AppError::NotFound => (StatusCode::NOT_FOUND, "entity not found".to_string()),
AppError::Unexpected(message) => {
error!(error = %message, "unexpected application error");
(
StatusCode::INTERNAL_SERVER_ERROR,
"unexpected error".to_string(),
)
}
};
(status, Json(ErrorResponse::new(message))).into_response()
}
}
pub fn map_app_error(err: AppError) -> StatusCode {
match err {
AppError::Validation(_) => StatusCode::BAD_REQUEST,
AppError::NotFound => StatusCode::NOT_FOUND,
AppError::Unexpected(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
pub type AppResult<T> = Result<T, AppError>;
#[derive(Debug, Error)]
pub enum AppError {
#[error("validation failed: {0}")]
Validation(String),
#[error("entity not found")]
NotFound,
#[error("unexpected error: {0}")]
Unexpected(String),
}
impl AppError {
pub fn validation<T: ToString>(msg: T) -> Self {
Self::Validation(msg.to_string())
}
pub fn unexpected<T: ToString>(msg: T) -> Self {
Self::Unexpected(msg.to_string())
}
}
impl From<RepositoryError> for AppError {
fn from(value: RepositoryError) -> Self {
match value {
RepositoryError::NotFound => Self::NotFound,
RepositoryError::Conflict(msg) => Self::Validation(msg),
RepositoryError::Unexpected(msg) => Self::Unexpected(msg),
}
}
}