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)]
22pub struct RetryPolicy {
23 pub max_retries: u32,
25 pub base_delay: Duration,
27 pub backoff_factor: f64,
29 pub max_delay: Option<Duration>,
31}
32
33impl RetryPolicy {
34 pub fn no_retry() -> Self {
36 Self {
37 max_retries: 0,
38 base_delay: Duration::from_secs(1),
39 backoff_factor: 1.0,
40 max_delay: None,
41 }
42 }
43
44 pub fn fixed(max_retries: u32, delay: Duration) -> Self {
46 Self {
47 max_retries,
48 base_delay: delay,
49 backoff_factor: 1.0,
50 max_delay: Some(delay),
51 }
52 }
53
54 pub fn exponential(max_retries: u32, base_delay: Duration) -> Self {
56 Self {
57 max_retries,
58 base_delay,
59 backoff_factor: 2.0,
60 max_delay: Some(Duration::from_secs(300)), }
62 }
63
64 pub fn is_retryable(&self) -> bool {
66 self.max_retries > 0
67 }
68
69 pub fn retry_delay(&self, attempt: u32) -> Option<Duration> {
71 if attempt >= self.max_retries {
72 return None;
73 }
74
75 let delay = if self.backoff_factor == 1.0 {
76 self.base_delay
77 } else {
78 let seconds = self.base_delay.as_secs_f64() * self.backoff_factor.powi(attempt as i32);
79 Duration::from_secs_f64(seconds)
80 };
81
82 Some(self.max_delay.map_or(delay, |max| delay.min(max)))
83 }
84
85 pub fn delay(&self, attempt: u32) -> Duration {
87 self.retry_delay(attempt).unwrap_or(Duration::ZERO)
88 }
89
90 pub fn use_exponential_backoff(&self) -> bool {
92 self.backoff_factor > 1.0
93 }
94
95 pub fn max_retries(&self) -> u32 {
97 self.max_retries
98 }
99}
100
101impl Default for RetryPolicy {
102 fn default() -> Self {
103 Self::exponential(3, Duration::from_secs(1))
104 }
105}
106
107#[derive(Debug, Clone)]
109pub enum RecoveryStrategy {
110 RetryWithBackoff,
112 ValidateAndRetry,
114 Reauthenticate,
116 RequestPermission,
118 ManualIntervention,
120 RetryWithDelay,
122}
123
124type AnyError = Box<dyn std::error::Error + Send + Sync>;
125
126#[derive(Debug, Clone, Copy)]
128pub enum BuilderKind {
129 Network,
131 Authentication,
133 Api,
135 Validation,
137 Configuration,
139 Serialization,
141 Business,
143 Timeout,
145 RateLimit,
147 ServiceUnavailable,
149 Internal,
151}
152
153#[derive(Debug)]
155pub struct ErrorBuilder {
156 kind: BuilderKind,
158 message: Option<String>,
160 code: Option<ErrorCode>,
162 status: Option<u16>,
164 endpoint: Option<String>,
166 field: Option<String>,
168 source: Option<AnyError>,
170 policy: Option<RetryPolicy>,
172 ctx: ErrorContext,
174 duration: Option<Duration>,
176 operation: Option<String>,
178 limit: Option<u32>,
180 window: Option<Duration>,
182 reset_after: Option<Duration>,
184 service: Option<String>,
186 retry_after: Option<Duration>,
188}
189
190impl ErrorBuilder {
191 pub fn new(kind: BuilderKind) -> Self {
193 Self {
194 kind,
195 message: None,
196 code: None,
197 status: None,
198 endpoint: None,
199 field: None,
200 source: None,
201 policy: None,
202 ctx: ErrorContext::new(),
203 duration: None,
204 operation: None,
205 limit: None,
206 window: None,
207 reset_after: None,
208 service: None,
209 retry_after: None,
210 }
211 }
212
213 pub fn message(mut self, msg: impl Into<String>) -> Self {
215 self.message = Some(msg.into());
216 self
217 }
218
219 pub fn code(mut self, code: ErrorCode) -> Self {
221 self.code = Some(code);
222 self
223 }
224
225 pub fn status(mut self, status: u16) -> Self {
227 self.status = Some(status);
228 self
229 }
230
231 pub fn endpoint(mut self, endpoint: impl Into<String>) -> Self {
233 self.endpoint = Some(endpoint.into());
234 self
235 }
236
237 pub fn field(mut self, field: impl Into<String>) -> Self {
239 self.field = Some(field.into());
240 self
241 }
242
243 pub fn source<E: std::error::Error + Send + Sync + 'static>(mut self, err: E) -> Self {
245 self.source = Some(Box::new(err));
246 self
247 }
248
249 pub fn policy(mut self, policy: RetryPolicy) -> Self {
251 self.policy = Some(policy);
252 self
253 }
254
255 pub fn request_id(mut self, req: impl Into<String>) -> Self {
257 self.ctx.set_request_id(req);
258 self
259 }
260
261 pub fn operation(mut self, op: impl Into<String>) -> Self {
263 let op = op.into();
264 self.operation = Some(op.clone());
265 self.ctx.set_operation(op);
266 self
267 }
268
269 pub fn component(mut self, comp: impl Into<String>) -> Self {
271 self.ctx.set_component(comp);
272 self
273 }
274
275 pub fn user_message(mut self, msg: impl Into<String>) -> Self {
277 self.ctx.set_user_message(msg);
278 self
279 }
280
281 pub fn context(mut self, key: impl Into<String>, val: impl Into<String>) -> Self {
283 self.ctx.add_context(key, val);
284 self
285 }
286
287 pub fn duration(mut self, duration: Duration) -> Self {
289 self.duration = Some(duration);
290 self
291 }
292
293 pub fn limit(mut self, limit: u32) -> Self {
295 self.limit = Some(limit);
296 self
297 }
298
299 pub fn window(mut self, window: Duration) -> Self {
301 self.window = Some(window);
302 self
303 }
304
305 pub fn reset_after(mut self, reset: Duration) -> Self {
307 self.reset_after = Some(reset);
308 self
309 }
310
311 pub fn service(mut self, service: impl Into<String>) -> Self {
313 self.service = Some(service.into());
314 self
315 }
316
317 pub fn retry_after(mut self, retry_after: Duration) -> Self {
319 self.retry_after = Some(retry_after);
320 self
321 }
322
323 pub fn build(self) -> CoreError {
325 let msg = self.message.unwrap_or_else(|| "unknown error".to_string());
326 match self.kind {
327 BuilderKind::Network => CoreError::Network(Box::new(NetworkError {
328 message: msg,
329 source: self.source,
330 policy: self.policy.unwrap_or_default(),
331 ctx: Box::new(self.ctx),
332 })),
333 BuilderKind::Authentication => CoreError::Authentication {
334 message: msg,
335 code: self.code.unwrap_or(ErrorCode::AuthenticationFailed),
336 ctx: Box::new(self.ctx),
337 },
338 BuilderKind::Api => {
339 let status = self.status.unwrap_or(500);
340 CoreError::Api(Box::new(ApiError {
341 status,
342 endpoint: self
343 .endpoint
344 .unwrap_or_else(|| "unknown".to_string())
345 .into(),
346 message: msg,
347 source: self.source,
348 code: self
349 .code
350 .unwrap_or_else(|| ErrorCode::from_http_status(status)),
351 ctx: Box::new(self.ctx),
352 }))
353 }
354 BuilderKind::Validation => CoreError::Validation {
355 field: self.field.unwrap_or_else(|| "field".to_string()).into(),
356 message: msg,
357 code: self.code.unwrap_or(ErrorCode::ValidationError),
358 ctx: Box::new(self.ctx),
359 },
360 BuilderKind::Configuration => CoreError::Configuration {
361 message: msg,
362 code: self.code.unwrap_or(ErrorCode::ConfigurationError),
363 ctx: Box::new(self.ctx),
364 },
365 BuilderKind::Serialization => CoreError::Serialization {
366 message: msg,
367 source: self.source,
368 code: self.code.unwrap_or(ErrorCode::SerializationError),
369 ctx: Box::new(self.ctx),
370 },
371 BuilderKind::Business => CoreError::Business {
372 code: self.code.unwrap_or(ErrorCode::BusinessError),
373 message: msg,
374 ctx: Box::new(self.ctx),
375 },
376 BuilderKind::Timeout => CoreError::Timeout {
377 duration: self.duration.unwrap_or_default(),
378 operation: self.operation,
379 ctx: Box::new(self.ctx),
380 },
381 BuilderKind::RateLimit => CoreError::RateLimit {
382 limit: self.limit.unwrap_or(0),
383 window: self.window.unwrap_or(Duration::from_secs(1)),
384 reset_after: self.reset_after,
385 code: self.code.unwrap_or(ErrorCode::RateLimitExceeded),
386 ctx: Box::new(self.ctx),
387 },
388 BuilderKind::ServiceUnavailable => CoreError::ServiceUnavailable {
389 service: self.service.unwrap_or_else(|| "service".to_string()).into(),
390 retry_after: self.retry_after,
391 code: self.code.unwrap_or(ErrorCode::ServiceUnavailable),
392 ctx: Box::new(self.ctx),
393 },
394 BuilderKind::Internal => CoreError::Internal {
395 code: self.code.unwrap_or(ErrorCode::InternalError),
396 message: msg,
397 source: self.source,
398 ctx: Box::new(self.ctx),
399 },
400 }
401 }
402}
403
404#[non_exhaustive]
406#[derive(Debug, Error)]
407pub enum CoreError {
408 #[error("网络错误: {0}")]
410 Network(Box<NetworkError>),
411
412 #[error("认证失败: {message}")]
414 Authentication {
415 message: String,
417 code: ErrorCode,
419 ctx: Box<ErrorContext>,
421 },
422
423 #[error("API错误 {0}")]
425 Api(Box<ApiError>),
426
427 #[error("验证错误 {field}: {message}")]
429 Validation {
430 field: Cow<'static, str>,
432 message: String,
434 code: ErrorCode,
436 ctx: Box<ErrorContext>,
438 },
439
440 #[error("配置错误: {message}")]
442 Configuration {
443 message: String,
445 code: ErrorCode,
447 ctx: Box<ErrorContext>,
449 },
450
451 #[error("序列化错误: {message}")]
453 Serialization {
454 message: String,
456 #[source]
458 source: Option<AnyError>,
459 code: ErrorCode,
461 ctx: Box<ErrorContext>,
463 },
464
465 #[error("业务错误 {code:?}: {message}")]
467 Business {
468 code: ErrorCode,
470 message: String,
472 ctx: Box<ErrorContext>,
474 },
475
476 #[error("超时 {operation:?} after {duration:?}")]
478 Timeout {
479 duration: Duration,
481 operation: Option<String>,
483 ctx: Box<ErrorContext>,
485 },
486
487 #[error("限流: {limit} 次/{window:?}")]
489 RateLimit {
490 limit: u32,
492 window: Duration,
494 reset_after: Option<Duration>,
496 code: ErrorCode,
498 ctx: Box<ErrorContext>,
500 },
501
502 #[error("服务不可用: {service}")]
504 ServiceUnavailable {
505 service: Cow<'static, str>,
507 retry_after: Option<Duration>,
509 code: ErrorCode,
511 ctx: Box<ErrorContext>,
513 },
514
515 #[error("内部错误 {code:?}: {message}")]
517 Internal {
518 code: ErrorCode,
520 message: String,
522 #[source]
524 source: Option<AnyError>,
525 ctx: Box<ErrorContext>,
527 },
528}
529
530#[derive(Debug)]
532pub struct NetworkError {
533 pub message: String,
535 pub source: Option<AnyError>,
537 pub policy: RetryPolicy,
539 pub ctx: Box<ErrorContext>,
541}
542
543impl std::fmt::Display for NetworkError {
544 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
545 write!(f, "{}", self.message)
546 }
547}
548
549#[derive(Debug)]
551pub struct ApiError {
552 pub status: u16,
554 pub endpoint: Cow<'static, str>,
556 pub message: String,
558 pub source: Option<AnyError>,
560 pub code: ErrorCode,
562 pub ctx: Box<ErrorContext>,
564}
565
566impl std::fmt::Display for ApiError {
567 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
568 write!(f, "{} {}: {}", self.status, self.endpoint, self.message)
569 }
570}
571
572impl Clone for CoreError {
573 fn clone(&self) -> Self {
574 match self {
575 Self::Network(net) => Self::Network(Box::new(NetworkError {
576 message: net.message.clone(),
577 source: None, policy: net.policy.clone(),
579 ctx: net.ctx.clone(),
580 })),
581 Self::Authentication { message, code, ctx } => Self::Authentication {
582 message: message.clone(),
583 code: *code,
584 ctx: ctx.clone(),
585 },
586 Self::Api(api) => Self::Api(Box::new(ApiError {
587 status: api.status,
588 endpoint: api.endpoint.clone(),
589 message: api.message.clone(),
590 source: None,
591 code: api.code,
592 ctx: api.ctx.clone(),
593 })),
594 Self::Validation {
595 field,
596 message,
597 code,
598 ctx,
599 } => Self::Validation {
600 field: field.clone(),
601 message: message.clone(),
602 code: *code,
603 ctx: ctx.clone(),
604 },
605 Self::Configuration { message, code, ctx } => Self::Configuration {
606 message: message.clone(),
607 code: *code,
608 ctx: ctx.clone(),
609 },
610 Self::Serialization {
611 message, code, ctx, ..
612 } => Self::Serialization {
613 message: message.clone(),
614 source: None,
615 code: *code,
616 ctx: ctx.clone(),
617 },
618 Self::Business { code, message, ctx } => Self::Business {
619 code: *code,
620 message: message.clone(),
621 ctx: ctx.clone(),
622 },
623 Self::Timeout {
624 duration,
625 operation,
626 ctx,
627 } => Self::Timeout {
628 duration: *duration,
629 operation: operation.clone(),
630 ctx: ctx.clone(),
631 },
632 Self::RateLimit {
633 limit,
634 window,
635 reset_after,
636 code,
637 ctx,
638 } => Self::RateLimit {
639 limit: *limit,
640 window: *window,
641 reset_after: *reset_after,
642 code: *code,
643 ctx: ctx.clone(),
644 },
645 Self::ServiceUnavailable {
646 service,
647 retry_after,
648 code,
649 ctx,
650 } => Self::ServiceUnavailable {
651 service: service.clone(),
652 retry_after: *retry_after,
653 code: *code,
654 ctx: ctx.clone(),
655 },
656 Self::Internal {
657 code, message, ctx, ..
658 } => Self::Internal {
659 code: *code,
660 message: message.clone(),
661 source: None,
662 ctx: ctx.clone(),
663 },
664 }
665 }
666}
667
668impl CoreError {
669 pub fn builder(kind: BuilderKind) -> ErrorBuilder {
671 ErrorBuilder::new(kind)
672 }
673
674 pub fn network_builder() -> ErrorBuilder {
676 ErrorBuilder::new(BuilderKind::Network)
677 }
678
679 pub fn api_builder() -> ErrorBuilder {
681 ErrorBuilder::new(BuilderKind::Api)
682 }
683
684 pub fn validation_builder() -> ErrorBuilder {
686 ErrorBuilder::new(BuilderKind::Validation)
687 }
688
689 pub fn authentication_builder() -> ErrorBuilder {
691 ErrorBuilder::new(BuilderKind::Authentication)
692 }
693
694 pub fn business_builder() -> ErrorBuilder {
696 ErrorBuilder::new(BuilderKind::Business)
697 }
698
699 pub fn network_msg(message: impl Into<String>) -> Self {
701 network_error(message)
702 }
703
704 pub fn authentication(message: impl Into<String>) -> Self {
706 authentication_error(message)
707 }
708
709 pub fn api_error(
711 status: i32,
712 endpoint: impl Into<String>,
713 message: impl Into<String>,
714 request_id: Option<impl Into<String>>,
715 ) -> Self {
716 api_error(
717 status as u16,
718 endpoint,
719 message,
720 request_id.map(|id| id.into()),
721 )
722 }
723
724 pub fn validation_msg(message: impl Into<String>) -> Self {
726 validation_error("general", message)
727 }
728
729 pub fn message(&self) -> String {
731 self.to_string()
732 }
733
734 pub fn kind(&self) -> ErrorType {
736 self.error_type()
737 }
738
739 pub fn is_api_error(&self) -> bool {
741 matches!(self, Self::Api(_))
742 }
743
744 pub fn validation(field: impl Into<String>, message: impl Into<String>) -> Self {
747 let mut ctx = ErrorContext::new();
748 let field = field.into();
749 ctx.add_context("field", field.clone());
750 Self::Validation {
751 field: field.into(),
752 message: message.into(),
753 code: ErrorCode::ValidationError,
754 ctx: Box::new(ctx),
755 }
756 }
757
758 pub fn api_data_error(message: impl Into<String>) -> Self {
760 Self::Api(Box::new(ApiError {
761 status: 500,
762 endpoint: "data_error".into(),
763 message: format!("no data: {}", message.into()),
764 source: None,
765 code: ErrorCode::InternalServerError,
766 ctx: Box::new(ErrorContext::new()),
767 }))
768 }
769
770 pub fn code(&self) -> ErrorCode {
772 match self {
773 Self::Network(_) => ErrorCode::NetworkConnectionFailed,
774 Self::Authentication { code, .. } => *code,
775 Self::Api(api) => api.code,
776 Self::Validation { code, .. } => *code,
777 Self::Configuration { code, .. } => *code,
778 Self::Serialization { code, .. } => *code,
779 Self::Business { code, .. } => *code,
780 Self::Timeout { .. } => ErrorCode::NetworkTimeout,
781 Self::RateLimit { code, .. } => *code,
782 Self::ServiceUnavailable { code, .. } => *code,
783 Self::Internal { code, .. } => *code,
784 }
785 }
786
787 pub fn severity(&self) -> ErrorSeverity {
789 self.code().severity()
790 }
791
792 pub fn is_retryable(&self) -> bool {
794 match self {
795 Self::Network(net) => net.policy.is_retryable(),
796 Self::Api(api) => matches!(api.status, 429 | 500..=599),
797 Self::Timeout { .. } => self.code().is_retryable(),
798 Self::RateLimit { .. } => self.code().is_retryable(),
799 Self::ServiceUnavailable { .. } => self.code().is_retryable(),
800 Self::Internal { .. } => self.code().is_retryable(),
801 _ => false,
802 }
803 }
804
805 pub fn retry_delay(&self, attempt: u32) -> Option<Duration> {
807 match self {
808 Self::Network(net) => net.policy.retry_delay(attempt),
809 Self::RateLimit { window, .. } => Some(*window),
810 Self::ServiceUnavailable { retry_after, .. } => *retry_after,
811 Self::Api(api) if matches!(api.status, 429 | 500..=599) => {
812 Some(Duration::from_secs(1 << attempt.min(5)))
813 }
814 _ => None,
815 }
816 }
817
818 pub fn ctx(&self) -> &ErrorContext {
820 match self {
821 Self::Network(net) => &net.ctx,
822 Self::Api(api) => &api.ctx,
823 Self::Authentication { ctx, .. }
824 | Self::Validation { ctx, .. }
825 | Self::Configuration { ctx, .. }
826 | Self::Serialization { ctx, .. }
827 | Self::Business { ctx, .. }
828 | Self::Timeout { ctx, .. }
829 | Self::RateLimit { ctx, .. }
830 | Self::ServiceUnavailable { ctx, .. }
831 | Self::Internal { ctx, .. } => ctx,
832 }
833 }
834
835 pub fn map_context(self, f: impl FnOnce(&mut ErrorContext)) -> Self {
837 match self {
838 Self::Network(mut net) => {
839 f(net.ctx.as_mut());
840 Self::Network(net)
841 }
842 Self::Authentication {
843 message,
844 code,
845 mut ctx,
846 } => {
847 f(ctx.as_mut());
848 Self::Authentication { message, code, ctx }
849 }
850 Self::Api(mut api) => {
851 f(api.ctx.as_mut());
852 Self::Api(api)
853 }
854 Self::Validation {
855 field,
856 message,
857 code,
858 mut ctx,
859 } => {
860 f(ctx.as_mut());
861 Self::Validation {
862 field,
863 message,
864 code,
865 ctx,
866 }
867 }
868 Self::Configuration {
869 message,
870 code,
871 mut ctx,
872 } => {
873 f(ctx.as_mut());
874 Self::Configuration { message, code, ctx }
875 }
876 Self::Serialization {
877 message,
878 source,
879 code,
880 mut ctx,
881 } => {
882 f(ctx.as_mut());
883 Self::Serialization {
884 message,
885 source,
886 code,
887 ctx,
888 }
889 }
890 Self::Business {
891 code,
892 message,
893 mut ctx,
894 } => {
895 f(ctx.as_mut());
896 Self::Business { code, message, ctx }
897 }
898 Self::Timeout {
899 duration,
900 operation,
901 mut ctx,
902 } => {
903 f(ctx.as_mut());
904 Self::Timeout {
905 duration,
906 operation,
907 ctx,
908 }
909 }
910 Self::RateLimit {
911 limit,
912 window,
913 reset_after,
914 code,
915 mut ctx,
916 } => {
917 f(ctx.as_mut());
918 Self::RateLimit {
919 limit,
920 window,
921 reset_after,
922 code,
923 ctx,
924 }
925 }
926 Self::ServiceUnavailable {
927 service,
928 retry_after,
929 code,
930 mut ctx,
931 } => {
932 f(ctx.as_mut());
933 Self::ServiceUnavailable {
934 service,
935 retry_after,
936 code,
937 ctx,
938 }
939 }
940 Self::Internal {
941 code,
942 message,
943 source,
944 mut ctx,
945 } => {
946 f(ctx.as_mut());
947 Self::Internal {
948 code,
949 message,
950 source,
951 ctx,
952 }
953 }
954 }
955 }
956
957 pub fn with_context_kv(self, key: impl Into<String>, value: impl Into<String>) -> Self {
959 let key = key.into();
960 let value = value.into();
961 self.map_context(|ctx| {
962 ctx.add_context(key, value);
963 })
964 }
965
966 pub fn with_operation(
968 self,
969 operation: impl Into<String>,
970 component: impl Into<String>,
971 ) -> Self {
972 let operation = operation.into();
973 let component = component.into();
974
975 let mapped = self.map_context(|ctx| {
976 ctx.set_operation(operation.clone())
977 .set_component(component.clone())
978 .add_context("operation", operation.clone())
979 .add_context("component", component.clone());
980 });
981
982 match mapped {
983 Self::Timeout { duration, ctx, .. } => Self::Timeout {
984 duration,
985 operation: Some(operation),
986 ctx,
987 },
988 other => other,
989 }
990 }
991
992 pub fn record(&self) -> ErrorRecord {
994 ErrorRecord::from(self)
995 }
996
997 pub fn network<E>(source: E, ctx: ErrorContext) -> Self
1000 where
1001 E: std::error::Error + Send + Sync + 'static,
1002 {
1003 Self::Network(Box::new(NetworkError {
1004 message: "网络连接失败".to_string(),
1005 source: Some(Box::new(source)),
1006 policy: RetryPolicy::default(),
1007 ctx: Box::new(ctx),
1008 }))
1009 }
1010
1011 pub fn api(
1013 status: u16,
1014 endpoint: impl Into<Cow<'static, str>>,
1015 message: impl Into<String>,
1016 ctx: ErrorContext,
1017 ) -> Self {
1018 Self::Api(Box::new(ApiError {
1019 status,
1020 endpoint: endpoint.into(),
1021 message: message.into(),
1022 source: None,
1023 code: ErrorCode::from_http_status(status),
1024 ctx: Box::new(ctx),
1025 }))
1026 }
1027}
1028
1029#[derive(Debug, Serialize)]
1031#[serde_with::skip_serializing_none]
1032pub struct ErrorRecord {
1033 pub code: ErrorCode,
1035 pub severity: ErrorSeverity,
1037 pub retryable: bool,
1039 pub retry_delay_ms: Option<u64>,
1041 pub message: String,
1043 pub context: std::collections::HashMap<String, String>,
1045 pub request_id: Option<String>,
1047 pub operation: Option<String>,
1049 pub component: Option<String>,
1051}
1052
1053impl From<&CoreError> for ErrorRecord {
1054 fn from(err: &CoreError) -> Self {
1055 let ctx = err.ctx();
1056 Self {
1057 code: err.code(),
1058 severity: err.severity(),
1059 retryable: err.is_retryable(),
1060 retry_delay_ms: err.retry_delay(0).map(|d| d.as_millis() as u64),
1061 message: err.to_string(),
1062 context: ctx.all_context().clone(),
1063 request_id: ctx.request_id().map(|s| s.to_string()),
1064 operation: ctx.operation().map(|s| s.to_string()),
1065 component: ctx.component().map(|s| s.to_string()),
1066 }
1067 }
1068}
1069
1070impl From<reqwest::Error> for CoreError {
1071 fn from(source: reqwest::Error) -> Self {
1072 Self::Network(Box::new(NetworkError {
1073 message: source.to_string(),
1074 source: Some(Box::new(source)),
1075 policy: RetryPolicy::default(),
1076 ctx: Box::new(ErrorContext::new()),
1077 }))
1078 }
1079}
1080
1081impl From<serde_json::Error> for CoreError {
1082 fn from(source: serde_json::Error) -> Self {
1083 Self::Serialization {
1084 message: format!("JSON序列化错误: {}", source),
1085 source: Some(Box::new(source)),
1086 code: ErrorCode::SerializationError,
1087 ctx: Box::new(ErrorContext::new()),
1088 }
1089 }
1090}
1091
1092impl ErrorTrait for CoreError {
1093 fn severity(&self) -> ErrorSeverity {
1094 self.severity()
1095 }
1096
1097 fn is_retryable(&self) -> bool {
1098 self.is_retryable()
1099 }
1100
1101 fn retry_delay(&self, attempt: u32) -> Option<Duration> {
1102 self.retry_delay(attempt)
1103 }
1104
1105 fn user_message(&self) -> Option<&str> {
1106 match self {
1107 Self::Network(_) => Some("网络连接异常,请稍后重试"),
1108 Self::Authentication { .. } => Some("认证失败,请重新登录"),
1109 Self::Api(api) => Some(api.message.as_str()),
1110 Self::Validation { message, .. } => Some(message.as_str()),
1111 Self::Configuration { message, .. } => Some(message.as_str()),
1112 Self::Serialization { message, .. } => Some(message.as_str()),
1113 Self::Business { message, .. } => Some(message.as_str()),
1114 Self::Timeout { .. } => Some("请求超时,请稍后重试"),
1115 Self::RateLimit { .. } => Some("请求过于频繁,请稍候"),
1116 Self::ServiceUnavailable { .. } => Some("服务暂不可用,请稍后重试"),
1117 Self::Internal { message, .. } => Some(message.as_str()),
1118 }
1119 }
1120
1121 fn context(&self) -> &ErrorContext {
1122 self.ctx()
1123 }
1124
1125 fn error_type(&self) -> ErrorType {
1126 match self {
1127 Self::Network(_) => ErrorType::Network,
1128 Self::Authentication { .. } => ErrorType::Authentication,
1129 Self::Api(_) => ErrorType::Api,
1130 Self::Validation { .. } => ErrorType::Validation,
1131 Self::Configuration { .. } => ErrorType::Configuration,
1132 Self::Serialization { .. } => ErrorType::Serialization,
1133 Self::Business { .. } => ErrorType::Business,
1134 Self::Timeout { .. } => ErrorType::Timeout,
1135 Self::RateLimit { .. } => ErrorType::RateLimit,
1136 Self::ServiceUnavailable { .. } => ErrorType::ServiceUnavailable,
1137 Self::Internal { .. } => ErrorType::Internal,
1138 }
1139 }
1140
1141 fn error_code(&self) -> Option<&str> {
1142 None
1143 }
1144}
1145
1146pub fn network_error(message: impl Into<String>) -> CoreError {
1152 CoreError::Network(Box::new(NetworkError {
1153 message: message.into(),
1154 source: None,
1155 policy: RetryPolicy::default(),
1156 ctx: Box::new(ErrorContext::new()),
1157 }))
1158}
1159
1160pub fn authentication_error(message: impl Into<String>) -> CoreError {
1162 CoreError::Authentication {
1163 message: message.into(),
1164 code: ErrorCode::AuthenticationFailed,
1165 ctx: Box::new(ErrorContext::new()),
1166 }
1167}
1168
1169pub fn api_error(
1171 status: u16,
1172 endpoint: impl Into<String>,
1173 message: impl Into<String>,
1174 request_id: Option<String>,
1175) -> CoreError {
1176 CoreError::Api(Box::new(ApiError {
1177 status,
1178 endpoint: endpoint.into().into(),
1179 message: message.into(),
1180 source: None,
1181 code: ErrorCode::from_http_status(status),
1182 ctx: {
1183 let mut ctx = ErrorContext::new();
1184 if let Some(req_id) = request_id {
1185 ctx.set_request_id(req_id);
1186 }
1187 Box::new(ctx)
1188 },
1189 }))
1190}
1191
1192pub fn validation_error(field: impl Into<String>, message: impl Into<String>) -> CoreError {
1194 let field = field.into();
1195 let mut ctx = ErrorContext::new();
1196 ctx.add_context("field", field.clone());
1197
1198 CoreError::Validation {
1199 field: field.into(),
1200 message: message.into(),
1201 code: ErrorCode::ValidationError,
1202 ctx: Box::new(ctx),
1203 }
1204}
1205
1206pub fn serialization_error<T: std::error::Error + Send + Sync + 'static>(
1208 message: impl Into<String>,
1209 source: Option<T>,
1210) -> CoreError {
1211 CoreError::Serialization {
1212 message: message.into(),
1213 source: source.map(|e| Box::new(e) as AnyError),
1214 code: ErrorCode::SerializationError,
1215 ctx: Box::new(ErrorContext::new()),
1216 }
1217}
1218
1219pub fn business_error(message: impl Into<String>) -> CoreError {
1221 CoreError::Business {
1222 message: message.into(),
1223 code: ErrorCode::BusinessError,
1224 ctx: Box::new(ErrorContext::new()),
1225 }
1226}
1227
1228pub fn configuration_error(message: impl Into<String>) -> CoreError {
1230 CoreError::Configuration {
1231 message: message.into(),
1232 code: ErrorCode::ConfigurationError,
1233 ctx: Box::new(ErrorContext::new()),
1234 }
1235}
1236
1237pub fn timeout_error(timeout: Duration, operation: Option<String>) -> CoreError {
1239 CoreError::Timeout {
1240 duration: timeout,
1241 operation,
1242 ctx: Box::new(ErrorContext::new()),
1243 }
1244}
1245
1246pub fn rate_limit_error(limit: u32, window: Duration, retry_after: Option<Duration>) -> CoreError {
1248 CoreError::RateLimit {
1249 limit,
1250 window,
1251 reset_after: retry_after,
1252 code: ErrorCode::TooManyRequests,
1253 ctx: Box::new(ErrorContext::new()),
1254 }
1255}
1256
1257pub fn service_unavailable_error(
1259 service: impl Into<String>,
1260 retry_after: Option<Duration>,
1261) -> CoreError {
1262 CoreError::ServiceUnavailable {
1263 service: service.into().into(),
1264 retry_after,
1265 code: ErrorCode::ServiceUnavailable,
1266 ctx: Box::new(ErrorContext::new()),
1267 }
1268}
1269
1270pub fn permission_missing_error(scopes: &[impl AsRef<str>]) -> CoreError {
1272 let mut ctx = ErrorContext::new();
1273 ctx.add_context(
1274 "required_scopes",
1275 scopes
1276 .iter()
1277 .map(|s| s.as_ref())
1278 .collect::<Vec<_>>()
1279 .join(","),
1280 );
1281
1282 CoreError::Authentication {
1283 message: "权限范围不足".to_string(),
1284 code: ErrorCode::PermissionMissing,
1285 ctx: Box::new(ctx),
1286 }
1287}
1288
1289pub fn sso_token_invalid_error(detail: impl Into<String>) -> CoreError {
1291 let mut ctx = ErrorContext::new();
1292 ctx.add_context("detail", detail.into());
1293
1294 CoreError::Authentication {
1295 message: "SSO令牌无效".to_string(),
1296 code: ErrorCode::SsoTokenInvalid,
1297 ctx: Box::new(ctx),
1298 }
1299}
1300
1301pub fn user_identity_invalid_error(desc: impl Into<String>) -> CoreError {
1303 let mut ctx = ErrorContext::new();
1304 ctx.add_context("description", desc.into());
1305
1306 CoreError::Authentication {
1307 message: "用户身份无效".to_string(),
1308 code: ErrorCode::UserIdentityInvalid,
1309 ctx: Box::new(ctx),
1310 }
1311}
1312
1313pub fn token_invalid_error(detail: impl Into<String>) -> CoreError {
1315 let mut ctx = ErrorContext::new();
1316 ctx.add_context("detail", detail.into());
1317
1318 CoreError::Authentication {
1319 message: "访问令牌无效".to_string(),
1320 code: ErrorCode::AccessTokenInvalid,
1321 ctx: Box::new(ctx),
1322 }
1323}
1324
1325pub fn token_expired_error(detail: impl Into<String>) -> CoreError {
1327 let mut ctx = ErrorContext::new();
1328 ctx.add_context("detail", detail.into());
1329
1330 CoreError::Authentication {
1331 message: "访问令牌过期".to_string(),
1332 code: ErrorCode::AccessTokenExpiredV2,
1333 ctx: Box::new(ctx),
1334 }
1335}
1336
1337pub fn network_error_with_details(
1339 message: impl Into<String>,
1340 endpoint: Option<String>,
1341 request_id: Option<String>,
1342) -> CoreError {
1343 let mut ctx = ErrorContext::new();
1344 if let Some(ep) = endpoint {
1345 ctx.add_context("endpoint", ep);
1346 }
1347 if let Some(req_id) = request_id {
1348 ctx.set_request_id(req_id);
1349 }
1350
1351 CoreError::Network(Box::new(NetworkError {
1352 message: message.into(),
1353 source: None,
1354 policy: RetryPolicy::default(),
1355 ctx: Box::new(ctx),
1356 }))
1357}
1358
1359#[cfg(test)]
1360mod tests {
1361 use super::*;
1362 use std::time::Duration;
1363
1364 #[test]
1365 fn api_error_has_code_and_severity() {
1366 let err = CoreError::api(503, "/ping", "service down", ErrorContext::new());
1367
1368 assert_eq!(err.code(), ErrorCode::ServiceUnavailable);
1369 assert!(err.is_retryable());
1370 assert_eq!(err.severity(), ErrorSeverity::Critical);
1371 assert!(err.retry_delay(1).is_some());
1372 }
1373
1374 #[test]
1375 fn record_contains_context() {
1376 let mut ctx = ErrorContext::new();
1377 ctx.add_context("endpoint", "/user");
1378 ctx.set_request_id("req-1");
1379
1380 let err = CoreError::network(std::io::Error::other("boom"), ctx);
1381
1382 let rec = err.record();
1383 assert_eq!(rec.code, ErrorCode::NetworkConnectionFailed);
1384 assert_eq!(rec.context.get("endpoint"), Some(&"/user".to_string()));
1385 assert_eq!(rec.request_id.as_deref(), Some("req-1"));
1386 }
1387
1388 #[test]
1389 fn core_error_to_record() {
1390 let err = CoreError::api(503, "/ping", "svc down", ErrorContext::new());
1391 let rec: ErrorRecord = (&err).into();
1392 assert_eq!(rec.code, ErrorCode::ServiceUnavailable);
1393 assert!(rec.retryable);
1394 assert!(rec.message.contains("API错误"));
1395 }
1396
1397 #[test]
1398 fn builder_creates_api_error_with_context() {
1399 let err = CoreError::api_builder()
1400 .status(404)
1401 .endpoint("/users/1")
1402 .message("not found")
1403 .request_id("req-123")
1404 .build();
1405
1406 assert!(err.is_api_error());
1407 assert_eq!(err.context().request_id(), Some("req-123"));
1408 assert_eq!(err.code(), ErrorCode::NotFound);
1409 }
1410
1411 #[test]
1412 fn rate_limit_retry_delay() {
1413 let err = CoreError::RateLimit {
1414 limit: 10,
1415 window: Duration::from_secs(60),
1416 reset_after: Some(Duration::from_secs(30)),
1417 code: ErrorCode::RateLimitExceeded,
1418 ctx: Box::new(ErrorContext::new()),
1419 };
1420
1421 assert!(err.is_retryable());
1422 assert_eq!(err.retry_delay(0), Some(Duration::from_secs(60)));
1423 }
1424
1425 #[test]
1426 fn from_reqwest_error() {
1427 fn assert_from_reqwest<E: Into<CoreError>>() {}
1429 assert_from_reqwest::<reqwest::Error>();
1430 }
1431
1432 #[test]
1433 fn map_context_covers_all_variants() {
1434 let errors = vec![
1435 network_error("n"),
1436 authentication_error("a"),
1437 api_error(500, "/api", "api", None),
1438 validation_error("field", "invalid"),
1439 configuration_error("cfg"),
1440 serialization_error("serde", None::<serde_json::Error>),
1441 business_error("biz"),
1442 timeout_error(Duration::from_secs(1), None),
1443 rate_limit_error(100, Duration::from_secs(60), Some(Duration::from_secs(10))),
1444 service_unavailable_error("svc", Some(Duration::from_secs(30))),
1445 CoreError::Internal {
1446 code: ErrorCode::InternalError,
1447 message: "internal".to_string(),
1448 source: None,
1449 ctx: Box::new(ErrorContext::new()),
1450 },
1451 ];
1452
1453 for err in errors {
1454 let updated = err.map_context(|ctx| {
1455 ctx.add_context("k", "v");
1456 });
1457 assert_eq!(updated.context().get_context("k"), Some("v"));
1458 }
1459 }
1460
1461 #[test]
1462 fn with_context_kv_adds_context() {
1463 let err = validation_error("field", "invalid").with_context_kv("user_id", "u-1");
1464 assert_eq!(err.context().get_context("user_id"), Some("u-1"));
1465 }
1466
1467 #[test]
1468 fn with_operation_updates_timeout_field_and_context() {
1469 let err = timeout_error(Duration::from_secs(30), Some("old_op".to_string()))
1470 .with_operation("new_op", "client");
1471
1472 match err {
1473 CoreError::Timeout {
1474 operation, ref ctx, ..
1475 } => {
1476 assert_eq!(operation.as_deref(), Some("new_op"));
1477 assert_eq!(ctx.operation(), Some("new_op"));
1478 assert_eq!(ctx.component(), Some("client"));
1479 assert_eq!(ctx.get_context("operation"), Some("new_op"));
1480 assert_eq!(ctx.get_context("component"), Some("client"));
1481 }
1482 other => panic!("expected timeout error, got {:?}", other.error_type()),
1483 }
1484 }
1485}