1use super::traits::ErrorSeverity;
6use serde::{Deserialize, Serialize};
7use std::fmt;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14pub enum ErrorCode {
15 Success = 0,
18
19 BadRequest = 400,
22 Unauthorized = 401,
24 Forbidden = 403,
26 NotFound = 404,
28 MethodNotAllowed = 405,
30 Conflict = 409,
32 TooManyRequests = 429,
34
35 InternalServerError = 500,
38 BadGateway = 502,
40 ServiceUnavailable = 503,
42 GatewayTimeout = 504,
44
45 AppTicketInvalid = 10012,
48 AppStatusException = 10013,
50 AppPermissionDenied = 19001,
52 AccessTokenInvalid = 99991671,
54 AccessTokenFormatInvalid = 99991661,
56 AppAccessTokenInvalid = 99991664,
58 TenantAccessTokenInvalid = 99991663,
60 SsoTokenInvalid = 99991670,
62 PermissionMissing = 99991672,
64 AccessTokenNoPermission = 99991676,
66 AccessTokenExpiredV2 = 99991677,
68
69 UserSessionInvalid = 99991641,
72 UserSessionNotFound = 99991642,
74 UserSessionTimeout = 99991645,
76 UserIdentityInvalid = 99991669,
78 UserTypeNotSupportedV2 = 99991674,
80 UserIdentityMismatch = 99991675,
82 UserIdInvalid = 99992351,
84 OpenIdInvalid = 99992352,
86 UnionIdInvalid = 99992353,
88
89 AppNotInstalled = 10003,
92 UserNotFound = 60001,
94 UserStatusException = 60002,
96 DepartmentNotFound = 60003,
98 ChatNotFound = 70001,
100 ChatTypeNotSupported = 70002,
102 MessageNotFound = 80001,
104 MessageTypeNotSupported = 80002,
106 FileNotFound = 90001,
108 FileSizeExceeded = 90002,
110 FileTypeNotSupported = 90003,
112
113 CalendarNotFound = 110001,
116 EventNotFound = 110002,
118 EventConflict = 110003,
120 DocumentNotFound = 120001,
122 DocumentPermissionDenied = 120002,
124 DocumentLocked = 120003,
126 SheetNotFound = 120011,
128 TableNotFound = 120021,
130
131 NetworkTimeout = 999001,
134 NetworkConnectionFailed = 999002,
136 DnsResolutionFailed = 999003,
138 SslCertificateError = 999004,
140 ConnectionRefused = 999005,
142
143 SerializationError = 999010,
146 DataFormatError = 999011,
148 EncodingError = 999012,
150
151 AuthenticationFailed = 999020,
154 PermissionDenied = 999021,
156 TokenExpired = 999022,
158 InvalidSignature = 999023,
160
161 ValidationError = 999030,
164 MissingRequiredParameter = 999031,
166 InvalidParameterFormat = 999032,
168 ParameterOutOfRange = 999033,
170
171 BusinessError = 999040,
174 OperationNotSupported = 999041,
176 ResourceConflict = 999042,
178
179 InternalError = 999050,
182 ConfigurationError = 999051,
184 ResourceExhausted = 999052,
186
187 RateLimitExceeded = 999060,
190
191 ResponseTooLarge = 41300,
194
195 CacheMiss = 999070,
198 CacheServiceUnavailable = 999071,
200
201 SecurityPolicyViolation = 999080,
204 AccessDenied = 999081,
206
207 Unknown = 999999,
210}
211
212impl ErrorCode {
213 pub fn from_code(code: i32) -> Self {
215 match code {
216 c if (200..=299).contains(&c) => Self::Success,
217 0 => Self::Success,
218
219 400 => Self::BadRequest,
221 401 => Self::Unauthorized,
222 403 => Self::Forbidden,
223 404 => Self::NotFound,
224 405 => Self::MethodNotAllowed,
225 409 => Self::Conflict,
226 429 => Self::TooManyRequests,
227
228 500 => Self::InternalServerError,
230 502 => Self::BadGateway,
231 503 => Self::ServiceUnavailable,
232 504 => Self::GatewayTimeout,
233
234 10003 => Self::AppNotInstalled,
236 10012 => Self::AppTicketInvalid,
237 10013 => Self::AppStatusException,
238 19001 => Self::AppPermissionDenied,
239 60001 => Self::UserNotFound,
240 60002 => Self::UserStatusException,
241 60003 => Self::DepartmentNotFound,
242 70001 => Self::ChatNotFound,
243 70002 => Self::ChatTypeNotSupported,
244 80001 => Self::MessageNotFound,
245 80002 => Self::MessageTypeNotSupported,
246 90001 => Self::FileNotFound,
247 90002 => Self::FileSizeExceeded,
248 90003 => Self::FileTypeNotSupported,
249 110001 => Self::CalendarNotFound,
250 110002 => Self::EventNotFound,
251 110003 => Self::EventConflict,
252 120001 => Self::DocumentNotFound,
253 120002 => Self::DocumentPermissionDenied,
254 120003 => Self::DocumentLocked,
255 120011 => Self::SheetNotFound,
256 120021 => Self::TableNotFound,
257 99991661 => Self::AccessTokenFormatInvalid,
258 99991671 => Self::AccessTokenInvalid,
259 99991664 => Self::AppAccessTokenInvalid,
260 99991663 => Self::TenantAccessTokenInvalid,
261 99991670 => Self::SsoTokenInvalid,
262 99991672 => Self::PermissionMissing,
263 99991676 => Self::AccessTokenNoPermission,
264 99991677 => Self::AccessTokenExpiredV2,
265 99991641 => Self::UserSessionInvalid,
266 99991642 => Self::UserSessionNotFound,
267 99991645 => Self::UserSessionTimeout,
268 99991669 => Self::UserIdentityInvalid,
269 99991674 => Self::UserTypeNotSupportedV2,
270 99991675 => Self::UserIdentityMismatch,
271 99992351 => Self::UserIdInvalid,
272 99992352 => Self::OpenIdInvalid,
273 99992353 => Self::UnionIdInvalid,
274
275 999001 => Self::NetworkTimeout,
277 999002 => Self::NetworkConnectionFailed,
278 999003 => Self::DnsResolutionFailed,
279 999004 => Self::SslCertificateError,
280 999005 => Self::ConnectionRefused,
281
282 999010 => Self::SerializationError,
284 999011 => Self::DataFormatError,
285 999012 => Self::EncodingError,
286
287 999020 => Self::AuthenticationFailed,
289 999021 => Self::PermissionDenied,
290 999022 => Self::TokenExpired,
291 999023 => Self::InvalidSignature,
292
293 999030 => Self::ValidationError,
295 999031 => Self::MissingRequiredParameter,
296 999032 => Self::InvalidParameterFormat,
297 999033 => Self::ParameterOutOfRange,
298
299 999040 => Self::BusinessError,
301 999041 => Self::OperationNotSupported,
302 999042 => Self::ResourceConflict,
303
304 999050 => Self::InternalError,
306 999051 => Self::ConfigurationError,
307 999052 => Self::ResourceExhausted,
308
309 999060 => Self::RateLimitExceeded,
311
312 41300 => Self::ResponseTooLarge,
314
315 999070 => Self::CacheMiss,
317 999071 => Self::CacheServiceUnavailable,
318
319 999080 => Self::SecurityPolicyViolation,
321 999081 => Self::AccessDenied,
322
323 _ => Self::Unknown,
324 }
325 }
326
327 pub fn as_code(&self) -> i32 {
329 *self as i32
330 }
331
332 pub fn description(&self) -> &'static str {
334 match self {
335 Self::Success => "操作成功",
336
337 Self::BadRequest => "请求参数错误",
339 Self::Unauthorized => "未认证",
340 Self::Forbidden => "禁止访问",
341 Self::NotFound => "资源不存在",
342 Self::MethodNotAllowed => "方法不允许",
343 Self::Conflict => "请求冲突",
344 Self::TooManyRequests => "请求频率过高",
345
346 Self::InternalServerError => "内部服务器错误",
348 Self::BadGateway => "网关错误",
349 Self::ServiceUnavailable => "服务不可用",
350 Self::GatewayTimeout => "网关超时",
351
352 Self::AppNotInstalled => "应用未安装",
354 Self::AppTicketInvalid => "应用票据无效",
355 Self::AppStatusException => "应用状态异常",
356 Self::AppPermissionDenied => "应用权限不足",
357 Self::UserNotFound => "用户不存在",
358 Self::UserStatusException => "用户状态异常",
359 Self::DepartmentNotFound => "部门不存在",
360 Self::ChatNotFound => "群组不存在",
361 Self::ChatTypeNotSupported => "群组类型不支持",
362 Self::MessageNotFound => "消息不存在",
363 Self::MessageTypeNotSupported => "消息类型不支持",
364 Self::FileNotFound => "文件不存在",
365 Self::FileSizeExceeded => "文件大小超限",
366 Self::FileTypeNotSupported => "文件类型不支持",
367 Self::CalendarNotFound => "日历不存在",
368 Self::EventNotFound => "日程不存在",
369 Self::EventConflict => "日程冲突",
370 Self::DocumentNotFound => "文档不存在",
371 Self::DocumentPermissionDenied => "文档权限不足",
372 Self::DocumentLocked => "文档已锁定",
373 Self::SheetNotFound => "工作表不存在",
374 Self::TableNotFound => "表格不存在",
375 Self::AccessTokenFormatInvalid => "访问令牌格式错误",
376 Self::AccessTokenInvalid => "访问令牌无效",
377 Self::AppAccessTokenInvalid => "应用访问令牌无效",
378 Self::TenantAccessTokenInvalid => "租户访问令牌无效",
379 Self::SsoTokenInvalid => "SSO 访问令牌无效",
380 Self::PermissionMissing => "缺少所需权限",
381 Self::AccessTokenNoPermission => "访问令牌权限不足",
382 Self::AccessTokenExpiredV2 => "访问令牌已过期",
383 Self::UserSessionInvalid => "用户会话已失效",
384 Self::UserSessionNotFound => "用户会话不存在",
385 Self::UserSessionTimeout => "用户会话超时",
386 Self::UserIdentityInvalid => "用户身份解析失败",
387 Self::UserTypeNotSupportedV2 => "用户类型不支持",
388 Self::UserIdentityMismatch => "用户身份不匹配",
389 Self::UserIdInvalid => "用户ID非法",
390 Self::OpenIdInvalid => "OpenID 非法",
391 Self::UnionIdInvalid => "UnionID 非法",
392
393 Self::NetworkTimeout => "网络连接超时",
395 Self::NetworkConnectionFailed => "网络连接失败",
396 Self::DnsResolutionFailed => "DNS解析失败",
397 Self::SslCertificateError => "SSL证书错误",
398 Self::ConnectionRefused => "连接被拒绝",
399
400 Self::SerializationError => "JSON解析错误",
402 Self::DataFormatError => "数据格式错误",
403 Self::EncodingError => "编码错误",
404
405 Self::AuthenticationFailed => "身份验证失败",
407 Self::PermissionDenied => "权限被拒绝",
408 Self::TokenExpired => "令牌已过期",
409 Self::InvalidSignature => "无效签名",
410
411 Self::ValidationError => "参数验证失败",
413 Self::MissingRequiredParameter => "缺少必需参数",
414 Self::InvalidParameterFormat => "参数格式错误",
415 Self::ParameterOutOfRange => "参数值超出范围",
416
417 Self::BusinessError => "业务逻辑错误",
419 Self::OperationNotSupported => "操作不被支持",
420 Self::ResourceConflict => "资源状态冲突",
421
422 Self::InternalError => "系统内部错误",
424 Self::ConfigurationError => "配置错误",
425 Self::ResourceExhausted => "资源耗尽",
426
427 Self::RateLimitExceeded => "请求频率限制",
429
430 Self::ResponseTooLarge => "响应体大小超过限制",
432
433 Self::CacheMiss => "缓存未命中",
435 Self::CacheServiceUnavailable => "缓存服务不可用",
436
437 Self::SecurityPolicyViolation => "安全策略违规",
439 Self::AccessDenied => "访问被拒绝",
440
441 Self::Unknown => "未知错误",
442 }
443 }
444
445 pub fn detailed_description(&self) -> &'static str {
447 match self {
448 Self::Success => "请求已成功处理",
449
450 Self::BadRequest => "请求参数格式错误或缺少必需参数,请检查请求内容",
451 Self::Unauthorized => "身份验证失败,请检查访问令牌是否有效",
452 Self::Forbidden => "权限不足,无法访问请求的资源",
453 Self::NotFound => "请求的资源不存在或已被删除",
454 Self::MethodNotAllowed => "当前API不支持此HTTP方法",
455 Self::Conflict => "请求与当前资源状态冲突,请检查资源状态",
456 Self::TooManyRequests => "请求频率超过限制,请降低请求频率后重试",
457
458 Self::InternalServerError => "服务器内部错误,请稍后重试或联系技术支持",
459 Self::BadGateway => "网关服务器错误,请检查网络连接或稍后重试",
460 Self::ServiceUnavailable => "服务暂时不可用,请稍后重试",
461 Self::GatewayTimeout => "网关超时,请稍后重试",
462
463 Self::AppNotInstalled => "应用未安装到当前企业,请在飞书管理后台安装应用",
464 Self::AppTicketInvalid => "应用票据已失效,SDK会自动重新申请",
465 Self::AppStatusException => "应用状态异常,请检查应用是否正常启用",
466 Self::AppPermissionDenied => "应用缺少执行此操作的权限,请在开发者后台配置相应权限",
467 Self::UserNotFound => "指定的用户不存在,请检查用户ID是否正确",
468 Self::UserStatusException => "用户状态异常,可能已被禁用或删除",
469 Self::DepartmentNotFound => "指定的部门不存在,请检查部门ID是否正确",
470 Self::ChatNotFound => "指定的群组不存在或机器人未加入该群组",
471 Self::ChatTypeNotSupported => "当前群组类型不支持此操作",
472 Self::MessageNotFound => "指定的消息不存在或已被删除",
473 Self::MessageTypeNotSupported => "不支持的消息类型",
474 Self::FileNotFound => "指定的文件不存在或已被删除",
475 Self::FileSizeExceeded => "文件大小超出限制,请压缩后重试",
476 Self::FileTypeNotSupported => "不支持的文件类型",
477 Self::CalendarNotFound => "指定的日历不存在",
478 Self::EventNotFound => "指定的日程不存在或已被删除",
479 Self::EventConflict => "日程时间冲突,请选择其他时间",
480 Self::DocumentNotFound => "指定的文档不存在或已被删除",
481 Self::DocumentPermissionDenied => "文档权限不足,请联系文档所有者授权",
482 Self::DocumentLocked => "文档已被其他用户锁定,请稍后再试",
483 Self::SheetNotFound => "指定的工作表不存在",
484 Self::TableNotFound => "指定的表格不存在",
485 Self::AccessTokenFormatInvalid => "访问令牌格式或内容无效,请重新获取或检查传参",
486 Self::AccessTokenInvalid => "用户访问令牌无效或失效,需要重新获取用户授权",
487 Self::AppAccessTokenInvalid => "应用访问令牌无效,请检查应用ID和密钥配置",
488 Self::TenantAccessTokenInvalid => "租户访问令牌无效,请检查应用是否正确安装到企业",
489 Self::SsoTokenInvalid => "SSO 访问令牌无效,请重新登录或检查 SSO 配置",
490 Self::PermissionMissing => "缺少所需权限,请在开发者后台开通对应权限范围",
491 Self::AccessTokenNoPermission => "访问令牌权限不足,请重新授权所需 scope",
492 Self::AccessTokenExpiredV2 => "访问令牌已过期,需要重新获取",
493 Self::UserSessionInvalid => "用户会话失效,请重新登录或刷新会话",
494 Self::UserSessionNotFound => "未找到有效用户会话,请重新登录",
495 Self::UserSessionTimeout => "用户会话已超时,请重新登录",
496 Self::UserIdentityInvalid => "用户身份解析失败,请检查传入的身份标识",
497 Self::UserTypeNotSupportedV2 => "当前用户类型不支持该操作",
498 Self::UserIdentityMismatch => "用户身份不匹配,请检查 user_id/open_id 组合",
499 Self::UserIdInvalid => "用户ID非法,请检查参数格式",
500 Self::OpenIdInvalid => "OpenID 非法,请检查参数格式",
501 Self::UnionIdInvalid => "UnionID 非法,请检查参数格式",
502
503 Self::NetworkTimeout => "网络请求超时,请检查网络连接或增加超时时间",
504 Self::NetworkConnectionFailed => "网络连接失败,请检查网络设置和服务器状态",
505 Self::DnsResolutionFailed => "DNS解析失败,请检查域名设置或网络配置",
506 Self::SslCertificateError => "SSL证书验证失败,请检查证书配置或系统时间",
507 Self::ConnectionRefused => "连接被拒绝,请检查服务器状态和防火墙设置",
508
509 Self::SerializationError => "JSON序列化或反序列化失败,请检查数据格式",
510 Self::DataFormatError => "数据格式不正确,请检查输入数据结构",
511 Self::EncodingError => "数据编码错误,请检查字符编码设置",
512
513 Self::AuthenticationFailed => "身份验证失败,请检查凭据是否正确",
514 Self::PermissionDenied => "权限不足,无法执行此操作",
515 Self::TokenExpired => "访问令牌已过期,需要重新获取",
516 Self::InvalidSignature => "签名验证失败,请检查签名算法和密钥",
517
518 Self::ValidationError => "输入数据验证失败,请检查数据格式和内容",
519 Self::MissingRequiredParameter => "缺少必需的参数,请检查请求参数完整性",
520 Self::InvalidParameterFormat => "参数格式不正确,请按照要求格式提供参数",
521 Self::ParameterOutOfRange => "参数值超出允许范围,请检查参数值是否有效",
522
523 Self::BusinessError => "业务逻辑处理失败,请检查操作条件和业务规则",
524 Self::OperationNotSupported => "当前操作不被支持,请检查API文档或使用替代方法",
525 Self::ResourceConflict => "资源状态冲突,请检查资源当前状态或等待后重试",
526
527 Self::InternalError => "系统内部错误,请联系技术支持",
528 Self::ConfigurationError => "系统配置错误,请检查配置文件或环境变量",
529 Self::ResourceExhausted => "系统资源耗尽,请稍后重试或增加资源配额",
530
531 Self::RateLimitExceeded => "请求频率超过限制,请降低请求频率后重试",
532
533 Self::ResponseTooLarge => "响应体大小超过限制,请减小请求范围或联系管理员调整限制",
534
535 Self::CacheMiss => "缓存中未找到数据,将从数据源获取",
536 Self::CacheServiceUnavailable => "缓存服务不可用,将直接访问数据源",
537
538 Self::SecurityPolicyViolation => "操作违反安全策略,请检查操作权限和安全设置",
539 Self::AccessDenied => "访问被拒绝,请检查访问权限和身份验证",
540
541 Self::Unknown => "未知错误,请联系技术支持获取帮助",
542 }
543 }
544
545 pub fn is_success(&self) -> bool {
547 matches!(self, Self::Success)
548 }
549
550 pub fn is_client_error(&self) -> bool {
552 let code = *self as i32;
553 (400..=499).contains(&code) && code != 429
554 }
555
556 pub fn is_server_error(&self) -> bool {
558 let code = *self as i32;
559 (500..=599).contains(&code)
560 }
561
562 pub fn is_network_error(&self) -> bool {
564 matches!(
565 self,
566 Self::NetworkTimeout
567 | Self::NetworkConnectionFailed
568 | Self::DnsResolutionFailed
569 | Self::SslCertificateError
570 | Self::ConnectionRefused
571 )
572 }
573
574 pub fn is_auth_error(&self) -> bool {
576 matches!(
577 self,
578 Self::Unauthorized
579 | Self::AccessTokenFormatInvalid
580 | Self::AccessTokenInvalid
581 | Self::AppAccessTokenInvalid
582 | Self::TenantAccessTokenInvalid
583 | Self::SsoTokenInvalid
584 | Self::AuthenticationFailed
585 | Self::TokenExpired
586 | Self::AccessTokenExpiredV2
587 | Self::InvalidSignature
588 | Self::UserSessionInvalid
589 | Self::UserSessionNotFound
590 | Self::UserSessionTimeout
591 | Self::UserIdentityInvalid
592 | Self::UserTypeNotSupportedV2
593 | Self::UserIdentityMismatch
594 | Self::UserIdInvalid
595 | Self::OpenIdInvalid
596 | Self::UnionIdInvalid
597 )
598 }
599
600 pub fn is_permission_error(&self) -> bool {
602 matches!(
603 self,
604 Self::Forbidden
605 | Self::AppPermissionDenied
606 | Self::DocumentPermissionDenied
607 | Self::PermissionDenied
608 | Self::PermissionMissing
609 | Self::AccessTokenNoPermission
610 | Self::AccessDenied
611 )
612 }
613
614 pub fn is_retryable(&self) -> bool {
616 match self {
617 Self::Success => false,
618 Self::TooManyRequests => true,
619 Self::InternalServerError => true,
620 Self::BadGateway => true,
621 Self::ServiceUnavailable => true,
622 Self::GatewayTimeout => true,
623 Self::NetworkTimeout => true,
624 Self::NetworkConnectionFailed => true,
625 Self::ConnectionRefused => true,
626 Self::CacheServiceUnavailable => true,
627 Self::RateLimitExceeded => true,
628 _ => false,
629 }
630 }
631
632 pub fn suggested_retry_delay(&self) -> Option<u64> {
634 match self {
635 Self::TooManyRequests => Some(60),
636 Self::InternalServerError => Some(5),
637 Self::BadGateway => Some(3),
638 Self::ServiceUnavailable => Some(10),
639 Self::GatewayTimeout => Some(5),
640 Self::NetworkTimeout => Some(3),
641 Self::NetworkConnectionFailed => Some(5),
642 Self::ConnectionRefused => Some(10),
643 Self::RateLimitExceeded => Some(30),
644 _ => None,
645 }
646 }
647
648 pub fn http_status(&self) -> Option<u16> {
650 match self {
651 Self::ResponseTooLarge => Some(413),
652 _ => {
653 let code = *self as i32;
654 if (100..=599).contains(&code) {
655 Some(code as u16)
656 } else {
657 None
658 }
659 }
660 }
661 }
662
663 pub fn from_http_status(status: u16) -> Self {
665 Self::from_code(status as i32)
666 }
667
668 pub fn for_error_type(error_type: &str) -> Self {
674 match error_type {
675 "Network" | "network" => Self::NetworkConnectionFailed,
677
678 "Authentication" | "authentication" => Self::AuthenticationFailed,
680 "TokenExpired" | "token_expired" => Self::TokenExpired,
681
682 "Permission" | "permission" => Self::PermissionDenied,
684 "Forbidden" | "forbidden" => Self::Forbidden,
685
686 "Validation" | "validation" => Self::ValidationError,
688 "ApiError" | "api_error" => Self::BusinessError,
689
690 "Configuration" | "configuration" => Self::ConfigurationError,
692
693 "Serialization" | "serialization" => Self::SerializationError,
695
696 "Internal" | "internal" => Self::InternalError,
698 "Unknown" | "unknown" => Self::Unknown,
699
700 "BadRequest" | "bad_request" => Self::BadRequest,
702 "Unauthorized" | "unauthorized" => Self::Unauthorized,
703 "NotFound" | "not_found" => Self::NotFound,
704 "Conflict" | "conflict" => Self::Conflict,
705 "TooManyRequests" | "too_many_requests" => Self::TooManyRequests,
706 "InternalServerError" | "internal_server_error" => Self::InternalServerError,
707 "ServiceUnavailable" | "service_unavailable" => Self::ServiceUnavailable,
708
709 _ => Self::Unknown,
711 }
712 }
713
714 pub fn network() -> Self {
716 Self::NetworkConnectionFailed
717 }
718
719 pub fn authentication() -> Self {
721 Self::AuthenticationFailed
722 }
723
724 pub fn permission() -> Self {
726 Self::PermissionDenied
727 }
728
729 pub fn validation() -> Self {
731 Self::ValidationError
732 }
733
734 pub fn configuration() -> Self {
736 Self::ConfigurationError
737 }
738
739 pub fn internal() -> Self {
741 Self::InternalError
742 }
743
744 pub fn serialization() -> Self {
746 Self::SerializationError
747 }
748
749 pub fn unknown() -> Self {
751 Self::Unknown
752 }
753
754 pub fn from_message(message: &str) -> Self {
758 let msg_lower = message.to_lowercase();
759
760 if msg_lower.contains("network")
762 || msg_lower.contains("connection")
763 || msg_lower.contains("timeout")
764 || msg_lower.contains("dns")
765 {
766 return Self::NetworkConnectionFailed;
767 }
768
769 if msg_lower.contains("auth")
771 || msg_lower.contains("token")
772 || msg_lower.contains("unauthorized")
773 {
774 return Self::AuthenticationFailed;
775 }
776
777 if msg_lower.contains("permission")
779 || msg_lower.contains("forbidden")
780 || msg_lower.contains("access denied")
781 {
782 return Self::PermissionDenied;
783 }
784
785 if msg_lower.contains("invalid")
787 || msg_lower.contains("validation")
788 || msg_lower.contains("parameter")
789 {
790 return Self::ValidationError;
791 }
792
793 if msg_lower.contains("config") || msg_lower.contains("setting") {
795 return Self::ConfigurationError;
796 }
797
798 if msg_lower.contains("json")
800 || msg_lower.contains("serialize")
801 || msg_lower.contains("parse")
802 {
803 return Self::SerializationError;
804 }
805
806 if msg_lower.contains("not found") || msg_lower.contains("missing") {
808 return Self::NotFound;
809 }
810
811 if msg_lower.contains("unavailable") || msg_lower.contains("service") {
813 return Self::ServiceUnavailable;
814 }
815
816 Self::Unknown
817 }
818
819 pub fn should_retry(&self, attempt: u32) -> bool {
823 if !self.is_retryable() {
824 return false;
825 }
826
827 match self {
828 Self::TooManyRequests | Self::RateLimitExceeded => attempt < 5,
830 Self::NetworkTimeout | Self::NetworkConnectionFailed => attempt < 3,
831 Self::InternalServerError | Self::BadGateway | Self::GatewayTimeout => attempt < 2,
832 _ => attempt < 3,
833 }
834 }
835
836 pub fn exponential_backoff_delay(&self, attempt: u32) -> Option<u64> {
840 if !self.should_retry(attempt) {
841 return None;
842 }
843
844 let base_delay = self.suggested_retry_delay().unwrap_or(1);
845 let max_delay = match self {
846 Self::TooManyRequests => 300, Self::RateLimitExceeded => 180, _ => 60, };
850
851 let delay = base_delay * 2u64.pow(attempt.saturating_sub(1));
853 Some(delay.min(max_delay))
854 }
855
856 pub fn severity(&self) -> ErrorSeverity {
858 match self {
859 Self::Success => ErrorSeverity::Info,
860
861 Self::BadRequest
863 | Self::Forbidden
864 | Self::NotFound
865 | Self::MethodNotAllowed
866 | Self::Conflict
867 | Self::ValidationError
868 | Self::MissingRequiredParameter
869 | Self::InvalidParameterFormat
870 | Self::ParameterOutOfRange
871 | Self::ResponseTooLarge => ErrorSeverity::Warning,
872
873 Self::Unauthorized
875 | Self::AccessTokenInvalid
876 | Self::AppAccessTokenInvalid
877 | Self::TenantAccessTokenInvalid
878 | Self::AuthenticationFailed
879 | Self::TokenExpired
880 | Self::PermissionDenied
881 | Self::AccessDenied => ErrorSeverity::Error,
882
883 Self::InternalServerError
885 | Self::BadGateway
886 | Self::ServiceUnavailable
887 | Self::GatewayTimeout
888 | Self::NetworkTimeout
889 | Self::NetworkConnectionFailed
890 | Self::DnsResolutionFailed
891 | Self::SslCertificateError
892 | Self::ConnectionRefused
893 | Self::InternalError
894 | Self::ConfigurationError
895 | Self::ResourceExhausted => ErrorSeverity::Critical,
896
897 _ => ErrorSeverity::Error,
899 }
900 }
901
902 pub fn recovery_suggestion(&self) -> &'static str {
904 match self {
905 Self::BadRequest => "请检查请求参数是否正确",
906 Self::Unauthorized => "请重新进行身份验证",
907 Self::Forbidden => "请联系管理员获取相应权限",
908 Self::NotFound => "请确认资源是否存在",
909 Self::TooManyRequests => "请降低请求频率后重试",
910 Self::NetworkConnectionFailed => "请检查网络连接",
911 Self::TokenExpired => "请重新获取访问令牌",
912 Self::ServiceUnavailable => "服务暂时不可用,请稍后重试",
913 Self::InternalServerError => "系统内部错误,请联系技术支持",
914 Self::ValidationError => "请检查输入参数格式",
915 Self::ConfigurationError => "请检查系统配置",
916 Self::ResponseTooLarge => "请减小请求范围或联系管理员调整响应大小限制",
917 Self::Unknown => "发生未知错误,请联系技术支持",
918 _ => "请稍后重试,如问题持续请联系技术支持",
919 }
920 }
921
922 pub fn is_user_error(&self) -> bool {
924 matches!(
925 self,
926 Self::BadRequest
927 | Self::Unauthorized
928 | Self::Forbidden
929 | Self::ValidationError
930 | Self::MissingRequiredParameter
931 | Self::InvalidParameterFormat
932 | Self::ParameterOutOfRange
933 | Self::AccessTokenInvalid
934 | Self::TokenExpired
935 )
936 }
937
938 pub fn is_system_error(&self) -> bool {
940 matches!(
941 self,
942 Self::InternalServerError
943 | Self::BadGateway
944 | Self::ServiceUnavailable
945 | Self::GatewayTimeout
946 | Self::NetworkTimeout
947 | Self::NetworkConnectionFailed
948 | Self::DnsResolutionFailed
949 | Self::SslCertificateError
950 | Self::ConnectionRefused
951 | Self::InternalError
952 | Self::ConfigurationError
953 | Self::ResourceExhausted
954 )
955 }
956
957 pub fn log_level(&self) -> &'static str {
959 match self.severity() {
960 ErrorSeverity::Info => "info",
961 ErrorSeverity::Warning => "warn",
962 ErrorSeverity::Error => "error",
963 ErrorSeverity::Critical => "error",
964 }
965 }
966}
967
968impl fmt::Display for ErrorCode {
969 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
970 write!(f, "{} ({})", self.description(), self.as_code())
971 }
972}
973
974#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
976pub enum ErrorCategory {
977 Success,
979 Authentication,
981 Permission,
983 Parameter,
985 Resource,
987 Server,
989 Network,
991 Business,
993 System,
995 RateLimit,
997 Other,
999}
1000
1001impl ErrorCode {
1002 pub fn category(&self) -> ErrorCategory {
1004 match self {
1005 Self::Success => ErrorCategory::Success,
1006
1007 Self::Unauthorized
1009 | Self::AppTicketInvalid
1010 | Self::AccessTokenFormatInvalid
1011 | Self::AccessTokenInvalid
1012 | Self::AppAccessTokenInvalid
1013 | Self::TenantAccessTokenInvalid
1014 | Self::SsoTokenInvalid
1015 | Self::AuthenticationFailed
1016 | Self::TokenExpired
1017 | Self::AccessTokenExpiredV2
1018 | Self::InvalidSignature
1019 | Self::UserSessionInvalid
1020 | Self::UserSessionNotFound
1021 | Self::UserSessionTimeout
1022 | Self::UserIdentityInvalid
1023 | Self::UserTypeNotSupportedV2
1024 | Self::UserIdentityMismatch
1025 | Self::UserIdInvalid
1026 | Self::OpenIdInvalid
1027 | Self::UnionIdInvalid => ErrorCategory::Authentication,
1028
1029 Self::Forbidden
1031 | Self::AppPermissionDenied
1032 | Self::DocumentPermissionDenied
1033 | Self::PermissionDenied
1034 | Self::PermissionMissing
1035 | Self::AccessTokenNoPermission
1036 | Self::AccessDenied
1037 | Self::SecurityPolicyViolation => ErrorCategory::Permission,
1038
1039 Self::BadRequest
1041 | Self::ValidationError
1042 | Self::MissingRequiredParameter
1043 | Self::InvalidParameterFormat
1044 | Self::ParameterOutOfRange
1045 | Self::ChatTypeNotSupported
1046 | Self::MessageTypeNotSupported
1047 | Self::FileTypeNotSupported => ErrorCategory::Parameter,
1048
1049 Self::NotFound
1051 | Self::AppNotInstalled
1052 | Self::UserNotFound
1053 | Self::UserStatusException
1054 | Self::DepartmentNotFound
1055 | Self::ChatNotFound
1056 | Self::MessageNotFound
1057 | Self::FileNotFound
1058 | Self::FileSizeExceeded
1059 | Self::CalendarNotFound
1060 | Self::EventNotFound
1061 | Self::DocumentNotFound
1062 | Self::DocumentLocked
1063 | Self::SheetNotFound
1064 | Self::TableNotFound => ErrorCategory::Resource,
1065
1066 Self::InternalServerError
1068 | Self::BadGateway
1069 | Self::ServiceUnavailable
1070 | Self::GatewayTimeout
1071 | Self::AppStatusException
1072 | Self::InternalError
1073 | Self::CacheServiceUnavailable => ErrorCategory::Server,
1074
1075 Self::NetworkTimeout
1077 | Self::NetworkConnectionFailed
1078 | Self::DnsResolutionFailed
1079 | Self::SslCertificateError
1080 | Self::ConnectionRefused => ErrorCategory::Network,
1081
1082 Self::Conflict
1084 | Self::EventConflict
1085 | Self::BusinessError
1086 | Self::OperationNotSupported
1087 | Self::ResourceConflict => ErrorCategory::Business,
1088
1089 Self::MethodNotAllowed
1091 | Self::SerializationError
1092 | Self::DataFormatError
1093 | Self::EncodingError
1094 | Self::ConfigurationError
1095 | Self::ResourceExhausted
1096 | Self::ResponseTooLarge => ErrorCategory::System,
1097
1098 Self::TooManyRequests | Self::RateLimitExceeded => ErrorCategory::RateLimit,
1100
1101 _ => ErrorCategory::Other,
1103 }
1104 }
1105}
1106
1107#[cfg(test)]
1108mod tests {
1109 use super::*;
1110
1111 #[test]
1112 fn test_error_code_conversion() {
1113 assert_eq!(ErrorCode::from_code(0), ErrorCode::Success);
1114 assert_eq!(ErrorCode::from_code(404), ErrorCode::NotFound);
1115 assert_eq!(ErrorCode::from_code(999999), ErrorCode::Unknown);
1116 }
1117
1118 #[test]
1120 fn from_code_is_sole_feishu_common_code_mapper() {
1121 let cases = [
1122 (99991661, ErrorCode::AccessTokenFormatInvalid),
1123 (99991663, ErrorCode::TenantAccessTokenInvalid),
1124 (99991664, ErrorCode::AppAccessTokenInvalid),
1125 (99991670, ErrorCode::SsoTokenInvalid),
1126 (99991671, ErrorCode::AccessTokenInvalid),
1127 (99991672, ErrorCode::PermissionMissing),
1128 (99991676, ErrorCode::AccessTokenNoPermission),
1129 (99991677, ErrorCode::AccessTokenExpiredV2),
1130 (99991641, ErrorCode::UserSessionInvalid),
1131 (99991642, ErrorCode::UserSessionNotFound),
1132 (99991645, ErrorCode::UserSessionTimeout),
1133 (99991669, ErrorCode::UserIdentityInvalid),
1134 (99991674, ErrorCode::UserTypeNotSupportedV2),
1135 (99991675, ErrorCode::UserIdentityMismatch),
1136 (99992351, ErrorCode::UserIdInvalid),
1137 (99992352, ErrorCode::OpenIdInvalid),
1138 (99992353, ErrorCode::UnionIdInvalid),
1139 ];
1140 for (raw, expected) in cases {
1141 assert_eq!(
1142 ErrorCode::from_code(raw),
1143 expected,
1144 "from_code({raw}) must be the sole feishu-code mapping path"
1145 );
1146 }
1147 assert_eq!(ErrorCode::from_code(12345678), ErrorCode::Unknown);
1149 }
1150
1151 #[test]
1152 fn test_error_code_properties() {
1153 let not_found = ErrorCode::NotFound;
1154 assert_eq!(not_found.as_code(), 404);
1155 assert_eq!(not_found.description(), "资源不存在");
1156 assert!(not_found.is_client_error());
1157 assert!(!not_found.is_server_error());
1158 assert!(!not_found.is_retryable());
1159
1160 let timeout = ErrorCode::NetworkTimeout;
1161 assert!(timeout.is_network_error());
1162 assert!(timeout.is_retryable());
1163 assert_eq!(timeout.suggested_retry_delay(), Some(3));
1164 }
1165
1166 #[test]
1167 fn test_http_status_conversion() {
1168 assert_eq!(ErrorCode::from_http_status(200), ErrorCode::Success);
1169 assert_eq!(ErrorCode::from_http_status(404), ErrorCode::NotFound);
1170 assert_eq!(
1171 ErrorCode::from_http_status(500),
1172 ErrorCode::InternalServerError
1173 );
1174 }
1175
1176 #[test]
1177 fn test_error_categories() {
1178 let auth_error = ErrorCode::Unauthorized;
1179 assert_eq!(auth_error.category(), ErrorCategory::Authentication);
1180
1181 let perm_error = ErrorCode::Forbidden;
1182 assert_eq!(perm_error.category(), ErrorCategory::Permission);
1183
1184 let net_error = ErrorCode::NetworkTimeout;
1185 assert_eq!(net_error.category(), ErrorCategory::Network);
1186 }
1187
1188 #[test]
1189 fn test_error_code_display() {
1190 let error = ErrorCode::AccessTokenInvalid;
1191 let display = format!("{error}");
1192 assert!(display.contains("访问令牌无效"));
1193 assert!(display.contains("99991671"));
1194 }
1195
1196 #[test]
1197 fn test_new_error_code_methods() {
1198 assert_eq!(ErrorCode::network(), ErrorCode::NetworkConnectionFailed);
1200 assert_eq!(ErrorCode::authentication(), ErrorCode::AuthenticationFailed);
1201 assert_eq!(ErrorCode::permission(), ErrorCode::PermissionDenied);
1202 assert_eq!(ErrorCode::validation(), ErrorCode::ValidationError);
1203 assert_eq!(ErrorCode::configuration(), ErrorCode::ConfigurationError);
1204 assert_eq!(ErrorCode::internal(), ErrorCode::InternalError);
1205 assert_eq!(ErrorCode::serialization(), ErrorCode::SerializationError);
1206 assert_eq!(ErrorCode::unknown(), ErrorCode::Unknown);
1207 }
1208
1209 #[test]
1210 fn test_for_error_type() {
1211 assert_eq!(
1212 ErrorCode::for_error_type("Network"),
1213 ErrorCode::NetworkConnectionFailed
1214 );
1215 assert_eq!(
1216 ErrorCode::for_error_type("authentication"),
1217 ErrorCode::AuthenticationFailed
1218 );
1219 assert_eq!(
1220 ErrorCode::for_error_type("Validation"),
1221 ErrorCode::ValidationError
1222 );
1223 assert_eq!(
1224 ErrorCode::for_error_type("unknown_type"),
1225 ErrorCode::Unknown
1226 );
1227 }
1228
1229 #[test]
1230 fn test_from_message() {
1231 assert_eq!(
1232 ErrorCode::from_message("Network connection failed"),
1233 ErrorCode::NetworkConnectionFailed
1234 );
1235 assert_eq!(
1236 ErrorCode::from_message("Authentication token invalid"),
1237 ErrorCode::AuthenticationFailed
1238 );
1239 assert_eq!(
1240 ErrorCode::from_message("Permission denied"),
1241 ErrorCode::PermissionDenied
1242 );
1243 assert_eq!(
1244 ErrorCode::from_message("Invalid parameter"),
1245 ErrorCode::ValidationError
1246 );
1247 assert_eq!(
1248 ErrorCode::from_message("JSON parse error"),
1249 ErrorCode::SerializationError
1250 );
1251 assert_eq!(
1252 ErrorCode::from_message("Unknown error occurred"),
1253 ErrorCode::Unknown
1254 );
1255 }
1256
1257 #[test]
1258 fn test_retry_logic() {
1259 let retryable_error = ErrorCode::NetworkTimeout;
1260 assert!(retryable_error.should_retry(0));
1261 assert!(retryable_error.should_retry(1));
1262 assert!(!retryable_error.should_retry(3)); let non_retryable_error = ErrorCode::BadRequest;
1265 assert!(!non_retryable_error.should_retry(0));
1266
1267 let delay = retryable_error.exponential_backoff_delay(2);
1269 assert!(delay.is_some());
1270 assert!(delay.unwrap() > 1); }
1272
1273 #[test]
1274 fn test_error_severity() {
1275 assert_eq!(ErrorCode::Success.severity(), ErrorSeverity::Info);
1276 assert_eq!(ErrorCode::BadRequest.severity(), ErrorSeverity::Warning);
1277 assert_eq!(ErrorCode::Unauthorized.severity(), ErrorSeverity::Error);
1278 assert_eq!(
1279 ErrorCode::InternalServerError.severity(),
1280 ErrorSeverity::Critical
1281 );
1282 }
1283
1284 #[test]
1285 fn test_recovery_suggestions() {
1286 let suggestion = ErrorCode::NetworkConnectionFailed.recovery_suggestion();
1287 assert!(suggestion.contains("网络"));
1288
1289 let suggestion = ErrorCode::TokenExpired.recovery_suggestion();
1290 assert!(suggestion.contains("令牌"));
1291
1292 let suggestion = ErrorCode::ValidationError.recovery_suggestion();
1293 assert!(suggestion.contains("参数"));
1294 }
1295
1296 #[test]
1297 fn test_user_vs_system_errors() {
1298 assert!(ErrorCode::BadRequest.is_user_error());
1300 assert!(ErrorCode::Unauthorized.is_user_error());
1301 assert!(ErrorCode::ValidationError.is_user_error());
1302
1303 assert!(ErrorCode::InternalServerError.is_system_error());
1305 assert!(ErrorCode::NetworkTimeout.is_system_error());
1306 assert!(ErrorCode::ServiceUnavailable.is_system_error());
1307
1308 assert!(!ErrorCode::BadRequest.is_system_error());
1310 assert!(!ErrorCode::InternalServerError.is_user_error());
1311 }
1312
1313 #[test]
1314 fn test_log_levels() {
1315 assert_eq!(ErrorCode::Success.log_level(), "info");
1316 assert_eq!(ErrorCode::BadRequest.log_level(), "warn");
1317 assert_eq!(ErrorCode::Unauthorized.log_level(), "error");
1318 assert_eq!(ErrorCode::InternalServerError.log_level(), "error");
1319 }
1320}
1321
1322#[cfg(test)]
1323mod severity_tests {
1324 use super::*;
1325
1326 #[test]
1327 fn test_error_severity_basic() {
1328 assert_eq!(ErrorSeverity::Info.as_level(), 0);
1329 assert_eq!(ErrorSeverity::Warning.as_level(), 1);
1330 assert_eq!(ErrorSeverity::Error.as_level(), 2);
1331 assert_eq!(ErrorSeverity::Critical.as_level(), 3);
1332 }
1333
1334 #[test]
1335 fn test_error_severity_ordering() {
1336 assert!(ErrorSeverity::Info < ErrorSeverity::Warning);
1337 assert!(ErrorSeverity::Warning < ErrorSeverity::Error);
1338 assert!(ErrorSeverity::Error < ErrorSeverity::Critical);
1339 }
1340
1341 #[test]
1342 fn test_error_severity_from_level() {
1343 assert_eq!(ErrorSeverity::from_level(0), ErrorSeverity::Info);
1344 assert_eq!(ErrorSeverity::from_level(1), ErrorSeverity::Warning);
1345 assert_eq!(ErrorSeverity::from_level(2), ErrorSeverity::Error);
1346 assert_eq!(ErrorSeverity::from_level(3), ErrorSeverity::Critical);
1347 assert_eq!(ErrorSeverity::from_level(99), ErrorSeverity::Error); }
1349
1350 #[test]
1351 fn test_error_severity_properties() {
1352 assert!(!ErrorSeverity::Info.requires_immediate_action());
1353 assert!(!ErrorSeverity::Warning.requires_immediate_action());
1354 assert!(ErrorSeverity::Error.requires_immediate_action());
1355 assert!(ErrorSeverity::Critical.requires_immediate_action());
1356
1357 assert!(!ErrorSeverity::Info.requires_user_intervention());
1358 assert!(!ErrorSeverity::Warning.requires_user_intervention());
1359 assert!(ErrorSeverity::Error.requires_user_intervention());
1360 assert!(ErrorSeverity::Critical.requires_user_intervention());
1361
1362 assert!(ErrorSeverity::Info.is_auto_recoverable());
1363 assert!(ErrorSeverity::Warning.is_auto_recoverable());
1364 assert!(!ErrorSeverity::Error.is_auto_recoverable());
1365 assert!(!ErrorSeverity::Critical.is_auto_recoverable());
1366 }
1367
1368 #[test]
1369 fn test_error_severity_display() {
1370 assert_eq!(format!("{}", ErrorSeverity::Info), "信息");
1371 assert_eq!(format!("{}", ErrorSeverity::Warning), "警告");
1372 assert_eq!(format!("{}", ErrorSeverity::Error), "错误");
1373 assert_eq!(format!("{}", ErrorSeverity::Critical), "严重错误");
1374 }
1375}