1use std::path::Path;
2use std::sync::PoisonError;
3
4use thiserror::Error;
5
6#[derive(Debug, Error)]
7pub enum AppError {
8 #[error("配置错误: {0}")]
9 Config(String),
10 #[error("数据库错误: {0}")]
11 Database(String),
12 #[error("无效输入: {0}")]
13 InvalidInput(String),
14 #[error("IO 错误: {path}: {source}")]
15 Io {
16 path: String,
17 #[source]
18 source: std::io::Error,
19 },
20 #[error("{context}: {source}")]
21 IoContext {
22 context: String,
23 #[source]
24 source: std::io::Error,
25 },
26 #[error("JSON 解析错误: {path}: {source}")]
27 Json {
28 path: String,
29 #[source]
30 source: serde_json::Error,
31 },
32 #[error("JSON 序列化失败: {source}")]
33 JsonSerialize {
34 #[source]
35 source: serde_json::Error,
36 },
37 #[error("TOML 解析错误: {path}: {source}")]
38 Toml {
39 path: String,
40 #[source]
41 source: toml::de::Error,
42 },
43 #[error("锁获取失败: {0}")]
44 Lock(String),
45 #[error("MCP 校验失败: {0}")]
46 McpValidation(String),
47 #[error("{0}")]
48 Message(String),
49 #[error("{zh} ({en})")]
50 Localized {
51 key: &'static str,
52 zh: String,
53 en: String,
54 },
55}
56
57impl AppError {
58 pub fn io(path: impl AsRef<Path>, source: std::io::Error) -> Self {
59 Self::Io {
60 path: path.as_ref().display().to_string(),
61 source,
62 }
63 }
64
65 pub fn json(path: impl AsRef<Path>, source: serde_json::Error) -> Self {
66 Self::Json {
67 path: path.as_ref().display().to_string(),
68 source,
69 }
70 }
71
72 pub fn toml(path: impl AsRef<Path>, source: toml::de::Error) -> Self {
73 Self::Toml {
74 path: path.as_ref().display().to_string(),
75 source,
76 }
77 }
78
79 pub fn localized(key: &'static str, zh: impl Into<String>, en: impl Into<String>) -> Self {
80 Self::Localized {
81 key,
82 zh: zh.into(),
83 en: en.into(),
84 }
85 }
86}
87
88impl<T> From<PoisonError<T>> for AppError {
89 fn from(err: PoisonError<T>) -> Self {
90 Self::Lock(err.to_string())
91 }
92}
93
94impl From<rusqlite::Error> for AppError {
95 fn from(err: rusqlite::Error) -> Self {
96 Self::Database(err.to_string())
97 }
98}
99
100impl From<AppError> for String {
101 fn from(err: AppError) -> Self {
102 err.to_string()
103 }
104}
105
106pub fn format_skill_error(
108 code: &str,
109 context: &[(&str, &str)],
110 suggestion: Option<&str>,
111) -> String {
112 use serde_json::json;
113
114 let mut ctx_map = serde_json::Map::new();
115 for (key, value) in context {
116 ctx_map.insert(key.to_string(), json!(value));
117 }
118
119 let error_obj = json!({
120 "code": code,
121 "context": ctx_map,
122 "suggestion": suggestion,
123 });
124
125 serde_json::to_string(&error_obj).unwrap_or_else(|_| {
126 format!("ERROR:{code}")
128 })
129}