use axum::{
Json,
response::{IntoResponse, Response},
};
use http::StatusCode;
use mcp_common::t;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("{0}")]
ServiceNotFound(String),
#[error("{0}")]
ServiceRestartCooldown(String),
#[error("{0}")]
ServiceStartupInProgress(String),
#[error("{0}")]
ServiceStartupFailed(String),
#[error("{0}")]
BackendConnection(String),
#[error("{0}")]
ConfigParse(String),
#[error("{0}")]
McpServerError(String),
#[error("{0}")]
SerdeJsonError(String),
#[error("{0}")]
IoError(String),
#[error("{0}")]
RouteNotFound(String),
#[error("{0}")]
InvalidParameter(String),
}
impl AppError {
pub fn service_not_found(service: impl Into<String>) -> Self {
Self::ServiceNotFound(
t!(
"errors.mcp_proxy.service_not_found",
service = service.into()
)
.to_string(),
)
}
pub fn service_restart_cooldown(service: impl Into<String>) -> Self {
Self::ServiceRestartCooldown(
t!(
"errors.mcp_proxy.service_restart_cooldown",
service = service.into()
)
.to_string(),
)
}
pub fn service_startup_in_progress(service: impl Into<String>) -> Self {
Self::ServiceStartupInProgress(
t!(
"errors.mcp_proxy.service_startup_in_progress",
service = service.into()
)
.to_string(),
)
}
pub fn service_startup_failed(mcp_id: impl Into<String>, reason: impl Into<String>) -> Self {
Self::ServiceStartupFailed(
t!(
"errors.mcp_proxy.service_startup_failed",
mcp_id = mcp_id.into(),
reason = reason.into()
)
.to_string(),
)
}
pub fn backend_connection(detail: impl Into<String>) -> Self {
Self::BackendConnection(
t!(
"errors.mcp_proxy.backend_connection",
detail = detail.into()
)
.to_string(),
)
}
pub fn config_parse(detail: impl Into<String>) -> Self {
Self::ConfigParse(t!("errors.mcp_proxy.config_parse", detail = detail.into()).to_string())
}
pub fn mcp_server_error(detail: impl Into<String>) -> Self {
Self::McpServerError(
t!("errors.mcp_proxy.mcp_server_error", detail = detail.into()).to_string(),
)
}
pub fn json_serialization(detail: impl Into<String>) -> Self {
Self::SerdeJsonError(
t!(
"errors.mcp_proxy.json_serialization",
detail = detail.into()
)
.to_string(),
)
}
pub fn io_error(detail: impl Into<String>) -> Self {
Self::IoError(t!("errors.mcp_proxy.io_error", detail = detail.into()).to_string())
}
pub fn route_not_found(path: impl Into<String>) -> Self {
Self::RouteNotFound(t!("errors.mcp_proxy.route_not_found", path = path.into()).to_string())
}
pub fn invalid_parameter(detail: impl Into<String>) -> Self {
Self::InvalidParameter(
t!("errors.mcp_proxy.invalid_parameter", detail = detail.into()).to_string(),
)
}
pub fn error_code(&self) -> &'static str {
match self {
Self::ServiceNotFound(_) => "0001",
Self::ServiceRestartCooldown(_) => "0002",
Self::ServiceStartupInProgress(_) => "0003",
Self::ServiceStartupFailed { .. } => "0004",
Self::BackendConnection(_) => "0005",
Self::ConfigParse(_) => "0006",
Self::McpServerError(_) => "0007",
Self::SerdeJsonError(_) => "0008",
Self::IoError(_) => "0009",
Self::RouteNotFound(_) => "0010",
Self::InvalidParameter(_) => "0011",
}
}
pub fn status_code(&self) -> StatusCode {
match self {
Self::ServiceNotFound(_) => StatusCode::NOT_FOUND,
Self::ServiceRestartCooldown(_) => StatusCode::TOO_MANY_REQUESTS,
Self::ServiceStartupInProgress(_) => StatusCode::SERVICE_UNAVAILABLE,
Self::ServiceStartupFailed { .. } => StatusCode::INTERNAL_SERVER_ERROR,
Self::BackendConnection(_) => StatusCode::BAD_GATEWAY,
Self::ConfigParse(_) => StatusCode::BAD_REQUEST,
Self::McpServerError(_) => StatusCode::INTERNAL_SERVER_ERROR,
Self::SerdeJsonError(_) => StatusCode::BAD_REQUEST,
Self::IoError(_) => StatusCode::INTERNAL_SERVER_ERROR,
Self::RouteNotFound(_) => StatusCode::NOT_FOUND,
Self::InvalidParameter(_) => StatusCode::BAD_REQUEST,
}
}
}
impl From<anyhow::Error> for AppError {
fn from(err: anyhow::Error) -> Self {
Self::mcp_server_error(err.to_string())
}
}
impl From<serde_json::Error> for AppError {
fn from(err: serde_json::Error) -> Self {
Self::json_serialization(err.to_string())
}
}
impl From<std::io::Error> for AppError {
fn from(err: std::io::Error) -> Self {
Self::io_error(err.to_string())
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ErrorOutput {
pub code: String,
pub error: String,
}
impl ErrorOutput {
pub fn with_code(code: &'static str, error: impl Into<String>) -> Self {
Self {
code: code.to_string(),
error: error.into(),
}
}
}
impl IntoResponse for AppError {
fn into_response(self) -> Response<axum::body::Body> {
(
self.status_code(),
Json(ErrorOutput::with_code(self.error_code(), self.to_string())),
)
.into_response()
}
}