1use std::{borrow::Cow, time::Duration};
7
8use super::{
9 codes::ErrorCode,
10 context::ErrorContext,
11 traits::{ErrorSeverity, ErrorTrait, ErrorType},
12};
13use serde::Serialize;
14use thiserror::Error;
15
16#[derive(Debug, Clone)]
20pub struct RetryPolicy {
21 pub max_retries: u32,
23 pub base_delay: Duration,
25 pub backoff_factor: f64,
27 pub max_delay: Option<Duration>,
29}
30
31impl RetryPolicy {
32 pub fn no_retry() -> Self {
34 Self {
35 max_retries: 0,
36 base_delay: Duration::from_secs(1),
37 backoff_factor: 1.0,
38 max_delay: None,
39 }
40 }
41
42 pub fn fixed(max_retries: u32, delay: Duration) -> Self {
44 Self {
45 max_retries,
46 base_delay: delay,
47 backoff_factor: 1.0,
48 max_delay: Some(delay),
49 }
50 }
51
52 pub fn exponential(max_retries: u32, base_delay: Duration) -> Self {
54 Self {
55 max_retries,
56 base_delay,
57 backoff_factor: 2.0,
58 max_delay: Some(Duration::from_secs(300)), }
60 }
61
62 pub fn is_retryable(&self) -> bool {
64 self.max_retries > 0
65 }
66
67 pub fn retry_delay(&self, attempt: u32) -> Option<Duration> {
69 if attempt >= self.max_retries {
70 return None;
71 }
72
73 let delay = if self.backoff_factor == 1.0 {
74 self.base_delay
75 } else {
76 let seconds = self.base_delay.as_secs_f64() * self.backoff_factor.powi(attempt as i32);
77 Duration::from_secs_f64(seconds)
78 };
79
80 Some(self.max_delay.map_or(delay, |max| delay.min(max)))
81 }
82
83 pub fn delay(&self, attempt: u32) -> Duration {
85 self.retry_delay(attempt).unwrap_or(Duration::ZERO)
86 }
87
88 pub fn use_exponential_backoff(&self) -> bool {
90 self.backoff_factor > 1.0
91 }
92
93 pub fn max_retries(&self) -> u32 {
95 self.max_retries
96 }
97}
98
99impl Default for RetryPolicy {
100 fn default() -> Self {
101 Self::exponential(3, Duration::from_secs(1))
102 }
103}
104
105#[derive(Debug, Clone)]
107pub enum RecoveryStrategy {
108 RetryWithBackoff,
110 ValidateAndRetry,
112 Reauthenticate,
114 RequestPermission,
116 ManualIntervention,
118 RetryWithDelay,
120}
121
122type AnyError = Box<dyn std::error::Error + Send + Sync>;
123
124#[derive(Debug, Clone, Copy)]
126pub enum BuilderKind {
127 Network,
129 Authentication,
131 Api,
133 Validation,
135 Configuration,
137 Serialization,
139 Business,
141 Timeout,
143 RateLimit,
145 ServiceUnavailable,
147 Internal,
149}
150
151#[derive(Debug)]
153pub struct ErrorBuilder {
154 kind: BuilderKind,
156 message: Option<String>,
158 code: Option<ErrorCode>,
160 raw_code: Option<i32>,
162 endpoint: Option<String>,
164 field: Option<String>,
166 source: Option<AnyError>,
168 policy: Option<RetryPolicy>,
170 ctx: ErrorContext,
172 duration: Option<Duration>,
174 operation: Option<String>,
176 limit: Option<u32>,
178 window: Option<Duration>,
180 reset_after: Option<Duration>,
182 service: Option<String>,
184 retry_after: Option<Duration>,
186}
187
188impl ErrorBuilder {
189 pub fn new(kind: BuilderKind) -> Self {
191 Self {
192 kind,
193 message: None,
194 code: None,
195 raw_code: None,
196 endpoint: None,
197 field: None,
198 source: None,
199 policy: None,
200 ctx: ErrorContext::new(),
201 duration: None,
202 operation: None,
203 limit: None,
204 window: None,
205 reset_after: None,
206 service: None,
207 retry_after: None,
208 }
209 }
210
211 pub fn message(mut self, msg: impl Into<String>) -> Self {
213 self.message = Some(msg.into());
214 self
215 }
216
217 pub fn code(mut self, code: ErrorCode) -> Self {
219 self.code = Some(code);
220 self
221 }
222
223 pub fn raw_code(mut self, raw_code: i32) -> Self {
225 self.raw_code = Some(raw_code);
226 self
227 }
228
229 pub fn endpoint(mut self, endpoint: impl Into<String>) -> Self {
231 self.endpoint = Some(endpoint.into());
232 self
233 }
234
235 pub fn field(mut self, field: impl Into<String>) -> Self {
237 self.field = Some(field.into());
238 self
239 }
240
241 pub fn source<E: std::error::Error + Send + Sync + 'static>(mut self, err: E) -> Self {
243 self.source = Some(Box::new(err));
244 self
245 }
246
247 pub fn policy(mut self, policy: RetryPolicy) -> Self {
249 self.policy = Some(policy);
250 self
251 }
252
253 pub fn request_id(mut self, req: impl Into<String>) -> Self {
255 self.ctx.set_request_id(req);
256 self
257 }
258
259 pub fn operation(mut self, op: impl Into<String>) -> Self {
261 let op = op.into();
262 self.operation = Some(op.clone());
263 self.ctx.set_operation(op);
264 self
265 }
266
267 pub fn component(mut self, comp: impl Into<String>) -> Self {
269 self.ctx.set_component(comp);
270 self
271 }
272
273 pub fn user_message(mut self, msg: impl Into<String>) -> Self {
275 self.ctx.set_user_message(msg);
276 self
277 }
278
279 pub fn context(mut self, key: impl Into<String>, val: impl Into<String>) -> Self {
281 self.ctx.add_context(key, val);
282 self
283 }
284
285 pub fn duration(mut self, duration: Duration) -> Self {
287 self.duration = Some(duration);
288 self
289 }
290
291 pub fn limit(mut self, limit: u32) -> Self {
293 self.limit = Some(limit);
294 self
295 }
296
297 pub fn window(mut self, window: Duration) -> Self {
299 self.window = Some(window);
300 self
301 }
302
303 pub fn reset_after(mut self, reset: Duration) -> Self {
305 self.reset_after = Some(reset);
306 self
307 }
308
309 pub fn service(mut self, service: impl Into<String>) -> Self {
311 self.service = Some(service.into());
312 self
313 }
314
315 pub fn retry_after(mut self, retry_after: Duration) -> Self {
317 self.retry_after = Some(retry_after);
318 self
319 }
320
321 pub fn build(self) -> CoreError {
323 let msg = self.message.unwrap_or_else(|| "unknown error".to_string());
324 match self.kind {
325 BuilderKind::Network => CoreError::Network(Box::new(NetworkError {
326 message: msg,
327 source: self.source,
328 policy: self.policy.unwrap_or_default(),
329 ctx: Box::new(self.ctx),
330 })),
331 BuilderKind::Authentication => CoreError::Authentication {
332 message: msg,
333 code: self.code.unwrap_or(ErrorCode::AuthenticationFailed),
334 ctx: Box::new(self.ctx),
335 },
336 BuilderKind::Api => {
337 let raw_code = self.raw_code.unwrap_or(500);
338 CoreError::Api(Box::new(ApiError {
339 raw_code,
340 endpoint: self
341 .endpoint
342 .unwrap_or_else(|| "unknown".to_string())
343 .into(),
344 message: msg,
345 source: self.source,
346 code: self.code.unwrap_or_else(|| ErrorCode::from_code(raw_code)),
347 ctx: Box::new(self.ctx),
348 }))
349 }
350 BuilderKind::Validation => CoreError::Validation {
351 field: self.field.unwrap_or_else(|| "field".to_string()).into(),
352 message: msg,
353 code: self.code.unwrap_or(ErrorCode::ValidationError),
354 ctx: Box::new(self.ctx),
355 },
356 BuilderKind::Configuration => CoreError::Configuration {
357 message: msg,
358 code: self.code.unwrap_or(ErrorCode::ConfigurationError),
359 ctx: Box::new(self.ctx),
360 },
361 BuilderKind::Serialization => CoreError::Serialization {
362 message: msg,
363 source: self.source,
364 code: self.code.unwrap_or(ErrorCode::SerializationError),
365 ctx: Box::new(self.ctx),
366 },
367 BuilderKind::Business => CoreError::Business {
368 code: self.code.unwrap_or(ErrorCode::BusinessError),
369 message: msg,
370 ctx: Box::new(self.ctx),
371 },
372 BuilderKind::Timeout => CoreError::Timeout {
373 duration: self.duration.unwrap_or_default(),
374 operation: self.operation,
375 ctx: Box::new(self.ctx),
376 },
377 BuilderKind::RateLimit => CoreError::RateLimit {
378 limit: self.limit.unwrap_or(0),
379 window: self.window.unwrap_or(Duration::from_secs(1)),
380 reset_after: self.reset_after,
381 code: self.code.unwrap_or(ErrorCode::RateLimitExceeded),
382 ctx: Box::new(self.ctx),
383 },
384 BuilderKind::ServiceUnavailable => CoreError::ServiceUnavailable {
385 service: self.service.unwrap_or_else(|| "service".to_string()).into(),
386 retry_after: self.retry_after,
387 code: self.code.unwrap_or(ErrorCode::ServiceUnavailable),
388 ctx: Box::new(self.ctx),
389 },
390 BuilderKind::Internal => CoreError::Internal {
391 code: self.code.unwrap_or(ErrorCode::InternalError),
392 message: msg,
393 source: self.source,
394 ctx: Box::new(self.ctx),
395 },
396 }
397 }
398}
399
400#[non_exhaustive]
402#[derive(Debug, Error)]
403pub enum CoreError {
404 #[error("网络错误: {0}")]
406 Network(Box<NetworkError>),
407
408 #[error("认证失败: {message}")]
410 Authentication {
411 message: String,
413 code: ErrorCode,
415 ctx: Box<ErrorContext>,
417 },
418
419 #[error("API错误 {0}")]
421 Api(Box<ApiError>),
422
423 #[error("验证错误 {field}: {message}")]
425 Validation {
426 field: Cow<'static, str>,
428 message: String,
430 code: ErrorCode,
432 ctx: Box<ErrorContext>,
434 },
435
436 #[error("配置错误: {message}")]
438 Configuration {
439 message: String,
441 code: ErrorCode,
443 ctx: Box<ErrorContext>,
445 },
446
447 #[error("序列化错误: {message}")]
449 Serialization {
450 message: String,
452 #[source]
454 source: Option<AnyError>,
455 code: ErrorCode,
457 ctx: Box<ErrorContext>,
459 },
460
461 #[error("业务错误 {code:?}: {message}")]
463 Business {
464 code: ErrorCode,
466 message: String,
468 ctx: Box<ErrorContext>,
470 },
471
472 #[error("超时 {operation:?} after {duration:?}")]
474 Timeout {
475 duration: Duration,
477 operation: Option<String>,
479 ctx: Box<ErrorContext>,
481 },
482
483 #[error("限流: {limit} 次/{window:?}")]
485 RateLimit {
486 limit: u32,
488 window: Duration,
490 reset_after: Option<Duration>,
492 code: ErrorCode,
494 ctx: Box<ErrorContext>,
496 },
497
498 #[error("服务不可用: {service}")]
500 ServiceUnavailable {
501 service: Cow<'static, str>,
503 retry_after: Option<Duration>,
505 code: ErrorCode,
507 ctx: Box<ErrorContext>,
509 },
510
511 #[error("响应体过大: {actual} 字节超过限制 {limit} 字节")]
513 ResponseTooLarge {
514 limit: u64,
516 actual: u64,
518 ctx: Box<ErrorContext>,
520 },
521
522 #[error("内部错误 {code:?}: {message}")]
524 Internal {
525 code: ErrorCode,
527 message: String,
529 #[source]
531 source: Option<AnyError>,
532 ctx: Box<ErrorContext>,
534 },
535}
536
537#[derive(Debug)]
539pub struct NetworkError {
540 pub message: String,
542 pub source: Option<AnyError>,
544 pub policy: RetryPolicy,
546 pub ctx: Box<ErrorContext>,
548}
549
550impl std::fmt::Display for NetworkError {
551 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
552 write!(f, "{}", self.message)
553 }
554}
555
556#[derive(Debug)]
558pub struct ApiError {
559 pub raw_code: i32,
561 pub endpoint: Cow<'static, str>,
563 pub message: String,
565 pub source: Option<AnyError>,
567 pub code: ErrorCode,
569 pub ctx: Box<ErrorContext>,
571}
572
573impl std::fmt::Display for ApiError {
574 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
575 write!(f, "{} {}: {}", self.raw_code, self.endpoint, self.message)
576 }
577}
578
579impl Clone for CoreError {
580 fn clone(&self) -> Self {
581 match self {
582 Self::Network(net) => Self::Network(Box::new(NetworkError {
583 message: net.message.clone(),
584 source: None, policy: net.policy.clone(),
586 ctx: net.ctx.clone(),
587 })),
588 Self::Authentication { message, code, ctx } => Self::Authentication {
589 message: message.clone(),
590 code: *code,
591 ctx: ctx.clone(),
592 },
593 Self::Api(api) => Self::Api(Box::new(ApiError {
594 raw_code: api.raw_code,
595 endpoint: api.endpoint.clone(),
596 message: api.message.clone(),
597 source: None,
598 code: api.code,
599 ctx: api.ctx.clone(),
600 })),
601 Self::Validation {
602 field,
603 message,
604 code,
605 ctx,
606 } => Self::Validation {
607 field: field.clone(),
608 message: message.clone(),
609 code: *code,
610 ctx: ctx.clone(),
611 },
612 Self::Configuration { message, code, ctx } => Self::Configuration {
613 message: message.clone(),
614 code: *code,
615 ctx: ctx.clone(),
616 },
617 Self::Serialization {
618 message, code, ctx, ..
619 } => Self::Serialization {
620 message: message.clone(),
621 source: None,
622 code: *code,
623 ctx: ctx.clone(),
624 },
625 Self::Business { code, message, ctx } => Self::Business {
626 code: *code,
627 message: message.clone(),
628 ctx: ctx.clone(),
629 },
630 Self::Timeout {
631 duration,
632 operation,
633 ctx,
634 } => Self::Timeout {
635 duration: *duration,
636 operation: operation.clone(),
637 ctx: ctx.clone(),
638 },
639 Self::RateLimit {
640 limit,
641 window,
642 reset_after,
643 code,
644 ctx,
645 } => Self::RateLimit {
646 limit: *limit,
647 window: *window,
648 reset_after: *reset_after,
649 code: *code,
650 ctx: ctx.clone(),
651 },
652 Self::ServiceUnavailable {
653 service,
654 retry_after,
655 code,
656 ctx,
657 } => Self::ServiceUnavailable {
658 service: service.clone(),
659 retry_after: *retry_after,
660 code: *code,
661 ctx: ctx.clone(),
662 },
663 Self::ResponseTooLarge { limit, actual, ctx } => Self::ResponseTooLarge {
664 limit: *limit,
665 actual: *actual,
666 ctx: ctx.clone(),
667 },
668 Self::Internal {
669 code, message, ctx, ..
670 } => Self::Internal {
671 code: *code,
672 message: message.clone(),
673 source: None,
674 ctx: ctx.clone(),
675 },
676 }
677 }
678}
679
680impl CoreError {
681 pub fn builder(kind: BuilderKind) -> ErrorBuilder {
683 ErrorBuilder::new(kind)
684 }
685
686 pub fn network_builder() -> ErrorBuilder {
688 ErrorBuilder::new(BuilderKind::Network)
689 }
690
691 pub fn api_builder() -> ErrorBuilder {
693 ErrorBuilder::new(BuilderKind::Api)
694 }
695
696 pub fn validation_builder() -> ErrorBuilder {
698 ErrorBuilder::new(BuilderKind::Validation)
699 }
700
701 pub fn authentication_builder() -> ErrorBuilder {
703 ErrorBuilder::new(BuilderKind::Authentication)
704 }
705
706 pub fn business_builder() -> ErrorBuilder {
708 ErrorBuilder::new(BuilderKind::Business)
709 }
710
711 pub fn network_msg(message: impl Into<String>) -> Self {
713 network_error(message)
714 }
715
716 pub fn authentication(message: impl Into<String>) -> Self {
718 authentication_error(message)
719 }
720
721 pub fn api_error(
723 raw_code: i32,
724 endpoint: impl Into<String>,
725 message: impl Into<String>,
726 request_id: Option<impl Into<String>>,
727 ) -> Self {
728 api_error(raw_code, endpoint, message, request_id.map(|id| id.into()))
729 }
730
731 pub fn validation_msg(message: impl Into<String>) -> Self {
733 validation_error("general", message)
734 }
735
736 pub fn message(&self) -> String {
738 self.to_string()
739 }
740
741 pub fn kind(&self) -> ErrorType {
743 self.error_type()
744 }
745
746 pub fn is_api_error(&self) -> bool {
748 matches!(self, Self::Api(_))
749 }
750
751 pub fn validation(field: impl Into<String>, message: impl Into<String>) -> Self {
754 let mut ctx = ErrorContext::new();
755 let field = field.into();
756 ctx.add_context("field", field.clone());
757 Self::Validation {
758 field: field.into(),
759 message: message.into(),
760 code: ErrorCode::ValidationError,
761 ctx: Box::new(ctx),
762 }
763 }
764
765 pub fn api_data_error(message: impl Into<String>) -> Self {
767 Self::Api(Box::new(ApiError {
768 raw_code: 500,
769 endpoint: "data_error".into(),
770 message: format!("no data: {}", message.into()),
771 source: None,
772 code: ErrorCode::InternalServerError,
773 ctx: Box::new(ErrorContext::new()),
774 }))
775 }
776
777 pub fn code(&self) -> ErrorCode {
779 match self {
780 Self::Network(_) => ErrorCode::NetworkConnectionFailed,
781 Self::Authentication { code, .. } => *code,
782 Self::Api(api) => api.code,
783 Self::Validation { code, .. } => *code,
784 Self::Configuration { code, .. } => *code,
785 Self::Serialization { code, .. } => *code,
786 Self::Business { code, .. } => *code,
787 Self::Timeout { .. } => ErrorCode::NetworkTimeout,
788 Self::RateLimit { code, .. } => *code,
789 Self::ServiceUnavailable { code, .. } => *code,
790 Self::ResponseTooLarge { .. } => ErrorCode::ResponseTooLarge,
791 Self::Internal { code, .. } => *code,
792 }
793 }
794
795 pub fn severity(&self) -> ErrorSeverity {
797 self.code().severity()
798 }
799
800 pub fn is_retryable(&self) -> bool {
805 match self {
806 Self::Network(net) => net.policy.is_retryable(),
807 _ => self.code().is_retryable(),
808 }
809 }
810
811 pub fn retry_delay(&self, attempt: u32) -> Option<Duration> {
813 match self {
814 Self::Network(net) => net.policy.retry_delay(attempt),
815 Self::RateLimit { window, .. } => Some(*window),
816 Self::ServiceUnavailable { retry_after, .. } => *retry_after,
817 Self::Api(_) if self.code().is_retryable() => {
820 Some(Duration::from_secs(1 << attempt.min(5)))
821 }
822 _ => None,
823 }
824 }
825
826 pub fn ctx(&self) -> &ErrorContext {
828 match self {
829 Self::Network(net) => &net.ctx,
830 Self::Api(api) => &api.ctx,
831 Self::Authentication { ctx, .. }
832 | Self::Validation { ctx, .. }
833 | Self::Configuration { ctx, .. }
834 | Self::Serialization { ctx, .. }
835 | Self::Business { ctx, .. }
836 | Self::Timeout { ctx, .. }
837 | Self::RateLimit { ctx, .. }
838 | Self::ServiceUnavailable { ctx, .. }
839 | Self::ResponseTooLarge { ctx, .. }
840 | Self::Internal { ctx, .. } => ctx,
841 }
842 }
843
844 pub fn map_context(self, f: impl FnOnce(&mut ErrorContext)) -> Self {
846 match self {
847 Self::Network(mut net) => {
848 f(net.ctx.as_mut());
849 Self::Network(net)
850 }
851 Self::Authentication {
852 message,
853 code,
854 mut ctx,
855 } => {
856 f(ctx.as_mut());
857 Self::Authentication { message, code, ctx }
858 }
859 Self::Api(mut api) => {
860 f(api.ctx.as_mut());
861 Self::Api(api)
862 }
863 Self::Validation {
864 field,
865 message,
866 code,
867 mut ctx,
868 } => {
869 f(ctx.as_mut());
870 Self::Validation {
871 field,
872 message,
873 code,
874 ctx,
875 }
876 }
877 Self::Configuration {
878 message,
879 code,
880 mut ctx,
881 } => {
882 f(ctx.as_mut());
883 Self::Configuration { message, code, ctx }
884 }
885 Self::Serialization {
886 message,
887 source,
888 code,
889 mut ctx,
890 } => {
891 f(ctx.as_mut());
892 Self::Serialization {
893 message,
894 source,
895 code,
896 ctx,
897 }
898 }
899 Self::Business {
900 code,
901 message,
902 mut ctx,
903 } => {
904 f(ctx.as_mut());
905 Self::Business { code, message, ctx }
906 }
907 Self::Timeout {
908 duration,
909 operation,
910 mut ctx,
911 } => {
912 f(ctx.as_mut());
913 Self::Timeout {
914 duration,
915 operation,
916 ctx,
917 }
918 }
919 Self::RateLimit {
920 limit,
921 window,
922 reset_after,
923 code,
924 mut ctx,
925 } => {
926 f(ctx.as_mut());
927 Self::RateLimit {
928 limit,
929 window,
930 reset_after,
931 code,
932 ctx,
933 }
934 }
935 Self::ServiceUnavailable {
936 service,
937 retry_after,
938 code,
939 mut ctx,
940 } => {
941 f(ctx.as_mut());
942 Self::ServiceUnavailable {
943 service,
944 retry_after,
945 code,
946 ctx,
947 }
948 }
949 Self::ResponseTooLarge {
950 limit,
951 actual,
952 mut ctx,
953 } => {
954 f(ctx.as_mut());
955 Self::ResponseTooLarge { limit, actual, ctx }
956 }
957 Self::Internal {
958 code,
959 message,
960 source,
961 mut ctx,
962 } => {
963 f(ctx.as_mut());
964 Self::Internal {
965 code,
966 message,
967 source,
968 ctx,
969 }
970 }
971 }
972 }
973
974 pub fn with_context_kv(self, key: impl Into<String>, value: impl Into<String>) -> Self {
976 let key = key.into();
977 let value = value.into();
978 self.map_context(|ctx| {
979 ctx.add_context(key, value);
980 })
981 }
982
983 pub fn with_request_id(self, request_id: impl Into<String>) -> Self {
985 let request_id = request_id.into();
986 self.map_context(|ctx| {
987 ctx.set_request_id(request_id);
988 })
989 }
990
991 pub fn with_resource(self, resource: impl Into<String>) -> Self {
993 let resource = resource.into();
994 self.map_context(|ctx| {
995 ctx.add_context("resource", resource);
996 })
997 }
998
999 pub fn with_operation(
1001 self,
1002 operation: impl Into<String>,
1003 component: impl Into<String>,
1004 ) -> Self {
1005 let operation = operation.into();
1006 let component = component.into();
1007
1008 let mapped = self.map_context(|ctx| {
1009 ctx.set_operation(operation.clone())
1010 .set_component(component.clone())
1011 .add_context("operation", operation.clone())
1012 .add_context("component", component.clone());
1013 });
1014
1015 match mapped {
1016 Self::Timeout { duration, ctx, .. } => Self::Timeout {
1017 duration,
1018 operation: Some(operation),
1019 ctx,
1020 },
1021 other => other,
1022 }
1023 }
1024
1025 pub fn with_standard_context(
1027 self,
1028 operation: impl Into<String>,
1029 component: impl Into<String>,
1030 resource: impl Into<String>,
1031 request_id: Option<String>,
1032 ) -> Self {
1033 let mut err = self
1034 .with_operation(operation, component)
1035 .with_resource(resource);
1036
1037 if let Some(request_id) = request_id.filter(|value| !value.trim().is_empty()) {
1038 err = err.with_request_id(request_id);
1039 }
1040
1041 err
1042 }
1043
1044 pub fn record(&self) -> ErrorRecord {
1046 ErrorRecord::from(self)
1047 }
1048
1049 pub fn network<E>(source: E, ctx: ErrorContext) -> Self
1052 where
1053 E: std::error::Error + Send + Sync + 'static,
1054 {
1055 Self::Network(Box::new(NetworkError {
1056 message: "网络连接失败".to_string(),
1057 source: Some(Box::new(source)),
1058 policy: RetryPolicy::default(),
1059 ctx: Box::new(ctx),
1060 }))
1061 }
1062
1063 pub fn api(
1065 raw_code: i32,
1066 endpoint: impl Into<Cow<'static, str>>,
1067 message: impl Into<String>,
1068 ctx: ErrorContext,
1069 ) -> Self {
1070 Self::Api(Box::new(ApiError {
1071 raw_code,
1072 endpoint: endpoint.into(),
1073 message: message.into(),
1074 source: None,
1075 code: ErrorCode::from_code(raw_code),
1076 ctx: Box::new(ctx),
1077 }))
1078 }
1079
1080 pub fn response_too_large(limit: u64, actual: u64) -> Self {
1082 Self::ResponseTooLarge {
1083 limit,
1084 actual,
1085 ctx: Box::new(ErrorContext::new()),
1086 }
1087 }
1088}
1089
1090#[derive(Debug, Serialize)]
1092#[serde_with::skip_serializing_none]
1093pub struct ErrorRecord {
1094 pub code: ErrorCode,
1096 pub severity: ErrorSeverity,
1098 pub retryable: bool,
1100 pub retry_delay_ms: Option<u64>,
1102 pub message: String,
1104 pub context: std::collections::HashMap<String, String>,
1106 pub request_id: Option<String>,
1108 pub operation: Option<String>,
1110 pub component: Option<String>,
1112}
1113
1114impl From<&CoreError> for ErrorRecord {
1115 fn from(err: &CoreError) -> Self {
1116 let ctx = err.ctx();
1117 Self {
1118 code: err.code(),
1119 severity: err.severity(),
1120 retryable: err.is_retryable(),
1121 retry_delay_ms: err.retry_delay(0).map(|d| d.as_millis() as u64),
1122 message: err.to_string(),
1123 context: ctx.all_context().clone(),
1124 request_id: ctx.request_id().map(|s| s.to_string()),
1125 operation: ctx.operation().map(|s| s.to_string()),
1126 component: ctx.component().map(|s| s.to_string()),
1127 }
1128 }
1129}
1130
1131impl From<reqwest::Error> for CoreError {
1132 fn from(source: reqwest::Error) -> Self {
1133 Self::Network(Box::new(NetworkError {
1134 message: source.to_string(),
1135 source: Some(Box::new(source)),
1136 policy: RetryPolicy::default(),
1137 ctx: Box::new(ErrorContext::new()),
1138 }))
1139 }
1140}
1141
1142impl From<serde_json::Error> for CoreError {
1143 fn from(source: serde_json::Error) -> Self {
1144 Self::Serialization {
1145 message: format!("JSON序列化错误: {source}"),
1146 source: Some(Box::new(source)),
1147 code: ErrorCode::SerializationError,
1148 ctx: Box::new(ErrorContext::new()),
1149 }
1150 }
1151}
1152
1153impl ErrorTrait for CoreError {
1154 fn severity(&self) -> ErrorSeverity {
1155 self.severity()
1156 }
1157
1158 fn is_retryable(&self) -> bool {
1159 self.is_retryable()
1160 }
1161
1162 fn retry_delay(&self, attempt: u32) -> Option<Duration> {
1163 self.retry_delay(attempt)
1164 }
1165
1166 fn user_message(&self) -> Option<&str> {
1167 match self {
1168 Self::Network(_) => Some("网络连接异常,请稍后重试"),
1169 Self::Authentication { .. } => Some("认证失败,请重新登录"),
1170 Self::Api(api) => Some(api.message.as_str()),
1171 Self::Validation { message, .. } => Some(message.as_str()),
1172 Self::Configuration { message, .. } => Some(message.as_str()),
1173 Self::Serialization { message, .. } => Some(message.as_str()),
1174 Self::Business { message, .. } => Some(message.as_str()),
1175 Self::Timeout { .. } => Some("请求超时,请稍后重试"),
1176 Self::RateLimit { .. } => Some("请求过于频繁,请稍候"),
1177 Self::ServiceUnavailable { .. } => Some("服务暂不可用,请稍后重试"),
1178 Self::ResponseTooLarge { .. } => Some("响应数据过大,请减小请求范围"),
1179 Self::Internal { message, .. } => Some(message.as_str()),
1180 }
1181 }
1182
1183 fn context(&self) -> &ErrorContext {
1184 self.ctx()
1185 }
1186
1187 fn error_type(&self) -> ErrorType {
1188 match self {
1189 Self::Network(_) => ErrorType::Network,
1190 Self::Authentication { .. } => ErrorType::Authentication,
1191 Self::Api(_) => ErrorType::Api,
1192 Self::Validation { .. } => ErrorType::Validation,
1193 Self::Configuration { .. } => ErrorType::Configuration,
1194 Self::Serialization { .. } => ErrorType::Serialization,
1195 Self::Business { .. } => ErrorType::Business,
1196 Self::Timeout { .. } => ErrorType::Timeout,
1197 Self::RateLimit { .. } => ErrorType::RateLimit,
1198 Self::ServiceUnavailable { .. } => ErrorType::ServiceUnavailable,
1199 Self::ResponseTooLarge { .. } => ErrorType::ResponseTooLarge,
1200 Self::Internal { .. } => ErrorType::Internal,
1201 }
1202 }
1203
1204 fn error_code(&self) -> Option<&str> {
1205 None
1206 }
1207}
1208
1209pub fn network_error(message: impl Into<String>) -> CoreError {
1213 CoreError::Network(Box::new(NetworkError {
1214 message: message.into(),
1215 source: None,
1216 policy: RetryPolicy::default(),
1217 ctx: Box::new(ErrorContext::new()),
1218 }))
1219}
1220
1221pub fn authentication_error(message: impl Into<String>) -> CoreError {
1223 CoreError::Authentication {
1224 message: message.into(),
1225 code: ErrorCode::AuthenticationFailed,
1226 ctx: Box::new(ErrorContext::new()),
1227 }
1228}
1229
1230pub fn api_error(
1235 raw_code: i32,
1236 endpoint: impl Into<String>,
1237 message: impl Into<String>,
1238 request_id: Option<String>,
1239) -> CoreError {
1240 CoreError::Api(Box::new(ApiError {
1241 raw_code,
1242 endpoint: endpoint.into().into(),
1243 message: message.into(),
1244 source: None,
1245 code: ErrorCode::from_code(raw_code),
1246 ctx: {
1247 let mut ctx = ErrorContext::new();
1248 if let Some(req_id) = request_id {
1249 ctx.set_request_id(req_id);
1250 }
1251 Box::new(ctx)
1252 },
1253 }))
1254}
1255
1256pub fn validation_error(field: impl Into<String>, message: impl Into<String>) -> CoreError {
1258 let field = field.into();
1259 let mut ctx = ErrorContext::new();
1260 ctx.add_context("field", field.clone());
1261
1262 CoreError::Validation {
1263 field: field.into(),
1264 message: message.into(),
1265 code: ErrorCode::ValidationError,
1266 ctx: Box::new(ctx),
1267 }
1268}
1269
1270pub fn serialization_error<T: std::error::Error + Send + Sync + 'static>(
1272 message: impl Into<String>,
1273 source: Option<T>,
1274) -> CoreError {
1275 CoreError::Serialization {
1276 message: message.into(),
1277 source: source.map(|e| Box::new(e) as AnyError),
1278 code: ErrorCode::SerializationError,
1279 ctx: Box::new(ErrorContext::new()),
1280 }
1281}
1282
1283pub fn business_error(message: impl Into<String>) -> CoreError {
1285 CoreError::Business {
1286 message: message.into(),
1287 code: ErrorCode::BusinessError,
1288 ctx: Box::new(ErrorContext::new()),
1289 }
1290}
1291
1292pub fn configuration_error(message: impl Into<String>) -> CoreError {
1294 CoreError::Configuration {
1295 message: message.into(),
1296 code: ErrorCode::ConfigurationError,
1297 ctx: Box::new(ErrorContext::new()),
1298 }
1299}
1300
1301pub fn timeout_error(timeout: Duration, operation: Option<String>) -> CoreError {
1303 CoreError::Timeout {
1304 duration: timeout,
1305 operation,
1306 ctx: Box::new(ErrorContext::new()),
1307 }
1308}
1309
1310pub fn rate_limit_error(limit: u32, window: Duration, retry_after: Option<Duration>) -> CoreError {
1312 CoreError::RateLimit {
1313 limit,
1314 window,
1315 reset_after: retry_after,
1316 code: ErrorCode::TooManyRequests,
1317 ctx: Box::new(ErrorContext::new()),
1318 }
1319}
1320
1321pub fn service_unavailable_error(
1323 service: impl Into<String>,
1324 retry_after: Option<Duration>,
1325) -> CoreError {
1326 CoreError::ServiceUnavailable {
1327 service: service.into().into(),
1328 retry_after,
1329 code: ErrorCode::ServiceUnavailable,
1330 ctx: Box::new(ErrorContext::new()),
1331 }
1332}
1333
1334pub fn permission_missing_error(scopes: &[impl AsRef<str>]) -> CoreError {
1336 let mut ctx = ErrorContext::new();
1337 ctx.add_context(
1338 "required_scopes",
1339 scopes
1340 .iter()
1341 .map(|s| s.as_ref())
1342 .collect::<Vec<_>>()
1343 .join(","),
1344 );
1345
1346 CoreError::Authentication {
1347 message: "权限范围不足".to_string(),
1348 code: ErrorCode::PermissionMissing,
1349 ctx: Box::new(ctx),
1350 }
1351}
1352
1353pub fn sso_token_invalid_error(detail: impl Into<String>) -> CoreError {
1355 let mut ctx = ErrorContext::new();
1356 ctx.add_context("detail", detail.into());
1357
1358 CoreError::Authentication {
1359 message: "SSO令牌无效".to_string(),
1360 code: ErrorCode::SsoTokenInvalid,
1361 ctx: Box::new(ctx),
1362 }
1363}
1364
1365pub fn user_identity_invalid_error(desc: impl Into<String>) -> CoreError {
1367 let mut ctx = ErrorContext::new();
1368 ctx.add_context("description", desc.into());
1369
1370 CoreError::Authentication {
1371 message: "用户身份无效".to_string(),
1372 code: ErrorCode::UserIdentityInvalid,
1373 ctx: Box::new(ctx),
1374 }
1375}
1376
1377pub fn token_invalid_error(detail: impl Into<String>) -> CoreError {
1379 let mut ctx = ErrorContext::new();
1380 ctx.add_context("detail", detail.into());
1381
1382 CoreError::Authentication {
1383 message: "访问令牌无效".to_string(),
1384 code: ErrorCode::AccessTokenInvalid,
1385 ctx: Box::new(ctx),
1386 }
1387}
1388
1389pub fn token_expired_error(detail: impl Into<String>) -> CoreError {
1391 let mut ctx = ErrorContext::new();
1392 ctx.add_context("detail", detail.into());
1393
1394 CoreError::Authentication {
1395 message: "访问令牌过期".to_string(),
1396 code: ErrorCode::AccessTokenExpiredV2,
1397 ctx: Box::new(ctx),
1398 }
1399}
1400
1401pub fn network_error_with_details(
1403 message: impl Into<String>,
1404 endpoint: Option<String>,
1405 request_id: Option<String>,
1406) -> CoreError {
1407 let mut ctx = ErrorContext::new();
1408 if let Some(ep) = endpoint {
1409 ctx.add_context("endpoint", ep);
1410 }
1411 if let Some(req_id) = request_id {
1412 ctx.set_request_id(req_id);
1413 }
1414
1415 CoreError::Network(Box::new(NetworkError {
1416 message: message.into(),
1417 source: None,
1418 policy: RetryPolicy::default(),
1419 ctx: Box::new(ctx),
1420 }))
1421}
1422
1423#[cfg(test)]
1424mod tests {
1425 use super::*;
1426 use std::time::Duration;
1427
1428 #[test]
1429 fn api_error_has_code_and_severity() {
1430 let err = CoreError::api(503, "/ping", "service down", ErrorContext::new());
1431
1432 assert_eq!(err.code(), ErrorCode::ServiceUnavailable);
1433 assert!(err.is_retryable());
1434 assert_eq!(err.severity(), ErrorSeverity::Critical);
1435 assert!(err.retry_delay(1).is_some());
1436 }
1437
1438 #[test]
1439 fn record_contains_context() {
1440 let mut ctx = ErrorContext::new();
1441 ctx.add_context("endpoint", "/user");
1442 ctx.set_request_id("req-1");
1443
1444 let err = CoreError::network(std::io::Error::other("boom"), ctx);
1445
1446 let rec = err.record();
1447 assert_eq!(rec.code, ErrorCode::NetworkConnectionFailed);
1448 assert_eq!(rec.context.get("endpoint"), Some(&"/user".to_string()));
1449 assert_eq!(rec.request_id.as_deref(), Some("req-1"));
1450 }
1451
1452 #[test]
1453 fn core_error_to_record() {
1454 let err = CoreError::api(503, "/ping", "svc down", ErrorContext::new());
1455 let rec: ErrorRecord = (&err).into();
1456 assert_eq!(rec.code, ErrorCode::ServiceUnavailable);
1457 assert!(rec.retryable);
1458 assert!(rec.message.contains("API错误"));
1459 }
1460
1461 #[test]
1462 fn builder_creates_api_error_with_context() {
1463 let err = CoreError::api_builder()
1464 .raw_code(404)
1465 .endpoint("/users/1")
1466 .message("not found")
1467 .request_id("req-123")
1468 .build();
1469
1470 assert!(err.is_api_error());
1471 assert_eq!(err.context().request_id(), Some("req-123"));
1472 assert_eq!(err.code(), ErrorCode::NotFound);
1473 }
1474
1475 #[test]
1476 fn rate_limit_retry_delay() {
1477 let err = CoreError::RateLimit {
1478 limit: 10,
1479 window: Duration::from_secs(60),
1480 reset_after: Some(Duration::from_secs(30)),
1481 code: ErrorCode::RateLimitExceeded,
1482 ctx: Box::new(ErrorContext::new()),
1483 };
1484
1485 assert!(err.is_retryable());
1486 assert_eq!(err.retry_delay(0), Some(Duration::from_secs(60)));
1487 }
1488
1489 #[test]
1490 fn from_reqwest_error() {
1491 fn assert_from_reqwest<E: Into<CoreError>>() {}
1493 assert_from_reqwest::<reqwest::Error>();
1494 }
1495
1496 #[test]
1497 fn map_context_covers_all_variants() {
1498 let errors = vec![
1499 network_error("n"),
1500 authentication_error("a"),
1501 api_error(500, "/api", "api", None),
1502 validation_error("field", "invalid"),
1503 configuration_error("cfg"),
1504 serialization_error("serde", None::<serde_json::Error>),
1505 business_error("biz"),
1506 timeout_error(Duration::from_secs(1), None),
1507 rate_limit_error(100, Duration::from_secs(60), Some(Duration::from_secs(10))),
1508 service_unavailable_error("svc", Some(Duration::from_secs(30))),
1509 CoreError::response_too_large(1024 * 1024, 5 * 1024 * 1024),
1510 CoreError::Internal {
1511 code: ErrorCode::InternalError,
1512 message: "internal".to_string(),
1513 source: None,
1514 ctx: Box::new(ErrorContext::new()),
1515 },
1516 ];
1517
1518 for err in errors {
1519 let updated = err.map_context(|ctx| {
1520 ctx.add_context("k", "v");
1521 });
1522 assert_eq!(updated.context().get_context("k"), Some("v"));
1523 }
1524 }
1525
1526 #[test]
1527 fn with_context_kv_adds_context() {
1528 let err = validation_error("field", "invalid").with_context_kv("user_id", "u-1");
1529 assert_eq!(err.context().get_context("user_id"), Some("u-1"));
1530 }
1531
1532 #[test]
1533 fn with_request_id_updates_context() {
1534 let err = validation_error("field", "invalid").with_request_id("req-123");
1535 assert_eq!(err.context().request_id(), Some("req-123"));
1536 }
1537
1538 #[test]
1539 fn with_resource_adds_resource_context() {
1540 let err = validation_error("field", "invalid").with_resource("查询记录");
1541 assert_eq!(err.context().get_context("resource"), Some("查询记录"));
1542 }
1543
1544 #[test]
1545 fn with_operation_updates_timeout_field_and_context() {
1546 let err = timeout_error(Duration::from_secs(30), Some("old_op".to_string()))
1547 .with_operation("new_op", "client");
1548
1549 match err {
1550 CoreError::Timeout {
1551 operation, ref ctx, ..
1552 } => {
1553 assert_eq!(operation.as_deref(), Some("new_op"));
1554 assert_eq!(ctx.operation(), Some("new_op"));
1555 assert_eq!(ctx.component(), Some("client"));
1556 assert_eq!(ctx.get_context("operation"), Some("new_op"));
1557 assert_eq!(ctx.get_context("component"), Some("client"));
1558 }
1559 other => panic!("expected timeout error, got {:?}", other.error_type()),
1560 }
1561 }
1562
1563 #[test]
1564 fn with_standard_context_sets_all_standard_fields() {
1565 let err = validation_error("response.data", "响应数据为空").with_standard_context(
1566 "extract_response_data",
1567 "openlark-docs",
1568 "查询记录",
1569 Some("req-456".to_string()),
1570 );
1571
1572 assert_eq!(err.context().operation(), Some("extract_response_data"));
1573 assert_eq!(err.context().component(), Some("openlark-docs"));
1574 assert_eq!(err.context().get_context("resource"), Some("查询记录"));
1575 assert_eq!(err.context().request_id(), Some("req-456"));
1576 }
1577
1578 #[test]
1580 fn api_retry_uses_errorcode_variant_with_stable_delay_formula() {
1581 for (raw, expected) in [
1583 (429, ErrorCode::TooManyRequests),
1584 (500, ErrorCode::InternalServerError),
1585 (502, ErrorCode::BadGateway),
1586 (503, ErrorCode::ServiceUnavailable),
1587 (504, ErrorCode::GatewayTimeout),
1588 ] {
1589 let err = api_error(raw, "/api", "retryable", None::<String>);
1590 assert_eq!(err.code(), expected, "raw_code={raw}");
1591 assert!(
1592 err.is_retryable(),
1593 "{expected:?} (raw={raw}) must be retryable via code.is_retryable()"
1594 );
1595 assert_eq!(err.retry_delay(0), Some(Duration::from_secs(1)));
1597 assert_eq!(err.retry_delay(1), Some(Duration::from_secs(2)));
1598 assert_eq!(err.retry_delay(5), Some(Duration::from_secs(32)));
1599 assert_eq!(
1600 err.retry_delay(6),
1601 Some(Duration::from_secs(32)),
1602 "attempt.min(5) caps shift"
1603 );
1604 }
1605
1606 let feishu = api_error(99991663, "/api", "token invalid", None::<String>);
1608 assert_eq!(feishu.code(), ErrorCode::TenantAccessTokenInvalid);
1609 assert!(!feishu.is_retryable());
1610 assert!(feishu.retry_delay(0).is_none());
1611
1612 let bad = api_error(400, "/api", "bad request", None::<String>);
1614 assert!(!bad.is_retryable());
1615 assert!(bad.retry_delay(0).is_none());
1616 }
1617}