1use std::fmt::{Display, Formatter};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5pub enum LarkErrorCode {
6 Success = 0,
8
9 AppTicketInvalid = 10012,
12 AccessTokenInvalid = 99991671,
14 AppAccessTokenInvalid = 99991664,
16 TenantAccessTokenInvalid = 99991663,
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 AppNotInstalled = 10003,
48 AppStatusException = 10013,
50 AppPermissionDenied = 19001,
52 UserNotFound = 60001,
54 UserStatusException = 60002,
56 DepartmentNotFound = 60003,
58 ChatNotFound = 70001,
60 ChatTypeNotSupported = 70002,
62 MessageNotFound = 80001,
64 MessageTypeNotSupported = 80002,
66 FileNotFound = 90001,
68 FileSizeExceeded = 90002,
70 FileTypeNotSupported = 90003,
72
73 CalendarNotFound = 110001,
76 EventNotFound = 110002,
78 EventConflict = 110003,
80
81 DocumentNotFound = 120001,
84 DocumentPermissionDenied = 120002,
86 DocumentLocked = 120003,
88 SheetNotFound = 120011,
90 TableNotFound = 120021,
92
93 AppNotPublished = 130001,
96 AppVersionIncompatible = 130002,
98
99 NetworkTimeout = 999001,
102 NetworkConnectionFailed = 999002,
104 DnsResolutionFailed = 999003,
106 SslCertificateError = 999004,
108}
109
110impl LarkErrorCode {
111 pub fn from_code(code: i32) -> Option<Self> {
113 match code {
114 0 => Some(Self::Success),
115 10003 => Some(Self::AppNotInstalled),
116 10012 => Some(Self::AppTicketInvalid),
117 10013 => Some(Self::AppStatusException),
118 19001 => Some(Self::AppPermissionDenied),
119 99991671 => Some(Self::AccessTokenInvalid),
120 99991664 => Some(Self::AppAccessTokenInvalid),
121 99991663 => Some(Self::TenantAccessTokenInvalid),
122 400 => Some(Self::BadRequest),
123 401 => Some(Self::Unauthorized),
124 403 => Some(Self::Forbidden),
125 404 => Some(Self::NotFound),
126 405 => Some(Self::MethodNotAllowed),
127 409 => Some(Self::Conflict),
128 429 => Some(Self::TooManyRequests),
129 500 => Some(Self::InternalServerError),
130 502 => Some(Self::BadGateway),
131 503 => Some(Self::ServiceUnavailable),
132 504 => Some(Self::GatewayTimeout),
133 60001 => Some(Self::UserNotFound),
134 60002 => Some(Self::UserStatusException),
135 60003 => Some(Self::DepartmentNotFound),
136 70001 => Some(Self::ChatNotFound),
137 70002 => Some(Self::ChatTypeNotSupported),
138 80001 => Some(Self::MessageNotFound),
139 80002 => Some(Self::MessageTypeNotSupported),
140 90001 => Some(Self::FileNotFound),
141 90002 => Some(Self::FileSizeExceeded),
142 90003 => Some(Self::FileTypeNotSupported),
143 110001 => Some(Self::CalendarNotFound),
144 110002 => Some(Self::EventNotFound),
145 110003 => Some(Self::EventConflict),
146 120001 => Some(Self::DocumentNotFound),
147 120002 => Some(Self::DocumentPermissionDenied),
148 120003 => Some(Self::DocumentLocked),
149 120011 => Some(Self::SheetNotFound),
150 120021 => Some(Self::TableNotFound),
151 130001 => Some(Self::AppNotPublished),
152 130002 => Some(Self::AppVersionIncompatible),
153 999001 => Some(Self::NetworkTimeout),
154 999002 => Some(Self::NetworkConnectionFailed),
155 999003 => Some(Self::DnsResolutionFailed),
156 999004 => Some(Self::SslCertificateError),
157 _ => None,
158 }
159 }
160
161 pub fn description(&self) -> &'static str {
163 match self {
164 Self::Success => "操作成功",
165 Self::AppNotInstalled => "应用未安装",
166 Self::AppTicketInvalid => "应用票据无效",
167 Self::AppStatusException => "应用状态异常",
168 Self::AppPermissionDenied => "应用权限不足",
169 Self::AccessTokenInvalid => "访问令牌无效",
170 Self::AppAccessTokenInvalid => "应用访问令牌无效",
171 Self::TenantAccessTokenInvalid => "租户访问令牌无效",
172 Self::BadRequest => "请求参数错误",
173 Self::Unauthorized => "认证失败",
174 Self::Forbidden => "权限不足",
175 Self::NotFound => "资源不存在",
176 Self::MethodNotAllowed => "请求方法不允许",
177 Self::Conflict => "请求冲突",
178 Self::TooManyRequests => "请求频率过高",
179 Self::InternalServerError => "内部服务器错误",
180 Self::BadGateway => "网关错误",
181 Self::ServiceUnavailable => "服务不可用",
182 Self::GatewayTimeout => "网关超时",
183 Self::UserNotFound => "用户不存在",
184 Self::UserStatusException => "用户状态异常",
185 Self::DepartmentNotFound => "部门不存在",
186 Self::ChatNotFound => "群组不存在",
187 Self::ChatTypeNotSupported => "群组类型不支持",
188 Self::MessageNotFound => "消息不存在",
189 Self::MessageTypeNotSupported => "消息类型不支持",
190 Self::FileNotFound => "文件不存在",
191 Self::FileSizeExceeded => "文件大小超限",
192 Self::FileTypeNotSupported => "文件类型不支持",
193 Self::CalendarNotFound => "日历不存在",
194 Self::EventNotFound => "日程不存在",
195 Self::EventConflict => "日程冲突",
196 Self::DocumentNotFound => "文档不存在",
197 Self::DocumentPermissionDenied => "文档权限不足",
198 Self::DocumentLocked => "文档已锁定",
199 Self::SheetNotFound => "工作表不存在",
200 Self::TableNotFound => "表格不存在",
201 Self::AppNotPublished => "应用未发布",
202 Self::AppVersionIncompatible => "应用版本不兼容",
203 Self::NetworkTimeout => "网络连接超时",
204 Self::NetworkConnectionFailed => "网络连接失败",
205 Self::DnsResolutionFailed => "DNS解析失败",
206 Self::SslCertificateError => "SSL证书错误",
207 }
208 }
209
210 pub fn detailed_description(&self) -> &'static str {
212 match self {
213 Self::Success => "请求已成功处理",
214 Self::AppNotInstalled => "应用未安装到当前企业,请先在飞书管理后台安装应用",
215 Self::AppTicketInvalid => "应用票据已失效,SDK会自动重新申请",
216 Self::AppStatusException => "应用状态异常,请检查应用是否正常启用",
217 Self::AppPermissionDenied => "应用缺少执行此操作的权限,请在开发者后台配置相应权限",
218 Self::AccessTokenInvalid => "用户访问令牌已过期,需要重新获取用户授权",
219 Self::AppAccessTokenInvalid => "应用访问令牌无效,请检查应用ID和密钥配置",
220 Self::TenantAccessTokenInvalid => "租户访问令牌无效,请检查应用是否正确安装到企业",
221 Self::BadRequest => "请求参数格式错误或缺少必需参数",
222 Self::Unauthorized => "身份认证失败,请检查访问令牌",
223 Self::Forbidden => "当前用户或应用缺少执行此操作的权限",
224 Self::NotFound => "请求的资源不存在或已被删除",
225 Self::MethodNotAllowed => "当前API不支持此HTTP方法",
226 Self::Conflict => "请求与当前资源状态冲突",
227 Self::TooManyRequests => "请求频率超过限制,请降低请求频率",
228 Self::InternalServerError => "服务器内部错误,请稍后重试",
229 Self::BadGateway => "网关错误,请检查网络连接或稍后重试",
230 Self::ServiceUnavailable => "服务暂时不可用,请稍后重试",
231 Self::GatewayTimeout => "网关超时,请稍后重试",
232 Self::UserNotFound => "指定的用户不存在,请检查用户ID是否正确",
233 Self::UserStatusException => "用户状态异常,可能已被禁用或删除",
234 Self::DepartmentNotFound => "指定的部门不存在,请检查部门ID是否正确",
235 Self::ChatNotFound => "指定的群组不存在或机器人未加入该群组",
236 Self::ChatTypeNotSupported => "当前群组类型不支持此操作",
237 Self::MessageNotFound => "指定的消息不存在或已被删除",
238 Self::MessageTypeNotSupported => "不支持的消息类型",
239 Self::FileNotFound => "指定的文件不存在或已被删除",
240 Self::FileSizeExceeded => "文件大小超出限制,请压缩后重试",
241 Self::FileTypeNotSupported => "不支持的文件类型",
242 Self::CalendarNotFound => "指定的日历不存在",
243 Self::EventNotFound => "指定的日程不存在或已被删除",
244 Self::EventConflict => "日程时间冲突,请选择其他时间",
245 Self::DocumentNotFound => "指定的文档不存在或已被删除",
246 Self::DocumentPermissionDenied => "文档权限不足,请联系文档所有者授权",
247 Self::DocumentLocked => "文档已被其他用户锁定,请稍后再试",
248 Self::SheetNotFound => "指定的工作表不存在",
249 Self::TableNotFound => "指定的表格不存在",
250 Self::AppNotPublished => "应用尚未发布到应用商店",
251 Self::AppVersionIncompatible => "应用版本不兼容,请更新到最新版本",
252 Self::NetworkTimeout => "网络连接超时,请检查网络设置",
253 Self::NetworkConnectionFailed => "网络连接失败,请检查网络设置",
254 Self::DnsResolutionFailed => "DNS解析失败,请检查域名设置",
255 Self::SslCertificateError => "SSL证书验证失败,请检查证书配置",
256 }
257 }
258
259 pub fn is_auth_error(&self) -> bool {
261 matches!(
262 self,
263 Self::AppTicketInvalid
264 | Self::AccessTokenInvalid
265 | Self::AppAccessTokenInvalid
266 | Self::TenantAccessTokenInvalid
267 | Self::Unauthorized
268 )
269 }
270
271 pub fn is_permission_error(&self) -> bool {
273 matches!(
274 self,
275 Self::Forbidden | Self::AppPermissionDenied | Self::DocumentPermissionDenied
276 )
277 }
278
279 pub fn is_client_error(&self) -> bool {
281 let code = *self as i32;
282 (400..=499).contains(&code) && code != 429
283 }
284
285 pub fn is_server_error(&self) -> bool {
287 let code = *self as i32;
288 (500..=599).contains(&code)
289 }
290
291 pub fn is_retryable(&self) -> bool {
293 matches!(
294 self,
295 Self::TooManyRequests
296 | Self::InternalServerError
297 | Self::BadGateway
298 | Self::ServiceUnavailable
299 | Self::GatewayTimeout
300 | Self::NetworkTimeout
301 | Self::NetworkConnectionFailed
302 | Self::DnsResolutionFailed
303 )
304 }
305
306 pub fn suggested_retry_delay(&self) -> Option<u64> {
308 match self {
309 Self::TooManyRequests => Some(60), Self::InternalServerError => Some(5), Self::BadGateway => Some(3), Self::ServiceUnavailable => Some(10), Self::GatewayTimeout => Some(5), Self::NetworkTimeout => Some(3), Self::NetworkConnectionFailed => Some(5), Self::DnsResolutionFailed => Some(10), _ => None,
318 }
319 }
320
321 pub fn severity(&self) -> super::error::ErrorSeverity {
323 match self {
324 Self::Success => super::error::ErrorSeverity::Info,
325 Self::TooManyRequests => super::error::ErrorSeverity::Warning,
326 Self::BadRequest | Self::Unauthorized | Self::Forbidden | Self::NotFound => {
327 super::error::ErrorSeverity::Error
328 }
329 Self::InternalServerError | Self::ServiceUnavailable | Self::GatewayTimeout => {
330 super::error::ErrorSeverity::Critical
331 }
332 _ => super::error::ErrorSeverity::Warning,
333 }
334 }
335
336 pub fn help_url(&self) -> Option<&'static str> {
338 match self {
339 Self::AppTicketInvalid
340 | Self::AccessTokenInvalid
341 | Self::AppAccessTokenInvalid
342 | Self::TenantAccessTokenInvalid => {
343 Some("https://open.feishu.cn/document/server-docs/authentication/access-token")
344 }
345 Self::Forbidden => {
346 Some("https://open.feishu.cn/document/home/introduction-to-scope-and-authorization")
347 }
348 Self::TooManyRequests => {
349 Some("https://open.feishu.cn/document/server-docs/api-call-guide/rate-limiting")
350 }
351 _ => Some("https://open.feishu.cn/document/"),
352 }
353 }
354}
355
356impl Display for LarkErrorCode {
357 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
358 write!(f, "{} ({})", self.description(), *self as i32)
359 }
360}
361
362#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
364pub enum ErrorCategory {
365 Authentication,
367 Permission,
369 Parameter,
371 Resource,
373 Server,
375 Network,
377 RateLimit,
379 Other,
381}
382
383impl LarkErrorCode {
384 pub fn category(&self) -> ErrorCategory {
386 match self {
387 Self::AppTicketInvalid
388 | Self::AccessTokenInvalid
389 | Self::AppAccessTokenInvalid
390 | Self::TenantAccessTokenInvalid
391 | Self::Unauthorized => ErrorCategory::Authentication,
392
393 Self::Forbidden | Self::AppPermissionDenied | Self::DocumentPermissionDenied => {
394 ErrorCategory::Permission
395 }
396
397 Self::BadRequest
398 | Self::MethodNotAllowed
399 | Self::FileSizeExceeded
400 | Self::FileTypeNotSupported
401 | Self::MessageTypeNotSupported
402 | Self::ChatTypeNotSupported
403 | Self::EventConflict
404 | Self::AppVersionIncompatible => ErrorCategory::Parameter,
405
406 Self::NotFound
407 | Self::UserNotFound
408 | Self::DepartmentNotFound
409 | Self::ChatNotFound
410 | Self::MessageNotFound
411 | Self::FileNotFound
412 | Self::CalendarNotFound
413 | Self::EventNotFound
414 | Self::DocumentNotFound
415 | Self::SheetNotFound
416 | Self::TableNotFound
417 | Self::AppNotInstalled
418 | Self::AppNotPublished
419 | Self::Conflict => ErrorCategory::Resource,
420
421 Self::TooManyRequests => ErrorCategory::RateLimit,
422
423 Self::InternalServerError
424 | Self::BadGateway
425 | Self::ServiceUnavailable
426 | Self::AppStatusException
427 | Self::UserStatusException
428 | Self::DocumentLocked => ErrorCategory::Server,
429
430 Self::GatewayTimeout
431 | Self::NetworkTimeout
432 | Self::NetworkConnectionFailed
433 | Self::DnsResolutionFailed
434 | Self::SslCertificateError => ErrorCategory::Network,
435
436 Self::Success => ErrorCategory::Other,
437 }
438 }
439}
440
441#[cfg(test)]
442mod tests {
443 use super::*;
444
445 #[test]
446 fn test_error_code_from_code() {
447 assert_eq!(LarkErrorCode::from_code(0), Some(LarkErrorCode::Success));
448 assert_eq!(
449 LarkErrorCode::from_code(10012),
450 Some(LarkErrorCode::AppTicketInvalid)
451 );
452 assert_eq!(LarkErrorCode::from_code(404), Some(LarkErrorCode::NotFound));
453 assert_eq!(LarkErrorCode::from_code(999999), None);
454 }
455
456 #[test]
457 fn test_error_classification() {
458 assert!(LarkErrorCode::AccessTokenInvalid.is_auth_error());
459 assert!(LarkErrorCode::Forbidden.is_permission_error());
460 assert!(LarkErrorCode::BadRequest.is_client_error());
461 assert!(LarkErrorCode::InternalServerError.is_server_error());
462 assert!(LarkErrorCode::TooManyRequests.is_retryable());
463 }
464
465 #[test]
466 fn test_retry_delay() {
467 assert_eq!(
468 LarkErrorCode::TooManyRequests.suggested_retry_delay(),
469 Some(60)
470 );
471 assert_eq!(LarkErrorCode::Success.suggested_retry_delay(), None);
472 }
473
474 #[test]
475 fn test_error_category() {
476 assert_eq!(
478 LarkErrorCode::AccessTokenInvalid.category(),
479 ErrorCategory::Authentication
480 );
481 assert_eq!(
482 LarkErrorCode::AppTicketInvalid.category(),
483 ErrorCategory::Authentication
484 );
485 assert_eq!(
486 LarkErrorCode::Unauthorized.category(),
487 ErrorCategory::Authentication
488 );
489
490 assert_eq!(
492 LarkErrorCode::Forbidden.category(),
493 ErrorCategory::Permission
494 );
495 assert_eq!(
496 LarkErrorCode::AppPermissionDenied.category(),
497 ErrorCategory::Permission
498 );
499 assert_eq!(
500 LarkErrorCode::DocumentPermissionDenied.category(),
501 ErrorCategory::Permission
502 );
503
504 assert_eq!(
506 LarkErrorCode::BadRequest.category(),
507 ErrorCategory::Parameter
508 );
509 assert_eq!(
510 LarkErrorCode::FileSizeExceeded.category(),
511 ErrorCategory::Parameter
512 );
513 assert_eq!(
514 LarkErrorCode::EventConflict.category(),
515 ErrorCategory::Parameter
516 );
517
518 assert_eq!(LarkErrorCode::NotFound.category(), ErrorCategory::Resource);
520 assert_eq!(
521 LarkErrorCode::UserNotFound.category(),
522 ErrorCategory::Resource
523 );
524 assert_eq!(
525 LarkErrorCode::DocumentNotFound.category(),
526 ErrorCategory::Resource
527 );
528
529 assert_eq!(
531 LarkErrorCode::TooManyRequests.category(),
532 ErrorCategory::RateLimit
533 );
534
535 assert_eq!(
537 LarkErrorCode::InternalServerError.category(),
538 ErrorCategory::Server
539 );
540 assert_eq!(
541 LarkErrorCode::DocumentLocked.category(),
542 ErrorCategory::Server
543 );
544
545 assert_eq!(
547 LarkErrorCode::NetworkTimeout.category(),
548 ErrorCategory::Network
549 );
550 assert_eq!(
551 LarkErrorCode::DnsResolutionFailed.category(),
552 ErrorCategory::Network
553 );
554 }
555
556 #[test]
557 fn test_business_error_codes() {
558 assert_eq!(
560 LarkErrorCode::from_code(60001),
561 Some(LarkErrorCode::UserNotFound)
562 );
563 assert_eq!(
564 LarkErrorCode::from_code(70001),
565 Some(LarkErrorCode::ChatNotFound)
566 );
567 assert_eq!(
568 LarkErrorCode::from_code(120001),
569 Some(LarkErrorCode::DocumentNotFound)
570 );
571 assert_eq!(
572 LarkErrorCode::from_code(110001),
573 Some(LarkErrorCode::CalendarNotFound)
574 );
575
576 assert_eq!(
578 LarkErrorCode::from_code(999001),
579 Some(LarkErrorCode::NetworkTimeout)
580 );
581 assert_eq!(
582 LarkErrorCode::from_code(999004),
583 Some(LarkErrorCode::SslCertificateError)
584 );
585 }
586
587 #[test]
588 fn test_error_descriptions() {
589 assert_eq!(LarkErrorCode::Success.description(), "操作成功");
591 assert_eq!(LarkErrorCode::UserNotFound.description(), "用户不存在");
592 assert_eq!(LarkErrorCode::ChatNotFound.description(), "群组不存在");
593 assert_eq!(LarkErrorCode::DocumentLocked.description(), "文档已锁定");
594 assert_eq!(LarkErrorCode::NetworkTimeout.description(), "网络连接超时");
595
596 assert!(LarkErrorCode::UserNotFound
598 .detailed_description()
599 .contains("用户ID"));
600 assert!(LarkErrorCode::TooManyRequests
601 .detailed_description()
602 .contains("请求频率"));
603 assert!(LarkErrorCode::DocumentPermissionDenied
604 .detailed_description()
605 .contains("权限不足"));
606 }
607
608 #[test]
609 fn test_help_urls() {
610 assert!(LarkErrorCode::AccessTokenInvalid.help_url().is_some());
612 assert!(LarkErrorCode::TooManyRequests.help_url().is_some());
613 assert!(LarkErrorCode::Forbidden.help_url().is_some());
614
615 let auth_url = LarkErrorCode::AppAccessTokenInvalid.help_url().unwrap();
616 assert!(auth_url.contains("authentication"));
617
618 let rate_limit_url = LarkErrorCode::TooManyRequests.help_url().unwrap();
619 assert!(rate_limit_url.contains("rate-limiting"));
620 }
621
622 #[test]
623 fn test_severity_levels() {
624 use crate::core::error::ErrorSeverity;
625
626 assert_eq!(LarkErrorCode::Success.severity(), ErrorSeverity::Info);
628 assert_eq!(
629 LarkErrorCode::TooManyRequests.severity(),
630 ErrorSeverity::Warning
631 );
632 assert_eq!(LarkErrorCode::BadRequest.severity(), ErrorSeverity::Error);
633 assert_eq!(
634 LarkErrorCode::InternalServerError.severity(),
635 ErrorSeverity::Critical
636 );
637 }
638
639 #[test]
640 fn test_comprehensive_error_properties() {
641 let client_error = LarkErrorCode::BadRequest;
643 assert!(!client_error.is_auth_error());
644 assert!(!client_error.is_permission_error());
645 assert!(client_error.is_client_error());
646 assert!(!client_error.is_server_error());
647 assert!(!client_error.is_retryable());
648 assert_eq!(client_error.suggested_retry_delay(), None);
649 assert_eq!(client_error.category(), ErrorCategory::Parameter);
650
651 let business_error = LarkErrorCode::DocumentNotFound;
653 assert!(!business_error.is_auth_error());
654 assert!(!business_error.is_permission_error());
655 assert!(!business_error.is_client_error()); assert!(!business_error.is_server_error()); assert!(!business_error.is_retryable());
658 assert_eq!(business_error.suggested_retry_delay(), None);
659 assert_eq!(business_error.category(), ErrorCategory::Resource);
660
661 let network_error = LarkErrorCode::NetworkTimeout;
663 assert!(network_error.is_retryable());
664 assert_eq!(network_error.suggested_retry_delay(), Some(3));
665 assert_eq!(network_error.category(), ErrorCategory::Network);
666
667 let permission_error = LarkErrorCode::DocumentPermissionDenied;
669 assert!(!permission_error.is_auth_error());
670 assert!(permission_error.is_permission_error());
671 assert_eq!(permission_error.category(), ErrorCategory::Permission);
672
673 let auth_error = LarkErrorCode::AccessTokenInvalid;
675 assert!(auth_error.is_auth_error());
676 assert!(!auth_error.is_permission_error());
677 assert_eq!(auth_error.category(), ErrorCategory::Authentication);
678
679 let server_error = LarkErrorCode::InternalServerError;
681 assert!(!server_error.is_auth_error());
682 assert!(!server_error.is_permission_error());
683 assert!(!server_error.is_client_error());
684 assert!(server_error.is_server_error());
685 assert!(server_error.is_retryable());
686 assert_eq!(server_error.suggested_retry_delay(), Some(5));
687 assert_eq!(server_error.category(), ErrorCategory::Server);
688 }
689}