open_lark/core/error.rs
1use thiserror::Error;
2
3/// 飞书开放平台API错误类型
4///
5/// 包含所有可能的API调用错误,提供详细的错误信息和处理建议。
6/// 支持错误分类、重试判断和用户友好的错误消息。
7///
8/// # 错误类型分类
9///
10/// - **网络错误**: RequestError, IOErr, UrlParseError
11/// - **数据错误**: DeserializeError, DataError
12/// - **参数错误**: IllegalParamError, BadRequest
13/// - **API错误**: ApiError, APIError
14/// - **认证错误**: MissingAccessToken
15///
16/// # 错误处理示例
17///
18/// ```rust
19/// use open_lark::core::error::LarkAPIError;
20///
21/// fn handle_api_error(error: LarkAPIError) {
22/// match error {
23/// LarkAPIError::MissingAccessToken => {
24/// println!("请检查应用凭据配置");
25/// }
26/// LarkAPIError::ApiError { code, message, .. } if code == 403 => {
27/// println!("权限不足: {}", message);
28/// }
29/// err if err.is_retryable() => {
30/// println!("网络错误,可以重试: {}", err.user_friendly_message());
31/// }
32/// _ => {
33/// println!("操作失败: {}", error.user_friendly_message());
34/// }
35/// }
36/// }
37/// ```
38///
39/// # 最佳实践
40///
41/// - 使用 `is_retryable()` 判断是否可以重试
42/// - 使用 `user_friendly_message()` 获取用户友好的错误提示
43/// - 使用 `is_permission_error()` 检查权限相关错误
44#[derive(Error, Debug)]
45pub enum LarkAPIError {
46 /// 输入输出错误
47 ///
48 /// 通常由文件操作、网络IO等底层操作失败引起。
49 #[error("IO error: {0}")]
50 IOErr(String),
51
52 /// 非法参数错误
53 ///
54 /// 当传入的参数不符合API要求时抛出,如无效的ID格式、超出范围的值等。
55 #[error("Invalid parameter: {0}")]
56 IllegalParamError(String),
57
58 /// JSON反序列化错误
59 ///
60 /// 当API响应的JSON格式无法解析为预期的数据结构时发生。
61 #[error("JSON deserialization error: {0}")]
62 DeserializeError(String),
63
64 /// HTTP请求失败
65 ///
66 /// 网络请求层面的错误,如连接超时、DNS解析失败等。通常可以重试。
67 #[error("HTTP request failed: {0}")]
68 RequestError(String),
69
70 /// URL解析错误
71 ///
72 /// 当构建的API请求URL格式不正确时发生。
73 #[error("URL parse error: {0}")]
74 UrlParseError(String),
75
76 /// 增强的API错误
77 ///
78 /// 包含错误码、消息和请求ID的完整错误信息,便于调试和问题追踪。
79 #[error("API error: {message} (code: {code}, request_id: {request_id:?})")]
80 ApiError {
81 /// API错误码
82 code: i32,
83 /// 错误消息
84 message: String,
85 /// 请求ID,用于问题追踪
86 request_id: Option<String>,
87 },
88
89 /// 缺少访问令牌
90 ///
91 /// 当API调用需要认证但未提供有效的访问令牌时发生。
92 #[error("Missing access token")]
93 MissingAccessToken,
94
95 /// 错误的请求
96 ///
97 /// 请求格式或内容不符合API规范。
98 #[error("Bad request: {0}")]
99 BadRequest(String),
100
101 /// 数据处理错误
102 ///
103 /// 数据验证、转换或处理过程中发生的错误。
104 #[error("Data error: {0}")]
105 DataError(String),
106
107 /// 标准API响应错误
108 ///
109 /// 飞书开放平台返回的标准错误响应,包含完整的错误信息。
110 #[error("API error: {msg} (code: {code})")]
111 APIError {
112 /// API错误码
113 code: i32,
114 /// 错误消息
115 msg: String,
116 /// 详细错误信息
117 error: Option<String>,
118 },
119}
120
121impl Clone for LarkAPIError {
122 fn clone(&self) -> Self {
123 match self {
124 Self::IOErr(msg) => Self::IOErr(msg.clone()),
125 Self::IllegalParamError(msg) => Self::IllegalParamError(msg.clone()),
126 Self::DeserializeError(msg) => Self::DeserializeError(msg.clone()),
127 Self::RequestError(msg) => Self::RequestError(msg.clone()),
128 Self::UrlParseError(msg) => Self::UrlParseError(msg.clone()),
129 Self::ApiError {
130 code,
131 message,
132 request_id,
133 } => Self::ApiError {
134 code: *code,
135 message: message.clone(),
136 request_id: request_id.clone(),
137 },
138 Self::MissingAccessToken => Self::MissingAccessToken,
139 Self::BadRequest(msg) => Self::BadRequest(msg.clone()),
140 Self::DataError(msg) => Self::DataError(msg.clone()),
141 Self::APIError { code, msg, error } => Self::APIError {
142 code: *code,
143 msg: msg.clone(),
144 error: error.clone(),
145 },
146 }
147 }
148}
149
150impl From<std::io::Error> for LarkAPIError {
151 fn from(err: std::io::Error) -> Self {
152 Self::IOErr(err.to_string())
153 }
154}
155
156impl From<serde_json::Error> for LarkAPIError {
157 fn from(err: serde_json::Error) -> Self {
158 Self::DeserializeError(err.to_string())
159 }
160}
161
162impl From<reqwest::Error> for LarkAPIError {
163 fn from(err: reqwest::Error) -> Self {
164 Self::RequestError(err.to_string())
165 }
166}
167
168impl From<url::ParseError> for LarkAPIError {
169 fn from(err: url::ParseError) -> Self {
170 Self::UrlParseError(err.to_string())
171 }
172}
173
174/// 错误严重程度
175///
176/// 用于对错误进行分级,帮助确定错误处理策略和用户提示方式。
177///
178/// # 使用场景
179///
180/// - **Info**: 信息性消息,通常不需要特殊处理
181/// - **Warning**: 警告信息,可能影响功能但不阻断操作
182/// - **Error**: 错误信息,导致操作失败但系统可恢复
183/// - **Critical**: 严重错误,可能导致系统不稳定
184///
185/// # 示例
186///
187/// ```rust
188/// use open_lark::core::error::ErrorSeverity;
189///
190/// fn log_error(severity: ErrorSeverity, message: &str) {
191/// match severity {
192/// ErrorSeverity::Info => println!("ℹ️ {}", message),
193/// ErrorSeverity::Warning => println!("⚠️ {}", message),
194/// ErrorSeverity::Error => eprintln!("❌ {}", message),
195/// ErrorSeverity::Critical => eprintln!("🚨 CRITICAL: {}", message),
196/// }
197/// }
198/// ```
199#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
200pub enum ErrorSeverity {
201 /// 信息级别 - 一般性提示信息
202 Info,
203 /// 警告级别 - 可能的问题但不影响核心功能
204 Warning,
205 /// 错误级别 - 操作失败但系统可恢复
206 Error,
207 /// 严重错误级别 - 可能影响系统稳定性
208 Critical,
209}
210
211impl LarkAPIError {
212 /// 创建包含上下文信息的API错误
213 ///
214 /// # 参数
215 /// - `code`: 错误码
216 /// - `message`: 错误消息
217 /// - `request_id`: 请求ID,用于问题追踪
218 ///
219 /// # 示例
220 /// ```rust
221 /// use open_lark::core::error::LarkAPIError;
222 ///
223 /// let error = LarkAPIError::api_error(
224 /// 403,
225 /// "权限不足",
226 /// Some("req_123456".to_string())
227 /// );
228 /// ```
229 pub fn api_error<M: Into<String>>(code: i32, message: M, request_id: Option<String>) -> Self {
230 Self::ApiError {
231 code,
232 message: message.into(),
233 request_id,
234 }
235 }
236
237 /// 创建非法参数错误
238 ///
239 /// # 参数
240 /// - `message`: 错误详细信息
241 ///
242 /// # 示例
243 /// ```rust
244 /// use open_lark::core::error::LarkAPIError;
245 ///
246 /// let error = LarkAPIError::illegal_param("用户ID格式不正确");
247 /// ```
248 pub fn illegal_param<T: Into<String>>(message: T) -> Self {
249 Self::IllegalParamError(message.into())
250 }
251
252 /// 检查是否为权限相关错误
253 ///
254 /// 用于判断错误是否由权限不足引起,便于进行相应的错误处理。
255 ///
256 /// # 返回值
257 /// - `true`: 权限相关错误
258 /// - `false`: 其他类型错误
259 pub fn is_permission_error(&self) -> bool {
260 match self {
261 Self::ApiError { code, .. } => {
262 *code == 403
263 || matches!(
264 crate::core::error_codes::LarkErrorCode::from_code(*code),
265 Some(crate::core::error_codes::LarkErrorCode::Forbidden)
266 )
267 }
268 _ => false,
269 }
270 }
271
272 /// 检查错误是否可以重试
273 ///
274 /// 判断当前错误是否为临时性错误,可以通过重试解决。
275 /// 通常网络超时、连接失败等错误可以重试。
276 ///
277 /// # 返回值
278 /// - `true`: 可以重试的错误
279 /// - `false`: 不可重试的错误(如参数错误、权限错误)
280 ///
281 /// # 示例
282 /// ```rust
283 /// use open_lark::core::error::LarkAPIError;
284 ///
285 /// let error = LarkAPIError::RequestError("连接超时".to_string());
286 /// if error.is_retryable() {
287 /// println!("可以重试该请求");
288 /// }
289 /// ```
290 pub fn is_retryable(&self) -> bool {
291 match self {
292 Self::ApiError { code, .. } => {
293 if let Some(error_code) = crate::core::error_codes::LarkErrorCode::from_code(*code)
294 {
295 error_code.is_retryable()
296 } else {
297 false
298 }
299 }
300 Self::RequestError(req_err) => {
301 req_err.contains("timeout")
302 || req_err.contains("timed out")
303 || req_err.contains("connect")
304 || req_err.contains("connection")
305 }
306 _ => false,
307 }
308 }
309
310 /// 获取用户友好的错误消息
311 ///
312 /// 将技术性的错误信息转换为用户容易理解的提示信息。
313 /// 包含错误原因和可能的解决建议。
314 ///
315 /// # 返回值
316 /// 经过本地化和优化的错误消息字符串
317 ///
318 /// # 示例
319 /// ```rust
320 /// use open_lark::core::error::LarkAPIError;
321 ///
322 /// let error = LarkAPIError::MissingAccessToken;
323 /// println!("错误提示: {}", error.user_friendly_message());
324 /// // 输出: "缺少访问令牌,请检查认证配置"
325 /// ```
326 pub fn user_friendly_message(&self) -> String {
327 match self {
328 Self::ApiError { code, message, .. } => {
329 if let Some(error_code) = crate::core::error_codes::LarkErrorCode::from_code(*code)
330 {
331 error_code.detailed_description().to_string()
332 } else {
333 format!("API调用失败: {message} (错误码: {code})")
334 }
335 }
336 Self::MissingAccessToken => "缺少访问令牌,请检查认证配置".to_string(),
337 Self::IllegalParamError(msg) => format!("参数错误: {msg}"),
338 Self::RequestError(req_err) => {
339 if req_err.contains("timeout") || req_err.contains("timed out") {
340 "请求超时,请检查网络连接".to_string()
341 } else if req_err.contains("connect") || req_err.contains("connection") {
342 "连接失败,请检查网络设置".to_string()
343 } else {
344 format!("网络请求失败: {req_err}")
345 }
346 }
347 _ => self.to_string(),
348 }
349 }
350}