feat(types): 错误体系重构 — ErrorKind + error code + Serialize
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Rust (crates/zclaw-types/src/error.rs): - 新增 ErrorKind enum (17 种) + Serde Serialize/Deserialize - 新增 error_codes 模块 (稳定错误码 E4040-E5110) - ZclawError 新增 kind() / code() 方法 - 新增 ErrorDetail struct + Serialize impl - 保留所有现有变体和构造器 (零破坏性) - 新增 12 个测试: kind 映射 + code 稳定性 + JSON 序列化 TypeScript (desktop/src/lib/error-types.ts): - 新增 RustErrorKind / RustErrorDetail 类型定义 - 新增 tryParseRustError() 结构化错误解析 - 新增 classifyRustError() 按 ErrorKind 分类 - classifyError() 优先解析结构化错误,fallback 字符串匹配 - 17 种 ErrorKind → 中文标题映射 验证: cargo check ✓ | tsc ✓ | 62 zclaw-types tests ✓
This commit is contained in:
@@ -45,6 +45,193 @@ export interface RecoveryStep {
|
||||
label?: string;
|
||||
}
|
||||
|
||||
// === Structured Error from Rust Backend ===
|
||||
|
||||
/**
|
||||
* Error kinds matching Rust `zclaw_types::ErrorKind`.
|
||||
* When the backend returns structured error JSON, use these for classification.
|
||||
*/
|
||||
export type RustErrorKind =
|
||||
| 'not_found'
|
||||
| 'permission'
|
||||
| 'auth'
|
||||
| 'llm'
|
||||
| 'tool'
|
||||
| 'storage'
|
||||
| 'config'
|
||||
| 'http'
|
||||
| 'timeout'
|
||||
| 'validation'
|
||||
| 'loop_detected'
|
||||
| 'rate_limit'
|
||||
| 'mcp'
|
||||
| 'security'
|
||||
| 'hand'
|
||||
| 'export'
|
||||
| 'internal';
|
||||
|
||||
/**
|
||||
* Structured error response from Rust backend.
|
||||
* Matches `zclaw_types::ErrorDetail` serialization.
|
||||
*/
|
||||
export interface RustErrorDetail {
|
||||
kind: RustErrorKind;
|
||||
code: string; // e.g., "E4040", "E5001"
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map Rust ErrorKind to frontend ErrorCategory.
|
||||
*/
|
||||
const ERROR_KIND_TO_CATEGORY: Record<RustErrorKind, ErrorCategory> = {
|
||||
not_found: 'client',
|
||||
permission: 'permission',
|
||||
auth: 'auth',
|
||||
llm: 'server',
|
||||
tool: 'system',
|
||||
storage: 'system',
|
||||
config: 'config',
|
||||
http: 'network',
|
||||
timeout: 'timeout',
|
||||
validation: 'validation',
|
||||
loop_detected: 'system',
|
||||
rate_limit: 'client',
|
||||
mcp: 'system',
|
||||
security: 'permission',
|
||||
hand: 'system',
|
||||
export: 'system',
|
||||
internal: 'system',
|
||||
};
|
||||
|
||||
/**
|
||||
* Map Rust ErrorKind to error severity.
|
||||
*/
|
||||
const ERROR_KIND_TO_SEVERITY: Record<RustErrorKind, ErrorSeverity> = {
|
||||
not_found: 'low',
|
||||
permission: 'medium',
|
||||
auth: 'high',
|
||||
llm: 'high',
|
||||
tool: 'medium',
|
||||
storage: 'high',
|
||||
config: 'medium',
|
||||
http: 'medium',
|
||||
timeout: 'medium',
|
||||
validation: 'low',
|
||||
loop_detected: 'medium',
|
||||
rate_limit: 'medium',
|
||||
mcp: 'medium',
|
||||
security: 'high',
|
||||
hand: 'medium',
|
||||
export: 'low',
|
||||
internal: 'high',
|
||||
};
|
||||
|
||||
/**
|
||||
* Map Rust ErrorKind to user-friendly title (Chinese).
|
||||
*/
|
||||
const ERROR_KIND_TO_TITLE: Record<RustErrorKind, string> = {
|
||||
not_found: '资源未找到',
|
||||
permission: '权限不足',
|
||||
auth: '认证失败',
|
||||
llm: '模型服务错误',
|
||||
tool: '工具执行失败',
|
||||
storage: '存储错误',
|
||||
config: '配置错误',
|
||||
http: '网络请求错误',
|
||||
timeout: '请求超时',
|
||||
validation: '输入无效',
|
||||
loop_detected: '检测到循环调用',
|
||||
rate_limit: '请求过于频繁',
|
||||
mcp: 'MCP 协议错误',
|
||||
security: '安全检查未通过',
|
||||
hand: '自主能力执行失败',
|
||||
export: '导出失败',
|
||||
internal: '内部错误',
|
||||
};
|
||||
|
||||
/**
|
||||
* Try to parse a Tauri command error as a structured Rust error.
|
||||
* Returns null if the error is not in structured format.
|
||||
*/
|
||||
export function tryParseRustError(error: unknown): RustErrorDetail | null {
|
||||
if (typeof error === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(error);
|
||||
if (parsed && typeof parsed.kind === 'string' && typeof parsed.code === 'string') {
|
||||
return parsed as RustErrorDetail;
|
||||
}
|
||||
} catch {
|
||||
// Not JSON — fall through to string matching
|
||||
}
|
||||
}
|
||||
if (typeof error === 'object' && error !== null) {
|
||||
const obj = error as Record<string, unknown>;
|
||||
if (typeof obj.kind === 'string' && typeof obj.code === 'string') {
|
||||
return error as RustErrorDetail;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Classify a structured Rust error into an AppError.
|
||||
*/
|
||||
export function classifyRustError(rustError: RustErrorDetail, originalError?: unknown): AppError {
|
||||
const kind = rustError.kind as RustErrorKind;
|
||||
return {
|
||||
id: `err_${Date.now()}_${generateRandomString(6)}`,
|
||||
category: ERROR_KIND_TO_CATEGORY[kind] ?? 'system',
|
||||
severity: ERROR_KIND_TO_SEVERITY[kind] ?? 'medium',
|
||||
title: ERROR_KIND_TO_TITLE[kind] ?? '未知错误',
|
||||
message: rustError.message,
|
||||
technicalDetails: `[${rustError.code}] ${rustError.kind}`,
|
||||
recoverable: !['auth', 'security', 'internal'].includes(kind),
|
||||
recoverySteps: getRecoveryStepsForKind(kind),
|
||||
timestamp: new Date(),
|
||||
originalError: import.meta.env.DEV ? originalError : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function getRecoveryStepsForKind(kind: RustErrorKind): RecoveryStep[] {
|
||||
switch (kind) {
|
||||
case 'auth':
|
||||
return [
|
||||
{ description: '重新登录或重新连接' },
|
||||
{ description: '检查 API Key 是否有效' },
|
||||
];
|
||||
case 'timeout':
|
||||
return [
|
||||
{ description: '稍后重试' },
|
||||
{ description: '尝试简化请求内容' },
|
||||
];
|
||||
case 'rate_limit':
|
||||
return [
|
||||
{ description: '等待片刻后重试' },
|
||||
{ description: '减少请求频率' },
|
||||
];
|
||||
case 'permission':
|
||||
return [
|
||||
{ description: '联系管理员获取权限' },
|
||||
{ description: '检查当前角色是否有操作权限' },
|
||||
];
|
||||
case 'storage':
|
||||
return [
|
||||
{ description: '检查磁盘空间' },
|
||||
{ description: '重启应用后重试' },
|
||||
];
|
||||
case 'llm':
|
||||
return [
|
||||
{ description: '检查模型配置是否正确' },
|
||||
{ description: '切换到其他模型重试' },
|
||||
];
|
||||
default:
|
||||
return [
|
||||
{ description: '重试操作' },
|
||||
{ description: '刷新页面后重试' },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// === Error Detection Patterns ===
|
||||
|
||||
interface ErrorPattern {
|
||||
@@ -345,6 +532,13 @@ function matchPattern(error: unknown): { pattern: ErrorPattern; match: string }
|
||||
* Classify an error and create an AppError with recovery suggestions.
|
||||
*/
|
||||
export function classifyError(error: unknown): AppError {
|
||||
// Priority 1: structured Rust error (when backend returns ErrorDetail JSON)
|
||||
const rustError = tryParseRustError(error);
|
||||
if (rustError) {
|
||||
return classifyRustError(rustError, error);
|
||||
}
|
||||
|
||||
// Priority 2: string pattern matching (existing behavior)
|
||||
const matched = matchPattern(error);
|
||||
|
||||
if (matched) {
|
||||
|
||||
Reference in New Issue
Block a user