Skip to main content

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