wechat_backend_auth/
error.rs

1use thiserror::Error;
2
3/// 微信授权错误类型
4#[derive(Error, Debug)]
5pub enum WeChatError {
6    /// 配置错误
7    #[error("配置错误: {0}")]
8    Configuration(String),
9
10    /// 网络传输错误
11    #[error("网络传输错误: {0}")]
12    Transport(#[from] TransportError),
13
14    /// 响应解析失败
15    #[error("响应解析失败 - URL: {url}, 错误: {error}, 预览: {preview}")]
16    ResponseParseFailed {
17        url: String,
18        error: String,
19        preview: String,
20    },
21
22    /// 微信API通用错误
23    #[error("微信API错误 (errcode: {errcode}): {errmsg}")]
24    ApiError { errcode: i32, errmsg: String },
25
26    // ===== Access Token 相关错误 =====
27    /// Access Token过期或无效
28    ///
29    /// 错误码: 40001, 40014, 42001
30    ///
31    /// 解决方案:
32    /// 1. 检查AppSecret是否正确
33    /// 2. 使用refresh_token刷新access_token
34    /// 3. 如果refresh_token也过期,需要重新授权
35    #[error("Access Token已过期或无效 (errcode: {code})")]
36    AccessTokenExpired { code: i32 },
37
38    /// Access Token缺失
39    ///
40    /// 错误码: 41001
41    ///
42    /// 解决方案: 检查API调用时是否传递了access_token参数
43    #[error("缺少access_token参数")]
44    AccessTokenMissing,
45
46    /// 用户修改密码导致Token失效
47    ///
48    /// 错误码: 42007
49    ///
50    /// 原因: 用户修改了微信密码,导致之前授权的所有token失效
51    ///
52    /// 解决方案: 引导用户重新进行授权
53    #[error("用户修改密码,access_token和refresh_token已失效,需要重新授权")]
54    TokenInvalidated,
55
56    // ===== Authorization Code 相关错误 =====
57    /// 无效的授权码
58    ///
59    /// 错误码: 40029
60    ///
61    /// 原因:
62    /// - 授权码格式错误
63    /// - 授权码不是通过微信授权流程获得
64    /// - AppID与授权码不匹配
65    ///
66    /// 解决方案: 检查授权流程是否正确,确保使用正确的AppID
67    #[error("无效的授权码")]
68    InvalidCode,
69
70    /// 授权码已被使用
71    ///
72    /// 错误码: 40163
73    ///
74    /// 原因:
75    /// - 授权码(code)只能使用一次
76    /// - 页面刷新或重复请求导致code被重复使用
77    /// - 服务端错误导致回调接口被调用多次
78    ///
79    /// 解决方案:
80    /// 1. 检查前端是否有重复请求
81    /// 2. 确保回调接口没有抛出异常
82    /// 3. 避免页面刷新时重复使用旧的code
83    /// 4. 让用户重新进行授权获取新的code
84    #[error("授权码已被使用(code只能使用一次,请重新授权)")]
85    CodeUsed,
86
87    /// 授权码已过期
88    ///
89    /// 错误码: 42003
90    ///
91    /// 原因: 授权码(code)的有效期为5分钟,超时后无法使用
92    ///
93    /// 解决方案: 引导用户重新进行授权获取新的code
94    #[error("授权码已过期(有效期5分钟,请重新授权)")]
95    CodeExpired,
96
97    // ===== Refresh Token 相关错误 =====
98    /// Refresh Token无效
99    ///
100    /// 错误码: 40030
101    ///
102    /// 解决方案: 引导用户重新授权
103    #[error("Refresh Token无效")]
104    RefreshTokenInvalid,
105
106    /// Refresh Token已过期
107    ///
108    /// 错误码: 42002
109    ///
110    /// 原因: refresh_token的有效期为30天,超时后无法使用
111    ///
112    /// 解决方案: 引导用户重新进行授权
113    #[error("Refresh Token已过期(有效期30天,请重新授权)")]
114    RefreshTokenExpired,
115
116    // ===== 权限和限制相关错误 =====
117    /// 用户未授权该API
118    ///
119    /// 错误码: 50001
120    ///
121    /// 原因:
122    /// - 授权作用域不足
123    /// - 账号类型不支持该API
124    /// - 未配置授权回调域名
125    ///
126    /// 解决方案:
127    /// 1. 检查授权时的scope参数
128    /// 2. 确认账号类型是否支持(如需要认证的服务号)
129    /// 3. 在微信后台配置授权回调域名
130    #[error("用户未授权该API,请检查授权作用域和账号权限")]
131    UserUnauthorized,
132
133    /// API调用频率超限
134    ///
135    /// 错误码: 45009(分钟级), 45011(日级)
136    ///
137    /// 解决方案:
138    /// 1. 实施请求限流
139    /// 2. 使用缓存减少API调用
140    /// 3. 等待限制解除后重试
141    #[error("API调用频率超限 (errcode: {code}): {msg}")]
142    RateLimitExceeded { code: i32, msg: String },
143
144    /// 服务器IP未在白名单中
145    ///
146    /// 错误码: 40164
147    ///
148    /// 解决方案: 在微信公众平台后台添加服务器IP到白名单
149    #[error("服务器IP未在微信后台白名单中")]
150    IpNotWhitelisted,
151
152    // ===== 系统错误 =====
153    /// 微信系统繁忙
154    ///
155    /// 错误码: -1
156    ///
157    /// 解决方案: 这是临时性错误,等待后重试即可
158    #[error("微信系统繁忙,请稍后重试")]
159    SystemBusy,
160
161    /// 参数错误
162    ///
163    /// 错误码: 61451
164    ///
165    /// 解决方案: 检查API调用参数是否正确
166    #[error("请求参数错误: {0}")]
167    InvalidParameter(String),
168
169    /// HTTP构建错误
170    #[error("HTTP请求构建错误: {0}")]
171    HttpBuildError(String),
172}
173
174/// 网络传输错误
175#[derive(Error, Debug)]
176pub enum TransportError {
177    /// HTTP请求失败
178    #[error("HTTP请求失败: {0}")]
179    RequestFailed(String),
180
181    /// 连接超时
182    #[error("连接超时")]
183    Timeout,
184
185    /// SSL/TLS错误
186    #[error("SSL/TLS错误: {0}")]
187    TlsError(String),
188
189    /// 网络连接错误
190    #[error("网络连接错误: {0}")]
191    ConnectionError(String),
192
193    /// 其他传输错误
194    #[error("传输错误: {0}")]
195    Other(String),
196}
197
198impl From<reqwest::Error> for WeChatError {
199    fn from(err: reqwest::Error) -> Self {
200        if err.is_timeout() {
201            WeChatError::Transport(TransportError::Timeout)
202        } else if err.is_connect() {
203            WeChatError::Transport(TransportError::ConnectionError(err.to_string()))
204        } else if err.is_request() {
205            WeChatError::Transport(TransportError::RequestFailed(err.to_string()))
206        } else {
207            WeChatError::Transport(TransportError::Other(err.to_string()))
208        }
209    }
210}
211
212/// 从微信API错误码创建对应的错误类型
213///
214/// 根据微信官方文档将错误码映射到具体的错误类型,便于业务层做针对性处理
215///
216/// # 参数
217///
218/// - `errcode`: 微信API返回的错误码
219/// - `errmsg`: 微信API返回的错误消息
220///
221/// # 返回
222///
223/// 返回对应的 `WeChatError` 类型
224pub(crate) fn from_errcode(errcode: i32, errmsg: String) -> WeChatError {
225    match errcode {
226        // 系统错误
227        -1 => WeChatError::SystemBusy,
228
229        // Access Token相关错误
230        40001 | 40014 => WeChatError::AccessTokenExpired { code: errcode },
231        41001 => WeChatError::AccessTokenMissing,
232        42001 => WeChatError::AccessTokenExpired { code: errcode },
233        42007 => WeChatError::TokenInvalidated,
234
235        // Authorization Code相关错误
236        40029 => WeChatError::InvalidCode,
237        40163 => WeChatError::CodeUsed,
238        42003 => WeChatError::CodeExpired,
239
240        // Refresh Token相关错误
241        40030 => WeChatError::RefreshTokenInvalid,
242        42002 => WeChatError::RefreshTokenExpired,
243
244        // IP白名单错误
245        40164 => WeChatError::IpNotWhitelisted,
246
247        // 权限和作用域错误
248        50001 => WeChatError::UserUnauthorized,
249
250        // 频率限制错误
251        45009 | 45011 => WeChatError::RateLimitExceeded {
252            code: errcode,
253            msg: errmsg.clone(),
254        },
255
256        // 参数错误
257        61451 => WeChatError::InvalidParameter(errmsg.clone()),
258
259        // 其他未分类错误
260        _ => WeChatError::ApiError { errcode, errmsg },
261    }
262}
263
264impl WeChatError {
265    /// 判断错误是否可以重试
266    ///
267    /// # 返回值
268    ///
269    /// - `true`: 错误是临时性的,可以重试
270    /// - `false`: 错误需要人工介入,重试无效
271    ///
272    /// # 示例
273    ///
274    /// ```
275    /// # use wechat_backend_auth::WeChatError;
276    /// # fn example(error: WeChatError) {
277    /// if error.is_retriable() {
278    ///     // 等待后重试
279    ///     println!("可以重试");
280    /// } else {
281    ///     println!("无法重试,需要人工处理");
282    /// }
283    /// # }
284    /// ```
285    pub fn is_retriable(&self) -> bool {
286        matches!(
287            self,
288            WeChatError::SystemBusy
289                | WeChatError::Transport(TransportError::Timeout)
290                | WeChatError::Transport(TransportError::ConnectionError(_))
291                | WeChatError::RateLimitExceeded { .. }
292        )
293    }
294
295    /// 判断错误是否需要重新授权
296    ///
297    /// # 返回值
298    ///
299    /// - `true`: 需要引导用户重新进行微信授权
300    /// - `false`: 不需要重新授权
301    ///
302    /// # 示例
303    ///
304    /// ```
305    /// # use wechat_backend_auth::WeChatError;
306    /// # fn example(error: WeChatError) {
307    /// if error.requires_reauthorization() {
308    ///     // 清除本地token缓存
309    ///     // 返回授权URL给前端
310    ///     println!("需要重新授权");
311    /// }
312    /// # }
313    /// ```
314    pub fn requires_reauthorization(&self) -> bool {
315        matches!(
316            self,
317            WeChatError::CodeExpired
318                | WeChatError::CodeUsed
319                | WeChatError::RefreshTokenExpired
320                | WeChatError::RefreshTokenInvalid
321                | WeChatError::TokenInvalidated
322        )
323    }
324
325    /// 判断错误是否是配置问题
326    ///
327    /// # 返回值
328    ///
329    /// - `true`: 是配置问题,需要修改配置
330    /// - `false`: 不是配置问题
331    ///
332    /// # 示例
333    ///
334    /// ```
335    /// # use wechat_backend_auth::WeChatError;
336    /// # fn example(error: WeChatError) {
337    /// if error.is_configuration_error() {
338    ///     // 记录配置错误日志
339    ///     // 发送告警通知
340    ///     eprintln!("配置错误: {}", error);
341    /// }
342    /// # }
343    /// ```
344    pub fn is_configuration_error(&self) -> bool {
345        matches!(
346            self,
347            WeChatError::Configuration(_)
348                | WeChatError::IpNotWhitelisted
349                | WeChatError::UserUnauthorized
350                | WeChatError::AccessTokenMissing
351        )
352    }
353
354    /// 获取错误的推荐重试延迟(秒)
355    ///
356    /// # 返回值
357    ///
358    /// - `Some(seconds)`: 建议延迟的秒数
359    /// - `None`: 不建议重试
360    ///
361    /// # 示例
362    ///
363    /// ```
364    /// # use wechat_backend_auth::WeChatError;
365    /// # async fn example(error: WeChatError) {
366    /// if let Some(delay) = error.recommended_retry_delay() {
367    ///     println!("建议{}秒后重试", delay);
368    ///     // tokio::time::sleep(Duration::from_secs(delay)).await;
369    /// }
370    /// # }
371    /// ```
372    pub fn recommended_retry_delay(&self) -> Option<u64> {
373        match self {
374            WeChatError::SystemBusy => Some(1),
375            WeChatError::RateLimitExceeded { code, .. } => match code {
376                45009 => Some(60),   // 分钟级限制,等待1分钟
377                45011 => Some(3600), // 日级限制,等待1小时
378                _ => Some(60),
379            },
380            WeChatError::Transport(TransportError::Timeout) => Some(2),
381            WeChatError::Transport(TransportError::ConnectionError(_)) => Some(5),
382            _ => None,
383        }
384    }
385}