chore: merge Rust backend into monorepo
git-subtree-dir: server git-subtree-mainline:fb6a84d75fgit-subtree-split:8a88706ff9
This commit is contained in:
96
server/src/error.rs
Normal file
96
server/src/error.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
use axum::{
|
||||
Json,
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use serde::Serialize;
|
||||
use utoipa::{IntoResponses, ToSchema};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ApiError {
|
||||
#[error("{0}")]
|
||||
BadRequest(String),
|
||||
#[error("authentication is required")]
|
||||
Unauthorized,
|
||||
#[error("resource not found")]
|
||||
NotFound,
|
||||
#[error("database has not been configured")]
|
||||
DatabaseUnavailable,
|
||||
#[error("{0}")]
|
||||
ServiceUnavailable(String),
|
||||
#[error("internal server error")]
|
||||
Internal,
|
||||
}
|
||||
|
||||
/// API 错误响应。
|
||||
#[derive(Serialize, ToSchema)]
|
||||
#[schema(example = json!({"error": "resource not found"}))]
|
||||
pub struct ErrorBody {
|
||||
/// 面向调用方的错误信息。
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
/// 所有需要数据库和临时用户身份的业务接口都可能返回的公共错误。
|
||||
#[allow(dead_code)]
|
||||
#[derive(IntoResponses)]
|
||||
pub enum CommonApiErrors {
|
||||
/// 参数格式或业务校验失败。
|
||||
#[response(
|
||||
status = 400,
|
||||
description = "请求参数无效",
|
||||
example = json!({"error": "X-User-Id must be a UUID"})
|
||||
)]
|
||||
BadRequest(ErrorBody),
|
||||
/// 缺少 `X-User-Id` 请求头。
|
||||
#[response(
|
||||
status = 401,
|
||||
description = "缺少开发阶段身份请求头",
|
||||
example = json!({"error": "authentication is required"})
|
||||
)]
|
||||
Unauthorized(ErrorBody),
|
||||
/// 服务未配置数据库连接。
|
||||
#[response(
|
||||
status = 503,
|
||||
description = "未配置 DATABASE_URL",
|
||||
example = json!({"error": "database has not been configured"})
|
||||
)]
|
||||
DatabaseUnavailable(ErrorBody),
|
||||
/// 数据库查询或服务内部处理失败。
|
||||
#[response(
|
||||
status = 500,
|
||||
description = "服务内部错误",
|
||||
example = json!({"error": "internal server error"})
|
||||
)]
|
||||
Internal(ErrorBody),
|
||||
}
|
||||
|
||||
impl IntoResponse for ApiError {
|
||||
fn into_response(self) -> Response {
|
||||
let (status, message) = match self {
|
||||
Self::BadRequest(message) => (StatusCode::BAD_REQUEST, message),
|
||||
Self::Unauthorized => (
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"authentication is required".to_owned(),
|
||||
),
|
||||
Self::NotFound => (StatusCode::NOT_FOUND, "resource not found".to_owned()),
|
||||
Self::DatabaseUnavailable => (
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
"database has not been configured".to_owned(),
|
||||
),
|
||||
Self::ServiceUnavailable(message) => (StatusCode::SERVICE_UNAVAILABLE, message),
|
||||
Self::Internal => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"internal server error".to_owned(),
|
||||
),
|
||||
};
|
||||
|
||||
(status, Json(ErrorBody { error: message })).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sqlx::Error> for ApiError {
|
||||
fn from(error: sqlx::Error) -> Self {
|
||||
tracing::error!(?error, "database query failed");
|
||||
Self::Internal
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user