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}