use erp_core::error::AppError; #[derive(Debug, thiserror::Error)] pub enum AiError { #[error("验证失败: {0}")] Validation(String), #[error("分析未找到: {0}")] AnalysisNotFound(String), #[error("Prompt 模板未找到: {0}")] PromptNotFound(String), #[error("AI 提供商不可用: {0}")] ProviderUnavailable(String), #[error("AI 提供商错误: {0}")] ProviderError(String), #[error("数据脱敏失败: {0}")] SanitizationError(String), #[error("模板渲染失败: {0}")] TemplateError(String), #[error("速率超限")] RateLimitExceeded, #[error("版本不匹配")] VersionMismatch, #[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 { fn from(e: AiError) -> Self { match e { AiError::Validation(msg) => AppError::Validation(msg), AiError::AnalysisNotFound(id) => AppError::NotFound(format!("分析结果: {id}")), AiError::PromptNotFound(name) => AppError::NotFound(format!("Prompt 模板: {name}")), AiError::ProviderUnavailable(p) => 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()), } } } impl From for AiError { fn from(e: sea_orm::DbErr) -> Self { AiError::DbError(e.to_string()) } } pub type AiResult = Result; #[cfg(test)] mod tests { use super::*; #[test] fn validation_maps_to_app_error_validation() { let err = AiError::Validation("字段缺失".to_string()); let app: AppError = err.into(); match app { AppError::Validation(msg) => assert!(msg.contains("字段缺失")), other => panic!("期望 AppError::Validation,得到 {:?}", other), } } #[test] fn analysis_not_found_maps_to_not_found() { let err = AiError::AnalysisNotFound("abc-123".to_string()); let app: AppError = err.into(); match app { AppError::NotFound(msg) => assert!(msg.contains("分析结果")), other => panic!("期望 AppError::NotFound,得到 {:?}", other), } } #[test] fn prompt_not_found_maps_to_not_found() { let err = AiError::PromptNotFound("lab_report_interpretation".to_string()); let app: AppError = err.into(); match app { AppError::NotFound(msg) => assert!(msg.contains("Prompt 模板")), other => panic!("期望 AppError::NotFound,得到 {:?}", other), } } #[test] fn provider_unavailable_maps_to_internal() { let err = AiError::ProviderUnavailable("Claude".to_string()); let app: AppError = err.into(); match app { AppError::Internal(msg) => assert!(msg.contains("Claude")), other => panic!("期望 AppError::Internal,得到 {:?}", other), } } #[test] fn provider_error_maps_to_internal() { let err = AiError::ProviderError("超时".to_string()); let app: AppError = err.into(); match app { AppError::Internal(msg) => assert!(msg.contains("超时")), other => panic!("期望 AppError::Internal,得到 {:?}", other), } } #[test] fn sanitization_error_maps_to_internal() { let err = AiError::SanitizationError("PII 泄漏".to_string()); let app: AppError = err.into(); match app { AppError::Internal(msg) => assert!(msg.contains("PII 泄漏")), other => panic!("期望 AppError::Internal,得到 {:?}", other), } } #[test] fn template_error_maps_to_internal() { let err = AiError::TemplateError("语法错误".to_string()); let app: AppError = err.into(); match app { AppError::Internal(msg) => assert!(msg.contains("语法错误")), other => panic!("期望 AppError::Internal,得到 {:?}", other), } } #[test] fn rate_limit_maps_to_too_many_requests() { let err = AiError::RateLimitExceeded; let app: AppError = err.into(); match app { AppError::TooManyRequests => {} other => panic!("期望 AppError::TooManyRequests,得到 {:?}", other), } } #[test] fn version_mismatch_maps_directly() { let err = AiError::VersionMismatch; let app: AppError = err.into(); match app { AppError::VersionMismatch => {} other => panic!("期望 AppError::VersionMismatch,得到 {:?}", other), } } #[test] fn db_error_maps_to_internal() { let err = AiError::DbError("连接失败".to_string()); let app: AppError = err.into(); match app { AppError::Internal(msg) => assert!(msg.contains("连接失败")), 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), } } }