From 24bb8e7bcabff6e71a564f45705251289e05472c Mon Sep 17 00:00:00 2001 From: iven Date: Tue, 5 May 2026 15:02:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(ai):=20=E6=89=A9=E5=B1=95=20AiError=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E9=85=8D=E9=A2=9D/=E7=BC=93=E5=AD=98/?= =?UTF-8?q?=E7=9F=A5=E8=AF=86=E5=BA=93/=E9=98=9F=E5=88=97/=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E9=94=99=E8=AF=AF=E5=8F=98=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 QuotaExhausted→429, CacheError/KnowledgeError/QueueError/ConfigError→500 --- crates/erp-ai/src/error.rs | 66 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/crates/erp-ai/src/error.rs b/crates/erp-ai/src/error.rs index 5dff165..f3d4e35 100644 --- a/crates/erp-ai/src/error.rs +++ b/crates/erp-ai/src/error.rs @@ -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 for AppError { @@ -43,6 +58,7 @@ impl From 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), + } + } }