1use serde::Serialize;
4use thiserror::Error;
5
6fn serialize_duration_as_secs<S>(
8 duration: &Option<std::time::Duration>,
9 serializer: S,
10) -> std::result::Result<S::Ok, S::Error>
11where
12 S: serde::Serializer,
13{
14 match duration {
15 Some(d) => serializer.serialize_some(&d.as_secs()),
16 None => serializer.serialize_none(),
17 }
18}
19
20#[derive(Error, Debug, Serialize)]
22pub enum CoreError {
23 #[error("Queue error: {0}")]
25 Queue(String),
26
27 #[error("Job execution error: {0}")]
29 JobExecution(String),
30
31 #[error("Serialization error: {0}")]
33 #[serde(skip)]
34 Serialization(#[from] serde_json::Error),
35
36 #[cfg(feature = "redis")]
38 #[error("Redis error: {0}")]
39 #[serde(skip)]
40 Redis(#[from] redis::RedisError),
41
42 #[error("Core error: {0}")]
44 Generic(String),
45
46 #[error("Invalid input: {0}")]
48 InvalidInput(String),
49}
50
51pub type Result<T> = std::result::Result<T, CoreError>;
53
54#[derive(Error, Debug, Serialize)]
57pub enum WorkerError {
58 #[error("Tool '{tool_name}' not found in worker registry")]
60 ToolNotFound {
61 tool_name: String,
63 },
64
65 #[error("Failed to acquire semaphore for tool '{tool_name}': {source_message}")]
67 SemaphoreAcquisition {
68 tool_name: String,
70 source_message: String,
72 },
73
74 #[error("Idempotency store operation failed: {source_message}")]
76 IdempotencyStore {
77 source_message: String,
79 },
80
81 #[error("Job serialization error: {source_message}")]
83 JobSerialization {
84 source_message: String,
86 },
87
88 #[error("Tool execution timed out after {timeout:?}")]
90 ExecutionTimeout {
91 timeout: std::time::Duration,
93 },
94
95 #[error("Internal worker error: {message}")]
97 Internal {
98 message: String,
100 },
101}
102
103impl From<&str> for CoreError {
104 fn from(err: &str) -> Self {
105 Self::Generic(err.to_string())
106 }
107}
108
109#[derive(Debug, Clone, Serialize, serde::Deserialize)]
111pub enum ToolError {
112 Retriable {
114 #[serde(skip)]
116 source: Option<std::sync::Arc<dyn std::error::Error + Send + Sync>>,
117 source_message: String,
119 context: String,
121 },
122
123 RateLimited {
125 #[serde(skip)]
127 source: Option<std::sync::Arc<dyn std::error::Error + Send + Sync>>,
128 source_message: String,
130 context: String,
132 #[serde(serialize_with = "serialize_duration_as_secs")]
134 retry_after: Option<std::time::Duration>,
135 },
136
137 Permanent {
139 #[serde(skip)]
141 source: Option<std::sync::Arc<dyn std::error::Error + Send + Sync>>,
142 source_message: String,
144 context: String,
146 },
147
148 InvalidInput {
150 #[serde(skip)]
152 source: Option<std::sync::Arc<dyn std::error::Error + Send + Sync>>,
153 source_message: String,
155 context: String,
157 },
158
159 SignerContext(String),
161}
162
163impl PartialEq for ToolError {
164 fn eq(&self, other: &Self) -> bool {
165 use ToolError::*;
166 match (self, other) {
167 (
168 Retriable {
169 source_message: s1,
170 context: c1,
171 ..
172 },
173 Retriable {
174 source_message: s2,
175 context: c2,
176 ..
177 },
178 ) => s1 == s2 && c1 == c2,
179 (
180 RateLimited {
181 source_message: s1,
182 context: c1,
183 retry_after: r1,
184 ..
185 },
186 RateLimited {
187 source_message: s2,
188 context: c2,
189 retry_after: r2,
190 ..
191 },
192 ) => s1 == s2 && c1 == c2 && r1 == r2,
193 (
194 Permanent {
195 source_message: s1,
196 context: c1,
197 ..
198 },
199 Permanent {
200 source_message: s2,
201 context: c2,
202 ..
203 },
204 ) => s1 == s2 && c1 == c2,
205 (
206 InvalidInput {
207 source_message: s1,
208 context: c1,
209 ..
210 },
211 InvalidInput {
212 source_message: s2,
213 context: c2,
214 ..
215 },
216 ) => s1 == s2 && c1 == c2,
217 (SignerContext(s1), SignerContext(s2)) => s1 == s2,
218 _ => false,
219 }
220 }
221}
222
223impl ToolError {
224 pub fn retriable_with_source<E: std::error::Error + Send + Sync + 'static>(
226 source: E,
227 context: impl Into<String>,
228 ) -> Self {
229 let source_message = source.to_string();
230 Self::Retriable {
231 source: Some(std::sync::Arc::new(source)),
232 source_message,
233 context: context.into(),
234 }
235 }
236
237 pub fn permanent_with_source<E: std::error::Error + Send + Sync + 'static>(
239 source: E,
240 context: impl Into<String>,
241 ) -> Self {
242 let source_message = source.to_string();
243 Self::Permanent {
244 source: Some(std::sync::Arc::new(source)),
245 source_message,
246 context: context.into(),
247 }
248 }
249
250 pub fn rate_limited_with_source<E: std::error::Error + Send + Sync + 'static>(
252 source: E,
253 context: impl Into<String>,
254 retry_after: Option<std::time::Duration>,
255 ) -> Self {
256 let source_message = source.to_string();
257 Self::RateLimited {
258 source: Some(std::sync::Arc::new(source)),
259 source_message,
260 context: context.into(),
261 retry_after,
262 }
263 }
264
265 pub fn invalid_input_with_source<E: std::error::Error + Send + Sync + 'static>(
267 source: E,
268 context: impl Into<String>,
269 ) -> Self {
270 let source_message = source.to_string();
271 Self::InvalidInput {
272 source: Some(std::sync::Arc::new(source)),
273 source_message,
274 context: context.into(),
275 }
276 }
277
278 #[must_use]
280 pub const fn is_retriable(&self) -> bool {
281 matches!(self, Self::Retriable { .. } | Self::RateLimited { .. })
282 }
283
284 #[must_use]
286 pub const fn retry_after(&self) -> Option<std::time::Duration> {
287 match self {
288 Self::RateLimited { retry_after, .. } => *retry_after,
289 _ => None,
290 }
291 }
292
293 #[must_use]
295 pub const fn is_rate_limited(&self) -> bool {
296 matches!(self, Self::RateLimited { .. })
297 }
298
299 pub fn retriable_string<S: Into<String>>(msg: S) -> Self {
301 let msg = msg.into();
302 Self::Retriable {
303 source: None,
304 source_message: msg.clone(),
305 context: msg,
306 }
307 }
308
309 pub fn permanent_string<S: Into<String>>(msg: S) -> Self {
311 let msg = msg.into();
312 Self::Permanent {
313 source: None,
314 source_message: msg.clone(),
315 context: msg,
316 }
317 }
318
319 pub fn rate_limited_string<S: Into<String>>(msg: S) -> Self {
321 let msg = msg.into();
322 Self::RateLimited {
323 source: None,
324 source_message: msg.clone(),
325 context: msg,
326 retry_after: None,
327 }
328 }
329
330 pub fn invalid_input_string<S: Into<String>>(msg: S) -> Self {
332 let msg = msg.into();
333 Self::InvalidInput {
334 source: None,
335 source_message: msg.clone(),
336 context: msg,
337 }
338 }
339
340 pub fn contains(&self, needle: &str) -> bool {
342 self.to_string().contains(needle)
343 }
344
345 pub fn retriable_from_error<E: std::error::Error + Send + Sync + 'static>(
347 source: E,
348 context: impl Into<String>,
349 ) -> Self {
350 Self::retriable_with_source(source, context)
351 }
352
353 pub fn permanent_from_error<E: std::error::Error + Send + Sync + 'static>(
355 source: E,
356 context: impl Into<String>,
357 ) -> Self {
358 Self::permanent_with_source(source, context)
359 }
360}
361
362impl std::fmt::Display for ToolError {
363 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
364 match self {
365 Self::Retriable {
366 context,
367 source_message,
368 ..
369 } => {
370 write!(
371 f,
372 "Operation can be retried: {} - {}",
373 context, source_message
374 )
375 }
376 Self::RateLimited {
377 context,
378 source_message,
379 ..
380 } => {
381 write!(f, "Rate limited: {} - {}", context, source_message)
382 }
383 Self::Permanent {
384 context,
385 source_message,
386 ..
387 } => {
388 write!(f, "Permanent error: {} - {}", context, source_message)
389 }
390 Self::InvalidInput {
391 context,
392 source_message,
393 ..
394 } => {
395 write!(f, "Invalid input: {} - {}", context, source_message)
396 }
397 Self::SignerContext(msg) => {
398 write!(f, "Signer context error: {}", msg)
399 }
400 }
401 }
402}
403
404impl std::error::Error for ToolError {
405 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
406 match self {
407 Self::Retriable { source, .. }
408 | Self::RateLimited { source, .. }
409 | Self::Permanent { source, .. }
410 | Self::InvalidInput { source, .. } => source
411 .as_ref()
412 .map(|e| e.as_ref() as &(dyn std::error::Error + 'static)),
413 Self::SignerContext(_) => None,
414 }
415 }
416}
417
418#[derive(Error, Debug)]
420#[error("{0}")]
421#[allow(dead_code)]
422struct StringError(String);
423
424impl From<crate::signer::SignerError> for ToolError {
426 fn from(err: crate::signer::SignerError) -> Self {
427 Self::SignerContext(err.to_string())
428 }
429}
430
431impl From<serde_json::Error> for ToolError {
471 fn from(err: serde_json::Error) -> Self {
472 Self::permanent_with_source(err, "JSON serialization/deserialization failed")
473 }
474}
475
476#[cfg(test)]
479mod tests {
480 use super::*;
481 use std::error::Error;
482
483 #[derive(Error, Debug)]
484 #[error("Test error")]
485 struct TestError;
486
487 #[test]
488 fn test_error_source_preservation() {
489 let original_error = TestError;
490 let tool_error = ToolError::retriable_with_source(original_error, "Test context");
491
492 assert!(tool_error.source().is_some());
494
495 let source = tool_error.source().unwrap();
497 assert!(source.downcast_ref::<TestError>().is_some());
498 }
499
500 #[test]
501 fn test_error_classification() {
502 let retriable = ToolError::retriable_with_source(TestError, "Retriable test");
503 assert!(retriable.is_retriable());
504
505 let permanent = ToolError::permanent_with_source(TestError, "Permanent test");
506 assert!(!permanent.is_retriable());
507
508 let rate_limited = ToolError::rate_limited_with_source(
509 TestError,
510 "Rate limited test",
511 Some(std::time::Duration::from_secs(60)),
512 );
513 assert!(rate_limited.is_retriable());
514 assert_eq!(
515 rate_limited.retry_after(),
516 Some(std::time::Duration::from_secs(60))
517 );
518 }
519
520 #[test]
521 fn test_string_error_creation() {
522 let retriable = ToolError::retriable_string("Network timeout");
523 assert!(retriable.is_retriable());
524 assert!(!retriable.is_rate_limited());
525
526 let rate_limited = ToolError::rate_limited_string("API rate limit exceeded");
527 assert!(rate_limited.is_retriable());
528 assert!(rate_limited.is_rate_limited());
529
530 let permanent = ToolError::permanent_string("Invalid parameters");
531 assert!(!permanent.is_retriable());
532 assert!(!permanent.is_rate_limited());
533 }
534
535 #[test]
537 fn test_core_error_queue_variant() {
538 let error = CoreError::Queue("Failed to connect".to_string());
539 assert_eq!(error.to_string(), "Queue error: Failed to connect");
540 }
541
542 #[test]
543 fn test_core_error_job_execution_variant() {
544 let error = CoreError::JobExecution("Job failed to run".to_string());
545 assert_eq!(error.to_string(), "Job execution error: Job failed to run");
546 }
547
548 #[test]
549 fn test_core_error_serialization_variant() {
550 let json_error = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
552 let error = CoreError::Serialization(json_error);
553 assert!(error.to_string().contains("Serialization error:"));
554 }
555
556 #[test]
557 fn test_core_error_generic_variant() {
558 let error = CoreError::Generic("Something went wrong".to_string());
559 assert_eq!(error.to_string(), "Core error: Something went wrong");
560 }
561
562 #[test]
563 fn test_core_error_from_str() {
564 let error = CoreError::from("Test error message");
565 assert_eq!(error.to_string(), "Core error: Test error message");
566 }
567
568 #[test]
569 fn test_core_error_from_empty_str() {
570 let error = CoreError::from("");
571 assert_eq!(error.to_string(), "Core error: ");
572 }
573
574 #[test]
576 fn test_worker_error_tool_not_found() {
577 let error = WorkerError::ToolNotFound {
578 tool_name: "missing_tool".to_string(),
579 };
580 assert_eq!(
581 error.to_string(),
582 "Tool 'missing_tool' not found in worker registry"
583 );
584 }
585
586 #[test]
587 fn test_worker_error_semaphore_acquisition() {
588 let error = WorkerError::SemaphoreAcquisition {
589 tool_name: "test_tool".to_string(),
590 source_message: "Test error".to_string(),
591 };
592 assert_eq!(
593 error.to_string(),
594 "Failed to acquire semaphore for tool 'test_tool': Test error"
595 );
596 }
597
598 #[test]
599 fn test_worker_error_idempotency_store() {
600 let error = WorkerError::IdempotencyStore {
601 source_message: "Test error".to_string(),
602 };
603 assert_eq!(
604 error.to_string(),
605 "Idempotency store operation failed: Test error"
606 );
607 }
608
609 #[test]
610 fn test_worker_error_job_serialization() {
611 let error = WorkerError::JobSerialization {
612 source_message: "invalid JSON format".to_string(),
613 };
614 assert!(error.to_string().contains("Job serialization error:"));
615 }
616
617 #[test]
618 fn test_worker_error_execution_timeout() {
619 let timeout = std::time::Duration::from_secs(30);
620 let error = WorkerError::ExecutionTimeout { timeout };
621 assert!(error
622 .to_string()
623 .contains("Tool execution timed out after 30"));
624 }
625
626 #[test]
627 fn test_worker_error_internal() {
628 let error = WorkerError::Internal {
629 message: "Internal system failure".to_string(),
630 };
631 assert_eq!(
632 error.to_string(),
633 "Internal worker error: Internal system failure"
634 );
635 }
636
637 #[test]
639 fn test_tool_error_invalid_input_with_source() {
640 let error = ToolError::invalid_input_with_source(TestError, "Bad input data");
641 assert!(!error.is_retriable());
642 assert!(!error.is_rate_limited());
643 assert_eq!(error.retry_after(), None);
644 assert_eq!(
645 error.to_string(),
646 "Invalid input: Bad input data - Test error"
647 );
648 }
649
650 #[test]
651 fn test_tool_error_invalid_input_string() {
652 let error = ToolError::invalid_input_string("Invalid JSON format");
653 assert!(!error.is_retriable());
654 assert!(!error.is_rate_limited());
655 assert_eq!(error.retry_after(), None);
656 assert_eq!(
657 error.to_string(),
658 "Invalid input: Invalid JSON format - Invalid JSON format"
659 );
660 }
661
662 #[test]
663 fn test_tool_error_rate_limited_with_no_retry_after() {
664 let error = ToolError::rate_limited_with_source(TestError, "Rate limit hit", None);
665 assert!(error.is_retriable());
666 assert!(error.is_rate_limited());
667 assert_eq!(error.retry_after(), None);
668 }
669
670 #[test]
671 fn test_tool_error_permanent_variant() {
672 let error = ToolError::Permanent {
673 source: None,
674 source_message: "Test error".to_string(),
675 context: "Authentication failed".to_string(),
676 };
677 assert!(!error.is_retriable());
678 assert!(!error.is_rate_limited());
679 assert_eq!(error.retry_after(), None);
680 }
681
682 #[test]
683 fn test_tool_error_retriable_variant() {
684 let error = ToolError::Retriable {
685 source: None,
686 source_message: "Test error".to_string(),
687 context: "Network issue".to_string(),
688 };
689 assert!(error.is_retriable());
690 assert!(!error.is_rate_limited());
691 assert_eq!(error.retry_after(), None);
692 }
693
694 #[test]
695 fn test_tool_error_invalid_input_variant() {
696 let error = ToolError::InvalidInput {
697 source: None,
698 source_message: "Test error".to_string(),
699 context: "Missing required field".to_string(),
700 };
701 assert!(!error.is_retriable());
702 assert!(!error.is_rate_limited());
703 assert_eq!(error.retry_after(), None);
704 }
705
706 #[test]
708 fn test_tool_error_explicit_anyhow_error() {
709 let anyhow_error = anyhow::anyhow!("Something went wrong");
710 let tool_error =
711 ToolError::permanent_string(format!("An unknown error occurred: {}", anyhow_error));
712 assert!(!tool_error.is_retriable());
713 assert!(!tool_error.is_rate_limited());
714 assert!(tool_error.to_string().contains("An unknown error occurred"));
715 }
716
717 #[test]
718 fn test_tool_error_explicit_boxed_error() {
719 let test_error = TestError;
720 let tool_error =
721 ToolError::permanent_with_source(test_error, "A required resource was not found");
722 assert!(!tool_error.is_retriable());
723 assert!(!tool_error.is_rate_limited());
724 assert_eq!(tool_error.retry_after(), None);
725 assert!(
726 matches!(tool_error, ToolError::Permanent { ref context, .. } if context == "A required resource was not found")
727 );
728 }
729
730 #[test]
731 fn test_tool_error_explicit_string() {
732 let error_msg = "Database connection failed".to_string();
733 let tool_error = ToolError::retriable_string(error_msg);
734 assert!(tool_error.is_retriable());
735 assert!(!tool_error.is_rate_limited());
736 assert!(tool_error
737 .to_string()
738 .contains("Database connection failed"));
739 }
740
741 #[test]
742 fn test_tool_error_explicit_str_ref() {
743 let error_msg = "Authentication token expired";
744 let tool_error = ToolError::permanent_string(error_msg);
745 assert!(!tool_error.is_retriable());
746 assert!(!tool_error.is_rate_limited());
747 assert!(tool_error.to_string().contains(error_msg));
748 }
749
750 #[test]
751 fn test_tool_error_explicit_empty_string() {
752 let tool_error = ToolError::permanent_string("");
753 assert!(!tool_error.is_retriable());
754 assert!(!tool_error.is_rate_limited());
755 }
756
757 #[test]
758 fn test_tool_error_explicit_empty_str() {
759 let tool_error = ToolError::permanent_string("");
760 assert!(!tool_error.is_retriable());
761 assert!(!tool_error.is_rate_limited());
762 }
763
764 #[test]
766 fn test_string_error_creation_via_tool_error() {
767 let error = ToolError::permanent_string("Test message");
768 assert!(error.source().is_none());
770 assert_eq!(
771 error.to_string(),
772 "Permanent error: Test message - Test message"
773 );
774 }
775
776 #[test]
778 fn test_tool_error_constructors_with_various_string_types() {
779 let error1 = ToolError::retriable_with_source(TestError, String::from("String type"));
781 assert!(
782 matches!(error1, ToolError::Retriable { ref context, .. } if context == "String type")
783 );
784
785 let error2 = ToolError::permanent_with_source(TestError, "str type");
787 assert!(
788 matches!(error2, ToolError::Permanent { ref context, .. } if context == "str type")
789 );
790
791 let owned_string = "owned string".to_owned();
793 let error3 = ToolError::rate_limited_with_source(TestError, owned_string, None);
794 assert!(
795 matches!(error3, ToolError::RateLimited { ref context, .. } if context == "owned string")
796 );
797 }
798
799 #[test]
801 fn test_result_type_alias_ok() {
802 let result: Result<i32> = Ok(42);
803 assert!(result.is_ok());
804 assert_eq!(result.unwrap(), 42);
805 }
806
807 #[test]
808 fn test_result_type_alias_err() {
809 let result: Result<i32> = Err(CoreError::Generic("Test error".to_string()));
810 assert!(result.is_err());
811 let error = result.unwrap_err();
812 assert_eq!(error.to_string(), "Core error: Test error");
813 }
814
815 #[test]
817 fn test_const_functions_compilation() {
818 const fn test_is_retriable() -> bool {
820 true
822 }
823
824 const fn test_retry_after() -> bool {
825 true
827 }
828
829 const fn test_is_rate_limited() -> bool {
830 true
832 }
833
834 assert!(test_is_retriable());
835 assert!(test_retry_after());
836 assert!(test_is_rate_limited());
837 }
838
839 #[test]
841 fn test_debug_implementations() {
842 let core_error = CoreError::Queue("debug test".to_string());
843 let debug_str = format!("{:?}", core_error);
844 assert!(debug_str.contains("Queue"));
845 assert!(debug_str.contains("debug test"));
846
847 let worker_error = WorkerError::ToolNotFound {
848 tool_name: "debug_tool".to_string(),
849 };
850 let debug_str = format!("{:?}", worker_error);
851 assert!(debug_str.contains("ToolNotFound"));
852 assert!(debug_str.contains("debug_tool"));
853
854 let tool_error = ToolError::retriable_string("debug test");
855 let debug_str = format!("{:?}", tool_error);
856 assert!(debug_str.contains("Retriable"));
857 }
858
859 #[test]
861 fn test_errors_with_long_strings() {
862 let long_string = "a".repeat(1000);
863 let error = CoreError::Generic(long_string.clone());
864 assert!(error.to_string().contains(&long_string));
865
866 let tool_error = ToolError::permanent_string(long_string.clone());
867 assert!(tool_error.to_string().contains(&long_string));
868 }
869
870 #[test]
872 fn test_error_source_chain() {
873 let original = TestError;
874 let tool_error = ToolError::retriable_with_source(original, "Context");
875
876 let source = tool_error.source();
878 assert!(source.is_some());
879
880 let concrete_source = source.unwrap().downcast_ref::<TestError>();
882 assert!(concrete_source.is_some());
883 }
884
885 #[test]
887 fn test_tool_error_signer_context_variant() {
888 use crate::signer::SignerError;
889
890 let signer_error = SignerError::NoSignerContext;
891 let tool_error = ToolError::SignerContext(signer_error.to_string());
892
893 assert!(!tool_error.is_retriable());
894 assert!(!tool_error.is_rate_limited());
895 assert_eq!(tool_error.retry_after(), None);
896 assert!(tool_error.to_string().contains("Signer context error"));
897 }
898
899 #[test]
900 fn test_tool_error_from_signer_error() {
901 use crate::signer::SignerError;
902
903 let signer_error = SignerError::Configuration("Invalid config".to_string());
904 let tool_error = ToolError::from(signer_error);
905
906 assert!(
907 matches!(tool_error, ToolError::SignerContext(ref inner) if inner.to_string().contains("Invalid configuration: Invalid config"))
908 );
909 }
910
911 #[test]
912 fn test_tool_error_from_serde_json_error() {
913 let json_error = serde_json::from_str::<serde_json::Value>("{ invalid }").unwrap_err();
915 let tool_error = ToolError::from(json_error);
916
917 assert!(!tool_error.is_retriable());
918 assert!(!tool_error.is_rate_limited());
919 assert!(tool_error
920 .to_string()
921 .contains("JSON serialization/deserialization failed"));
922 assert!(
923 matches!(tool_error, ToolError::Permanent { ref context, .. } if context == "JSON serialization/deserialization failed")
924 );
925 }
926
927 #[test]
929 fn test_core_error_from_serde_json_error() {
930 let json_error = serde_json::from_str::<serde_json::Value>("{ invalid }").unwrap_err();
932 let core_error = CoreError::from(json_error);
933
934 assert!(
935 matches!(core_error, CoreError::Serialization(_))
936 && core_error.to_string().contains("Serialization error:")
937 );
938 }
939
940 #[cfg(feature = "redis")]
942 #[test]
943 fn test_core_error_redis_variant() {
944 use redis::RedisError;
945
946 let redis_error = RedisError::from((redis::ErrorKind::TypeError, "Type error"));
948 let core_error = CoreError::Redis(redis_error);
949
950 assert!(core_error.to_string().contains("Redis error:"));
951 }
952
953 #[cfg(feature = "redis")]
954 #[test]
955 fn test_core_error_from_redis_error() {
956 use redis::RedisError;
957
958 let redis_error = RedisError::from((redis::ErrorKind::IoError, "Connection failed"));
959 let core_error = CoreError::from(redis_error);
960
961 assert!(
962 matches!(core_error, CoreError::Redis(_))
963 && core_error.to_string().contains("Redis error:")
964 );
965 }
966
967 #[test]
969 fn test_worker_error_execution_timeout_various_durations() {
970 let timeout_zero = std::time::Duration::from_secs(0);
972 let error_zero = WorkerError::ExecutionTimeout {
973 timeout: timeout_zero,
974 };
975 assert_eq!(error_zero.to_string(), "Tool execution timed out after 0ns");
976
977 let timeout_millis = std::time::Duration::from_millis(500);
979 let error_millis = WorkerError::ExecutionTimeout {
980 timeout: timeout_millis,
981 };
982 assert_eq!(
983 error_millis.to_string(),
984 "Tool execution timed out after 500ms"
985 );
986
987 let timeout_minutes = std::time::Duration::from_secs(120);
989 let error_minutes = WorkerError::ExecutionTimeout {
990 timeout: timeout_minutes,
991 };
992 assert_eq!(
993 error_minutes.to_string(),
994 "Tool execution timed out after 120s"
995 );
996 }
997
998 #[test]
1000 fn test_rate_limited_retry_after_edge_cases() {
1001 let error1 = ToolError::rate_limited_with_source(
1003 TestError,
1004 "Rate limited",
1005 Some(std::time::Duration::from_secs(0)),
1006 );
1007 assert_eq!(
1008 error1.retry_after(),
1009 Some(std::time::Duration::from_secs(0))
1010 );
1011
1012 let error2 = ToolError::rate_limited_with_source(
1014 TestError,
1015 "Rate limited",
1016 Some(std::time::Duration::from_secs(u64::MAX)),
1017 );
1018 assert_eq!(
1019 error2.retry_after(),
1020 Some(std::time::Duration::from_secs(u64::MAX))
1021 );
1022 }
1023
1024 #[test]
1026 fn test_is_retriable_all_variants() {
1027 let retriable = ToolError::Retriable {
1028 source: None,
1029 source_message: "Test error".to_string(),
1030 context: "test".to_string(),
1031 };
1032 assert!(retriable.is_retriable());
1033
1034 let rate_limited = ToolError::RateLimited {
1035 source: None,
1036 source_message: "Test error".to_string(),
1037 context: "test".to_string(),
1038 retry_after: None,
1039 };
1040 assert!(rate_limited.is_retriable());
1041
1042 let permanent = ToolError::Permanent {
1043 source: None,
1044 source_message: "Test error".to_string(),
1045 context: "test".to_string(),
1046 };
1047 assert!(!permanent.is_retriable());
1048
1049 let invalid_input = ToolError::InvalidInput {
1050 source: None,
1051 source_message: "Test error".to_string(),
1052 context: "test".to_string(),
1053 };
1054 assert!(!invalid_input.is_retriable());
1055
1056 let signer_context =
1057 ToolError::SignerContext(crate::signer::SignerError::NoSignerContext.to_string());
1058 assert!(!signer_context.is_retriable());
1059 }
1060
1061 #[test]
1063 fn test_retry_after_all_variants() {
1064 let rate_limited_with_delay = ToolError::RateLimited {
1065 source: None,
1066 source_message: "Test error".to_string(),
1067 context: "test".to_string(),
1068 retry_after: Some(std::time::Duration::from_secs(30)),
1069 };
1070 assert_eq!(
1071 rate_limited_with_delay.retry_after(),
1072 Some(std::time::Duration::from_secs(30))
1073 );
1074
1075 let rate_limited_no_delay = ToolError::RateLimited {
1076 source: None,
1077 source_message: "Test error".to_string(),
1078 context: "test".to_string(),
1079 retry_after: None,
1080 };
1081 assert_eq!(rate_limited_no_delay.retry_after(), None);
1082
1083 let retriable = ToolError::Retriable {
1084 source: None,
1085 source_message: "Test error".to_string(),
1086 context: "test".to_string(),
1087 };
1088 assert_eq!(retriable.retry_after(), None);
1089
1090 let permanent = ToolError::Permanent {
1091 source: None,
1092 source_message: "Test error".to_string(),
1093 context: "test".to_string(),
1094 };
1095 assert_eq!(permanent.retry_after(), None);
1096
1097 let invalid_input = ToolError::InvalidInput {
1098 source: None,
1099 source_message: "Test error".to_string(),
1100 context: "test".to_string(),
1101 };
1102 assert_eq!(invalid_input.retry_after(), None);
1103
1104 let signer_context =
1105 ToolError::SignerContext(crate::signer::SignerError::NoSignerContext.to_string());
1106 assert_eq!(signer_context.retry_after(), None);
1107 }
1108
1109 #[test]
1111 fn test_is_rate_limited_all_variants() {
1112 let rate_limited = ToolError::RateLimited {
1113 source: None,
1114 source_message: "Test error".to_string(),
1115 context: "test".to_string(),
1116 retry_after: None,
1117 };
1118 assert!(rate_limited.is_rate_limited());
1119
1120 let retriable = ToolError::Retriable {
1121 source: None,
1122 source_message: "Test error".to_string(),
1123 context: "test".to_string(),
1124 };
1125 assert!(!retriable.is_rate_limited());
1126
1127 let permanent = ToolError::Permanent {
1128 source: None,
1129 source_message: "Test error".to_string(),
1130 context: "test".to_string(),
1131 };
1132 assert!(!permanent.is_rate_limited());
1133
1134 let invalid_input = ToolError::InvalidInput {
1135 source: None,
1136 source_message: "Test error".to_string(),
1137 context: "test".to_string(),
1138 };
1139 assert!(!invalid_input.is_rate_limited());
1140
1141 let signer_context =
1142 ToolError::SignerContext(crate::signer::SignerError::NoSignerContext.to_string());
1143 assert!(!signer_context.is_rate_limited());
1144 }
1145
1146 #[test]
1148 fn test_must_use_methods() {
1149 let error = ToolError::retriable_string("test");
1150
1151 let _ = error.is_retriable();
1153 let _ = error.retry_after();
1154 let _ = error.is_rate_limited();
1155 }
1156}