use super::error_response::{ErrorResponse, ValidationErrorDetail};
use crate::http::response::into_response::IntoResponse;
use bytes::Bytes;
use http_body_util::{BodyExt, Full};
use hyper::{Response, StatusCode};
use miko_core::{MikoError, Resp};
use serde_json::json;
use std::any::Any;
use std::convert::Infallible;
use std::fmt;
use std::time::{SystemTime, UNIX_EPOCH};
use tokio::task_local;
task_local! {
pub static TRACE_ID: String;
}
pub fn get_trace_id() -> Option<String> {
TRACE_ID.try_with(|id| id.clone()).ok()
}
#[derive(Debug)]
pub enum AppError {
BadRequest(String),
Unauthorized(String),
Forbidden(String),
NotFound(String),
Conflict(String),
UnprocessableEntity(String),
TooManyRequests(String),
InternalServerError(String),
BadGateway(String),
ServiceUnavailable(String),
GatewayTimeout(String),
JsonParseError(serde_json::Error),
UrlEncodedParseError(serde_urlencoded::de::Error),
MultipartParseError(String),
ValidationError(Vec<ValidationErrorDetail>),
DatabaseError(String),
IoError(std::io::Error),
Timeout(String),
ExternalServiceError { service: String, message: String },
Custom {
status: StatusCode,
error_code: String,
message: String,
details: Option<serde_json::Value>,
},
}
impl AppError {
pub fn custom(
status: StatusCode,
error_code: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self::Custom {
status,
error_code: error_code.into(),
message: message.into(),
details: None,
}
}
pub fn custom_with_details(
status: StatusCode,
error_code: impl Into<String>,
message: impl Into<String>,
details: serde_json::Value,
) -> Self {
Self::Custom {
status,
error_code: error_code.into(),
message: message.into(),
details: Some(details),
}
}
pub fn status_code(&self) -> StatusCode {
match self {
Self::BadRequest(_)
| Self::JsonParseError(_)
| Self::UrlEncodedParseError(_)
| Self::MultipartParseError(_) => StatusCode::BAD_REQUEST,
Self::Unauthorized(_) => StatusCode::UNAUTHORIZED,
Self::Forbidden(_) => StatusCode::FORBIDDEN,
Self::NotFound(_) => StatusCode::NOT_FOUND,
Self::Conflict(_) => StatusCode::CONFLICT,
Self::UnprocessableEntity(_) | Self::ValidationError(_) => {
StatusCode::UNPROCESSABLE_ENTITY
}
Self::TooManyRequests(_) => StatusCode::TOO_MANY_REQUESTS,
Self::InternalServerError(_)
| Self::DatabaseError(_)
| Self::IoError(_)
| Self::ExternalServiceError { .. } => StatusCode::INTERNAL_SERVER_ERROR,
Self::BadGateway(_) => StatusCode::BAD_GATEWAY,
Self::ServiceUnavailable(_) => StatusCode::SERVICE_UNAVAILABLE,
Self::GatewayTimeout(_) | Self::Timeout(_) => StatusCode::GATEWAY_TIMEOUT,
Self::Custom { status, .. } => *status,
}
}
pub fn error_code(&self) -> String {
match self {
Self::BadRequest(_) => "BAD_REQUEST",
Self::Unauthorized(_) => "UNAUTHORIZED",
Self::Forbidden(_) => "FORBIDDEN",
Self::NotFound(_) => "NOT_FOUND",
Self::Conflict(_) => "CONFLICT",
Self::UnprocessableEntity(_) => "UNPROCESSABLE_ENTITY",
Self::TooManyRequests(_) => "TOO_MANY_REQUESTS",
Self::InternalServerError(_) => "INTERNAL_SERVER_ERROR",
Self::BadGateway(_) => "BAD_GATEWAY",
Self::ServiceUnavailable(_) => "SERVICE_UNAVAILABLE",
Self::GatewayTimeout(_) => "GATEWAY_TIMEOUT",
Self::Timeout(_) => "TIMEOUT",
Self::JsonParseError(_) => "JSON_PARSE_ERROR",
Self::UrlEncodedParseError(_) => "URL_ENCODED_PARSE_ERROR",
Self::MultipartParseError(_) => "MULTIPART_PARSE_ERROR",
Self::ValidationError(_) => "VALIDATION_ERROR",
Self::DatabaseError(_) => "DATABASE_ERROR",
Self::IoError(_) => "IO_ERROR",
Self::ExternalServiceError { .. } => "EXTERNAL_SERVICE_ERROR",
Self::Custom { error_code, .. } => return error_code.clone(),
}
.to_string()
}
pub fn message(&self) -> String {
match self {
Self::BadRequest(msg)
| Self::Unauthorized(msg)
| Self::Forbidden(msg)
| Self::NotFound(msg)
| Self::Conflict(msg)
| Self::UnprocessableEntity(msg)
| Self::TooManyRequests(msg)
| Self::InternalServerError(msg)
| Self::BadGateway(msg)
| Self::ServiceUnavailable(msg)
| Self::GatewayTimeout(msg)
| Self::Timeout(msg)
| Self::DatabaseError(msg)
| Self::MultipartParseError(msg) => msg.clone(),
Self::JsonParseError(e) => format!("Invalid JSON: {}", e),
Self::UrlEncodedParseError(e) => format!("Invalid URL encoding: {}", e),
Self::ValidationError(_) => "Request validation failed".to_string(),
Self::IoError(e) => format!("IO error: {}", e),
Self::ExternalServiceError { service, message } => {
format!("External service '{}' error: {}", service, message)
}
Self::Custom { message, .. } => message.clone(),
}
}
pub fn details(&self) -> Option<serde_json::Value> {
match self {
Self::ValidationError(errors) => Some(json!({
"fields": errors
})),
Self::ExternalServiceError { service, .. } => Some(json!({
"service": service
})),
Self::Custom { details, .. } => details.clone(),
_ => None,
}
}
pub fn from_panic(payload: Box<dyn Any + Send>) -> Self {
let msg = if let Some(s) = payload.downcast_ref::<&str>() {
s.to_string()
} else if let Some(s) = payload.downcast_ref::<String>() {
s.clone()
} else {
"Unknown panic".to_string()
};
Self::InternalServerError(format!("Server panicked: {}", msg))
}
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {}", self.error_code(), self.message())
}
}
impl std::error::Error for AppError {}
impl From<MikoError> for AppError {
fn from(err: MikoError) -> Self {
err.0.into()
}
}
impl From<config::ConfigError> for AppError {
fn from(err: config::ConfigError) -> Self {
tracing::warn!("Configuration error: {:?}", err);
Self::InternalServerError("Internal Configuration error".to_string())
}
}
impl From<serde_json::Error> for AppError {
fn from(err: serde_json::Error) -> Self {
Self::JsonParseError(err)
}
}
impl From<serde_urlencoded::de::Error> for AppError {
fn from(err: serde_urlencoded::de::Error) -> Self {
Self::UrlEncodedParseError(err)
}
}
impl From<std::io::Error> for AppError {
fn from(err: std::io::Error) -> Self {
Self::IoError(err)
}
}
impl From<multer::Error> for AppError {
fn from(err: multer::Error) -> Self {
Self::MultipartParseError(err.to_string())
}
}
impl From<anyhow::Error> for AppError {
fn from(err: anyhow::Error) -> Self {
Self::InternalServerError(err.to_string())
}
}
impl From<Infallible> for AppError {
fn from(_: Infallible) -> Self {
Self::InternalServerError("Infallible error occurred".to_string())
}
}
impl From<Box<dyn std::error::Error>> for AppError {
fn from(err: Box<dyn std::error::Error>) -> Self {
Self::InternalServerError(err.to_string())
}
}
impl From<Box<dyn std::error::Error + Send + Sync>> for AppError {
fn from(err: Box<dyn std::error::Error + Send + Sync>) -> Self {
Self::InternalServerError(err.to_string())
}
}
#[cfg(feature = "validation")]
impl From<garde::Report> for AppError {
fn from(report: garde::Report) -> Self {
use crate::error::ValidationErrorDetail;
let details: Vec<ValidationErrorDetail> = report
.iter()
.map(|(path, error)| ValidationErrorDetail {
field: path.to_string(),
message: error.to_string(),
code: "VALIDATION_FAILED".to_string(),
})
.collect();
Self::ValidationError(details)
}
}
impl IntoResponse for AppError {
fn into_response(self) -> Resp {
let status = self.status_code();
let error_code = self.error_code();
let message = self.message();
let details = self.details();
if status.is_server_error() {
tracing::error!(
error_code = %error_code,
message = %message,
trace_id = ?get_trace_id(),
"Internal server error"
);
}
let error_response = ErrorResponse {
status: status.as_u16(),
error: error_code,
message,
details,
trace_id: get_trace_id(), timestamp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
};
let body = serde_json::to_string(&error_response).unwrap_or_else(|_| {
r#"{"error":"SERIALIZATION_ERROR","message":"Failed to serialize error response"}"#
.to_string()
});
Response::builder()
.status(status)
.header("Content-Type", "application/json")
.body(
Full::new(Bytes::from(body))
.map_err(Into::into)
.boxed_unsync(),
)
.unwrap_or_else(|_| {
Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(
Full::new(Bytes::from(r#"{"error":"INTERNAL_SERVER_ERROR"}"#))
.map_err(Into::into)
.boxed_unsync(),
)
.unwrap()
})
}
}