feat(ai): 扩展 AiError 支持配额/缓存/知识库/队列/配置错误变体

新增 QuotaExhausted→429, CacheError/KnowledgeError/QueueError/ConfigError→500
This commit is contained in:
iven
2026-05-05 15:02:38 +08:00
parent 4d02b2b531
commit 24bb8e7bca

View File

@@ -31,6 +31,21 @@ pub enum AiError {
#[error("数据库错误: {0}")]
DbError(String),
#[error("AI 配额已耗尽: {reason}")]
QuotaExhausted { tenant_id: uuid::Uuid, reason: String },
#[error("缓存错误: {0}")]
CacheError(String),
#[error("知识库错误: {0}")]
KnowledgeError(String),
#[error("分析队列错误: {0}")]
QueueError(String),
#[error("AI 配置错误: {0}")]
ConfigError(String),
}
impl From<AiError> for AppError {
@@ -43,6 +58,7 @@ impl From<AiError> for AppError {
AppError::Internal(format!("AI 提供商 {p} 不可用"))
}
AiError::RateLimitExceeded => AppError::TooManyRequests,
AiError::QuotaExhausted { .. } => AppError::TooManyRequests,
AiError::VersionMismatch => AppError::VersionMismatch,
AiError::DbError(msg) => AppError::Internal(msg),
other => AppError::Internal(other.to_string()),
@@ -161,4 +177,54 @@ mod tests {
other => panic!("期望 AppError::Internal得到 {:?}", other),
}
}
#[test]
fn quota_exhausted_maps_to_429() {
let err = AiError::QuotaExhausted {
tenant_id: uuid::Uuid::now_v7(),
reason: "monthly budget".into(),
};
let app: AppError = err.into();
assert!(matches!(app, AppError::TooManyRequests));
}
#[test]
fn cache_error_maps_to_internal() {
let err = AiError::CacheError("redis timeout".into());
let app: AppError = err.into();
match app {
AppError::Internal(msg) => assert!(msg.contains("redis timeout")),
other => panic!("期望 AppError::Internal得到 {:?}", other),
}
}
#[test]
fn knowledge_error_maps_to_internal() {
let err = AiError::KnowledgeError("rule not found".into());
let app: AppError = err.into();
match app {
AppError::Internal(msg) => assert!(msg.contains("rule not found")),
other => panic!("期望 AppError::Internal得到 {:?}", other),
}
}
#[test]
fn queue_error_maps_to_internal() {
let err = AiError::QueueError("queue full".into());
let app: AppError = err.into();
match app {
AppError::Internal(msg) => assert!(msg.contains("queue full")),
other => panic!("期望 AppError::Internal得到 {:?}", other),
}
}
#[test]
fn config_error_maps_to_internal() {
let err = AiError::ConfigError("missing provider".into());
let app: AppError = err.into();
match app {
AppError::Internal(msg) => assert!(msg.contains("missing provider")),
other => panic!("期望 AppError::Internal得到 {:?}", other),
}
}
}