wechat_minapp/
error.rs

1//! 微信小程序错误处理模块
2//!
3//! 该模块定义了与微信小程序 API 交互过程中可能遇到的所有错误类型,
4//! 包括微信官方错误码映射和第三方库错误转换。
5//!
6//! # 错误类型
7//!
8//! 模块包含两种主要的错误类型:
9//!
10//! - [`Error`][]: 主要的错误枚举,包含所有可能的错误情况
11//! - [`ErrorCode`]: 微信官方错误码的 Rust 枚举表示
12//!
13//! # 错误处理示例
14//!
15//! ```no_run
16//! use wechat_minapp::error::{Error, ErrorCode};
17//!
18//! // 处理微信 API 返回的错误
19//! fn handle_wechat_error(errcode: i32, errmsg: String) -> Result<(), Error> {
20//!     if let Some(code) = ErrorCode::from_repr(errcode) {
21//!         return Err(Error::from((code, errmsg)));
22//!     }
23//!     Ok(())
24//! }
25//!
26//! // 处理网络错误
27//! async fn make_api_request() -> Result<(), Error> {
28//!     let client = reqwest::Client::new();
29//!     let response = client.get("https://api.weixin.qq.com/some/endpoint")
30//!         .send()
31//!         .await?; // 自动转换为 Error::Reqwest
32//!     Ok(())
33//! }
34//! ```
35//!
36//! # 错误转换
37//!
38//! 模块自动实现了从常见第三方库错误到 [`Error`] 的转换:
39//!
40//! - `reqwest::Error` → `Error::Reqwest`
41//! - `serde_json::Error` → `Error::SerdeJson`
42//! - `base64::DecodeError` → `Error::Base64Decode`
43//! - `aes::cipher::InvalidLength` → `Error::AesInvalidLength`
44//!
45//! 这使得错误处理更加方便,可以使用 `?` 操作符自动转换。
46
47use serde_repr::Deserialize_repr;
48
49use aes::cipher::InvalidLength as AesInvalidLength;
50use aes::cipher::block_padding::UnpadError;
51use base64::DecodeError as Base64DecodeError;
52use reqwest::Error as ReqwestError;
53use serde_json::Error as SerdeJsonError;
54use strum::Display;
55
56/// 微信小程序 SDK 错误枚举
57///
58/// 包含了所有可能遇到的错误类型,包括微信 API 错误、网络错误、加解密错误等。
59///
60/// # 错误分类
61///
62/// ## 微信 API 错误
63///
64/// 这些错误对应微信官方文档中的错误码:
65///
66/// - `InvalidCredential`: 凭证无效
67/// - `InvalidCode`: 登录 code 无效
68/// - `RateLimitExceeded`: API 调用频率限制
69/// - 等等...
70///
71/// ## 第三方库错误
72///
73/// 自动转换的第三方库错误:
74///
75/// - `Reqwest`: HTTP 请求错误
76/// - `SerdeJson`: JSON 序列化/反序列化错误
77/// - `Base64Decode`: Base64 解码错误
78/// - `AesInvalidLength`: AES 加解密长度错误
79///
80/// ## 系统错误
81///
82/// - `System`: 微信系统繁忙
83/// - `InternalServer`: 内部服务器错误
84///
85/// # 示例
86///
87/// ```no_run
88/// use wechat_minapp::error::Error;
89///
90/// async function make_request() -> Result<(), Error> {
91///     // 使用 ? 操作符自动转换错误
92///     let response = reqwest::get("https://api.weixin.qq.com/endpoint").await?;
93///     let data: serde_json::Value = response.json().await?;
94///     Ok(())
95/// }
96/// ```
97///
98/// # 序列化
99///
100/// 此枚举使用 `thiserror` 派生宏,提供了良好的错误消息格式。
101/// 每个变体都包含描述性的错误信息。
102#[non_exhaustive]
103#[derive(Debug, thiserror::Error)]
104pub enum Error {
105    // #[error("system error: {0}")]
106    // System(String),
107    // #[error("invalid credential: {0}")]
108    // InvalidCredential(String),
109    // #[error("invalid grant type: {0}")]
110    // InvalidGrantType(String),
111    // #[error("invalid app id: {0}")]
112    // InvalidAppId(String),
113    // #[error("invalid code: {0}")]
114    // InvalidCode(String),
115    // #[error("invalid parameter: {0}")]
116    // InvalidParameter(String),
117    // #[error("invalid secret: {0}")]
118    // InvalidSecret(String),
119    // #[error("forbidden ip: {0}")]
120    // ForbiddenIp(String),
121    // #[error("code blocked: {0}")]
122    // CodeBlocked(String),
123    // #[error("secret frozen: {0}")]
124    // SecretFrozen(String),
125    // #[error("missing access token: {0}")]
126    // MissingAccessToken(String),
127    // #[error("missing app id: {0}")]
128    // MissingAppId(String),
129    // #[error("missing secret: {0}")]
130    // MissingSecret(String),
131    // #[error("missing code: {0}")]
132    // MissingCode(String),
133    // #[error("required post method: {0}")]
134    // RequiredPostMethod(String),
135    // #[error("daily request limit exceeded: {0}")]
136    // DailyRequestLimitExceeded(String),
137    // #[error("rate limit exceeded: {0}")]
138    // RateLimitExceeded(String),
139    // #[error("forbidden token: {0}")]
140    // ForbiddenToken(String),
141    // #[error("account frozen: {0}")]
142    // AccountFrozen(String),
143    // #[error("third party token: {0}")]
144    // ThirdPartyToken(String),
145    // #[error("session key not existed or expired: {0}")]
146    // SessionKeyNotExistedOrExpired(String),
147    // #[error("invalid signature method: {0}")]
148    // InvalidSignatureMethod(String),
149    // #[error("invalid signature: {0}")]
150    // InvalidSignature(String),
151    // #[error("confirm required: {0}")]
152    // ConfirmRequired(String),
153    // #[error("request denied one day: {0}")]
154    // RequestDeniedOneDay(String),
155    // #[error("request denied one hour: {0}")]
156    // RequestDeniedOneHour(String),
157    // #[error("unpad error: {0}")]
158    // Unpad(UnpadError),
159    // #[error("aes invalid length: {0}")]
160    // AesInvalidLength(#[from] AesInvalidLength),
161    // #[error("base64 decode error: {0}")]
162    // Base64Decode(#[from] Base64DecodeError),
163    // #[error("reqwest: {0}")]
164    // Reqwest(#[from] ReqwestError),
165    // #[error("json error: {0}")]
166    // SerdeJson(#[from] SerdeJsonError),
167    // #[error("internal error: {0}")]
168    // InternalServer(String),
169    /// 微信系统繁忙,请稍候再试
170    #[error("system error: {0}")]
171    System(String),
172
173    /// 获取 access_token 时 AppSecret 错误,或者 access_token 无效
174    #[error("invalid credential: {0}")]
175    InvalidCredential(String),
176
177    /// 不合法的凭证类型
178    #[error("invalid grant type: {0}")]
179    InvalidGrantType(String),
180
181    /// 不合法的 AppID,请检查 AppID 的正确性
182    #[error("invalid app id: {0}")]
183    InvalidAppId(String),
184
185    /// 登录 code 无效或已过期
186    #[error("invalid code: {0}")]
187    InvalidCode(String),
188
189    /// 请求参数错误
190    #[error("invalid parameter: {0}")]
191    InvalidParameter(String),
192
193    /// 无效的 appsecret,请检查 appsecret 的正确性
194    #[error("invalid secret: {0}")]
195    InvalidSecret(String),
196
197    /// IP 地址不在白名单中
198    #[error("forbidden ip: {0}")]
199    ForbiddenIp(String),
200
201    /// 高风险等级用户,小程序登录被拦截
202    #[error("code blocked: {0}")]
203    CodeBlocked(String),
204
205    /// AppSecret 已被冻结,请登录小程序平台解冻
206    #[error("secret frozen: {0}")]
207    SecretFrozen(String),
208
209    /// 缺少 access_token 参数
210    #[error("missing access token: {0}")]
211    MissingAccessToken(String),
212
213    /// 缺少 appid 参数
214    #[error("missing app id: {0}")]
215    MissingAppId(String),
216
217    /// 缺少 secret 参数
218    #[error("missing secret: {0}")]
219    MissingSecret(String),
220
221    /// 缺少 code 参数
222    #[error("missing code: {0}")]
223    MissingCode(String),
224
225    /// 需要 POST 请求
226    #[error("required post method: {0}")]
227    RequiredPostMethod(String),
228
229    /// 调用超过天级别频率限制
230    #[error("daily request limit exceeded: {0}")]
231    DailyRequestLimitExceeded(String),
232
233    /// API 调用太频繁,请稍候再试
234    #[error("rate limit exceeded: {0}")]
235    RateLimitExceeded(String),
236
237    /// 禁止使用 token 接口
238    #[error("forbidden token: {0}")]
239    ForbiddenToken(String),
240
241    /// 账号已冻结
242    #[error("account frozen: {0}")]
243    AccountFrozen(String),
244
245    /// 第三方平台 API 需要使用第三方平台专用 token
246    #[error("third party token: {0}")]
247    ThirdPartyToken(String),
248
249    /// session_key 不存在或已过期
250    #[error("session key not existed or expired: {0}")]
251    SessionKeyNotExistedOrExpired(String),
252
253    /// 无效的签名方法
254    #[error("invalid signature method: {0}")]
255    InvalidSignatureMethod(String),
256
257    /// 无效的签名
258    #[error("invalid signature: {0}")]
259    InvalidSignature(String),
260
261    /// 此次调用需要管理员确认,请耐心等候
262    #[error("confirm required: {0}")]
263    ConfirmRequired(String),
264
265    /// 该IP调用请求已被公众号管理员拒绝,请24小时后再试
266    #[error("request denied one day: {0}")]
267    RequestDeniedOneDay(String),
268
269    /// 该IP调用请求已被公众号管理员拒绝,请1小时后再试
270    #[error("request denied one hour: {0}")]
271    RequestDeniedOneHour(String),
272
273    /// AES 解密时数据填充错误
274    #[error("unpad error: {0}")]
275    Unpad(UnpadError),
276
277    /// AES 加解密长度错误
278    #[error("aes invalid length: {0}")]
279    AesInvalidLength(#[from] AesInvalidLength),
280
281    /// Base64 解码错误
282    #[error("base64 decode error: {0}")]
283    Base64Decode(#[from] Base64DecodeError),
284
285    /// HTTP 请求错误
286    #[error("reqwest: {0}")]
287    Reqwest(#[from] ReqwestError),
288
289    /// JSON 序列化/反序列化错误
290    #[error("json error: {0}")]
291    SerdeJson(#[from] SerdeJsonError),
292
293    /// 内部服务器错误
294    #[error("internal error: {0}")]
295    InternalServer(String),
296}
297
298impl From<UnpadError> for Error {
299    fn from(error: UnpadError) -> Self {
300        Error::Unpad(error)
301    }
302}
303
304/// 微信官方错误码枚举
305///
306/// 对应微信小程序 API 返回的错误码,每个错误码都有对应的中文描述。
307///
308/// # 使用示例
309///
310/// ```
311/// use wechat_minapp::error::ErrorCode;
312///
313/// // 从数值获取错误码
314/// if let Some(error_code) = ErrorCode::from_repr(40029) {
315///     println!("错误: {}", error_code);
316///     // 输出: "错误: code 无效"
317/// }
318///
319/// // 获取错误码的数值
320/// let code_value = ErrorCode::InvalidCode as i32;
321/// assert_eq!(code_value, 40029);
322/// ```
323///
324/// # 错误码说明
325///
326/// 完整的错误码列表请参考:
327/// [微信官方文档 - 全局返回码说明](https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/#%E5%85%A8%E5%B1%80%E8%BF%94%E5%9B%9E%E7%A0%81%E8%AF%B4%E6%98%8E)
328#[derive(Debug, Deserialize_repr, Display)]
329#[repr(i32)]
330pub enum ErrorCode {
331    #[strum(serialize = "系统繁忙,此时请开发者稍候再试")]
332    System = -1,
333    #[strum(
334        serialize = "获取 access_token 时 AppSecret 错误,或者 access_token 无效。请开发者认真比对 AppSecret 的正确性,或查看是否正在为恰当的公众号调用接口"
335    )]
336    InvalidCredential = 40001,
337    #[strum(serialize = "不合法的凭证类型")]
338    InvalidGrantType = 40002,
339    #[strum(serialize = "不合法的 AppID ,请开发者检查 AppID 的正确性,避免异常字符,注意大小写")]
340    InvalidAppId = 40013,
341    #[strum(serialize = "code 无效")]
342    InvalidCode = 40029,
343    #[strum(serialize = "参数错误")]
344    InvalidParameter = 40097,
345    #[strum(serialize = "无效的appsecret,请检查appsecret的正确性")]
346    InvalidSecret = 40125,
347    #[strum(serialize = "将ip添加到ip白名单列表即可")]
348    ForbiddenIp = 40164,
349    #[strum(serialize = "高风险等级用户,小程序登录拦截 。风险等级详见用户安全解方案")]
350    CodeBlocked = 40226,
351    #[strum(serialize = "AppSecret已被冻结,请登录小程序平台解冻后再次调用")]
352    SecretFrozen = 40243,
353    #[strum(serialize = "缺少 access token 参数")]
354    MissingAccessToken = 41001,
355    #[strum(serialize = "缺少 appid 参数")]
356    MissingAppId = 41002,
357    #[strum(serialize = "缺少 secret 参数")]
358    MissingSecret = 41004,
359    MissingCode = 41008,
360    #[strum(serialize = "需要 POST 请求")]
361    RequiredPostMethod = 43002,
362    #[strum(serialize = "调用超过天级别频率限制。可调用clear_quota接口恢复调用额度。")]
363    DailyRequestLimitExceeded = 45009,
364    #[strum(serialize = "API 调用太频繁,请稍候再试")]
365    RateLimitExceeded = 45011,
366    #[strum(serialize = "禁止使用 token 接口")]
367    ForbiddenToken = 50004,
368    #[strum(serialize = "账号已冻结")]
369    AccountFrozen = 50007,
370    #[strum(serialize = "第三方平台 API 需要使用第三方平台专用 token")]
371    ThirdPartyToken = 61024,
372    #[strum(serialize = "session_key is not existed or expired")]
373    SessionKeyNotExistedOrExpired = 87007,
374    #[strum(serialize = "invalid sig_method")]
375    InvalidSignatureMethod = 87008,
376    #[strum(serialize = "无效的签名")]
377    InvalidSignature = 87009,
378    #[strum(serialize = "此次调用需要管理员确认,请耐心等候")]
379    ConfirmRequired = 89503,
380    #[strum(
381        serialize = "该IP调用求请求已被公众号管理员拒绝,请24小时后再试,建议调用前与管理员沟通确认"
382    )]
383    RequestDeniedOneDay = 89506,
384    #[strum(
385        serialize = "该IP调用求请求已被公众号管理员拒绝,请1小时后再试,建议调用前与管理员沟通确认"
386    )]
387    RequestDeniedOneHour = 89507,
388}
389
390impl From<(ErrorCode, String)> for Error {
391    /// 从微信错误码和消息创建 Error
392    ///
393    /// # 参数
394    ///
395    /// - `(code, message)`: 微信错误码和对应的错误消息
396    ///
397    /// # 返回
398    ///
399    /// 对应的 `Error` 枚举变体
400    fn from((code, message): (ErrorCode, String)) -> Self {
401        use ErrorCode::*;
402
403        match code {
404            System => Error::System(message),
405            InvalidCredential => Error::InvalidCredential(message),
406            InvalidGrantType => Error::InvalidGrantType(message),
407            InvalidAppId => Error::InvalidAppId(message),
408            InvalidCode => Error::InvalidCode(message),
409            InvalidParameter => Error::InvalidParameter(message),
410            InvalidSecret => Error::InvalidSecret(message),
411            ForbiddenIp => Error::ForbiddenIp(message),
412            CodeBlocked => Error::CodeBlocked(message),
413            SecretFrozen => Error::SecretFrozen(message),
414            MissingAccessToken => Error::MissingAccessToken(message),
415            MissingAppId => Error::MissingAppId(message),
416            MissingSecret => Error::MissingSecret(message),
417            MissingCode => Error::MissingCode(message),
418            RequiredPostMethod => Error::RequiredPostMethod(message),
419            DailyRequestLimitExceeded => Error::DailyRequestLimitExceeded(message),
420            RateLimitExceeded => Error::RateLimitExceeded(message),
421            ForbiddenToken => Error::ForbiddenToken(message),
422            AccountFrozen => Error::AccountFrozen(message),
423            ThirdPartyToken => Error::ThirdPartyToken(message),
424            SessionKeyNotExistedOrExpired => Error::SessionKeyNotExistedOrExpired(message),
425            InvalidSignatureMethod => Error::InvalidSignatureMethod(message),
426            InvalidSignature => Error::InvalidSignature(message),
427            ConfirmRequired => Error::ConfirmRequired(message),
428            RequestDeniedOneDay => Error::RequestDeniedOneDay(message),
429            RequestDeniedOneHour => Error::RequestDeniedOneHour(message),
430        }
431    }
432}