wechat_minapp/
error.rs

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