use axum::Json;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
#[derive(Debug)]
pub enum AppError {
BadRequest(String),
NotFound(String),
Conflict(String),
Unauthorized,
NotConnected {
node: Option<String>,
},
Unavailable(String),
GatewayTimeout(String),
Upstream(ocpp_core::RpcError),
Internal(String),
}
impl std::fmt::Display for AppError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
impl From<sqlx::Error> for AppError {
fn from(e: sqlx::Error) -> Self {
if let sqlx::Error::Database(db) = &e
&& db.is_unique_violation()
{
return AppError::Conflict(db.message().to_owned());
}
if let sqlx::Error::Database(db) = &e
&& db.is_foreign_key_violation()
{
return AppError::Conflict(format!("still referenced: {}", db.message()));
}
if let sqlx::Error::Database(db) = &e
&& db.is_check_violation()
{
return AppError::BadRequest(db.message().to_owned());
}
AppError::Internal(e.to_string())
}
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, body) = match self {
AppError::BadRequest(m) => (
StatusCode::BAD_REQUEST,
serde_json::json!({ "error": "bad_request", "message": m }),
),
AppError::NotFound(m) => (
StatusCode::NOT_FOUND,
serde_json::json!({ "error": "not_found", "message": m }),
),
AppError::Conflict(m) => (
StatusCode::CONFLICT,
serde_json::json!({ "error": "conflict", "message": m }),
),
AppError::Unauthorized => (
StatusCode::UNAUTHORIZED,
serde_json::json!({ "error": "unauthorized" }),
),
AppError::NotConnected { node } => (
StatusCode::CONFLICT,
serde_json::json!({ "error": "station_not_connected", "message": "station is not connected to this node", "connected_node": node }),
),
AppError::Unavailable(m) => (
StatusCode::SERVICE_UNAVAILABLE,
serde_json::json!({ "error": "unavailable", "message": m }),
),
AppError::GatewayTimeout(m) => (
StatusCode::GATEWAY_TIMEOUT,
serde_json::json!({ "error": "timeout", "message": m }),
),
AppError::Upstream(e) => (
StatusCode::BAD_GATEWAY,
serde_json::json!({ "error": "call_error", "error_code": e.code.to_string(), "description": e.description, "details": e.details }),
),
AppError::Internal(m) => {
tracing::error!(error = %m, "internal error");
(
StatusCode::INTERNAL_SERVER_ERROR,
serde_json::json!({ "error": "internal" }),
)
}
};
(status, Json(body)).into_response()
}
}