use anyhow::Error; use axum::{ Json, http::StatusCode, response::IntoResponse, extract::rejection::{ QueryRejection, PathRejection, JsonRejection, FormRejection, }, }; use serde::Serialize; use serde_json::Value; use sqlx::Error as SqlxError; #[derive(Debug, Serialize)] pub struct ErrResp { pub err_code: i32, pub err_msg: String, pub err_detail: Option, } impl ErrResp { pub fn new(err_code: i32, err_msg: impl Into, detail: Option) -> Self { Self { err_code, err_msg: err_msg.into(), err_detail: detail, } } } #[derive(Debug)] #[allow(dead_code)] pub enum ApiErr { Unauthorized(String, Option), Forbidden(String, Option), BadRequest(String, Option), NotFound(String, Option), Internal(String, Option), } impl IntoResponse for ApiErr { fn into_response(self) -> axum::response::Response { match self { ApiErr::Unauthorized(msg, detail) => { (StatusCode::UNAUTHORIZED, Json(ErrResp::new(401, msg, detail))).into_response() } ApiErr::Forbidden(msg, detail) => { (StatusCode::FORBIDDEN, Json(ErrResp::new(403, msg, detail))).into_response() } ApiErr::BadRequest(msg, detail) => { (StatusCode::BAD_REQUEST, Json(ErrResp::new(400, msg, detail))).into_response() } ApiErr::NotFound(msg, detail) => { (StatusCode::NOT_FOUND, Json(ErrResp::new(404, msg, detail))).into_response() } ApiErr::Internal(msg, detail) => ( StatusCode::INTERNAL_SERVER_ERROR, Json(ErrResp::new(500, msg, detail)), ) .into_response(), } } } impl From for ApiErr { fn from(err: Error) -> Self { tracing::error!("Error: {:?}; root_cause: {}", err, err.root_cause()); ApiErr::Internal("internal server error".to_string(), None) } } impl From for ApiErr { fn from(err: SqlxError) -> Self { match err { SqlxError::RowNotFound => { ApiErr::NotFound("Resource not found".into(), None) } SqlxError::Database(db_err) => { if db_err.code().as_deref() == Some("23505") { ApiErr::BadRequest("数据已存在".into(), None) } else { tracing::error!("Database error: {}", db_err); ApiErr::Internal("Database error".into(), None) } } _ => { tracing::error!("Database error: {}", err); ApiErr::Internal("Database error".into(), None) } } } } impl From for ApiErr { fn from(rejection: QueryRejection) -> Self { tracing::warn!("Query parameter error: {}", rejection); ApiErr::BadRequest( "Invalid query parameters".to_string(), Some(serde_json::json!({ "detail": rejection.to_string() })) ) } } impl From for ApiErr { fn from(rejection: PathRejection) -> Self { tracing::warn!("Path parameter error: {}", rejection); ApiErr::BadRequest( "Invalid path parameter".to_string(), Some(serde_json::json!({ "detail": rejection.to_string() })) ) } } impl From for ApiErr { fn from(rejection: JsonRejection) -> Self { tracing::warn!("JSON parsing error: {}", rejection); ApiErr::BadRequest( "Invalid JSON format".to_string(), Some(serde_json::json!({ "detail": rejection.to_string() })) ) } } impl From for ApiErr { fn from(rejection: FormRejection) -> Self { tracing::warn!("Form data error: {}", rejection); ApiErr::BadRequest( "Invalid form data".to_string(), Some(serde_json::json!({ "detail": rejection.to_string() })) ) } }