chore: merge Rust backend into monorepo

git-subtree-dir: server
git-subtree-mainline: fb6a84d75f
git-subtree-split: 8a88706ff9
This commit is contained in:
2026-07-20 10:50:48 +08:00
18 changed files with 6191 additions and 0 deletions

96
server/src/error.rs Normal file
View 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
}
}