aimdb_tokio_adapter/
error.rs

1//! Tokio-specific error handling support
2//!
3//! This module provides traits and implementations that add Tokio
4//! and std async runtime specific functionality to AimDB's core error types.
5//!
6//! Tokio is a std async runtime, so this adapter always works in std mode
7//! and uses the std field names from DbError with rich error descriptions.
8
9use aimdb_core::DbError;
10
11// Tokio Error Code Base Values
12const RUNTIME_ERROR_BASE: u16 = 0x7100;
13
14// Component IDs for Tokio runtime components
15const TASK_COMPONENT_ID: u8 = 12;
16
17/// Trait that provides Tokio-specific error conversions for DbError
18///
19/// This trait provides essential async runtime error conversions without requiring
20/// the core AimDB crate to depend on tokio directly.
21///
22/// # Production Usage
23/// Only includes methods actually used by the Tokio adapter runtime.
24/// For test-only error construction, use `DbError` constructors directly.
25pub trait TokioErrorSupport {
26    /// Converts a tokio::task::JoinError to DbError
27    ///
28    /// Used when spawned tasks fail or are cancelled. This is the primary
29    /// error conversion method used in production code.
30    ///
31    /// # Example
32    /// ```
33    /// use aimdb_core::DbError;
34    /// use aimdb_tokio_adapter::TokioErrorSupport;
35    /// // let join_error = task_handle.await.unwrap_err();
36    /// // let db_error = DbError::from_join_error(join_error);
37    /// ```
38    fn from_join_error(error: tokio::task::JoinError) -> Self;
39}
40
41impl TokioErrorSupport for DbError {
42    /// Converts a tokio::task::JoinError to DbError.
43    ///
44    /// This is the only production-critical error conversion for the Tokio adapter.
45    /// Task join errors occur when spawned tasks fail, and require special handling
46    /// to distinguish between cancellation, panics, and regular task failures.
47    fn from_join_error(error: tokio::task::JoinError) -> Self {
48        if error.is_cancelled() {
49            DbError::ResourceUnavailable {
50                resource_type: TASK_COMPONENT_ID,
51                resource_name: "Task was cancelled".to_string(),
52            }
53        } else if error.is_panic() {
54            DbError::Internal {
55                code: (RUNTIME_ERROR_BASE | 0x02) as u32,
56                message: "Task panicked".to_string(),
57            }
58        } else {
59            DbError::ResourceUnavailable {
60                resource_type: TASK_COMPONENT_ID,
61                resource_name: format!("Task join error: {}", error),
62            }
63        }
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use std::time::Duration;
71
72    #[tokio::test]
73    async fn test_join_error_conversion_cancelled() {
74        // Test cancelled task
75        let handle = tokio::spawn(async {
76            tokio::time::sleep(Duration::from_millis(1000)).await;
77        });
78        handle.abort();
79
80        let result = handle.await;
81        match result {
82            Err(join_error) => {
83                let db_error = DbError::from_join_error(join_error);
84                if let DbError::ResourceUnavailable { resource_name, .. } = db_error {
85                    assert!(resource_name.contains("cancelled"));
86                } else {
87                    panic!("Expected ResourceUnavailable variant for cancelled task");
88                }
89            }
90            Ok(_) => panic!("Expected join error"),
91        }
92    }
93
94    #[tokio::test]
95    async fn test_join_error_conversion_panic() {
96        // Test panicked task
97        let handle = tokio::spawn(async { panic!("test panic") });
98
99        let result = handle.await;
100        match result {
101            Err(join_error) => {
102                let db_error = DbError::from_join_error(join_error);
103                if let DbError::Internal { code, message } = db_error {
104                    assert_eq!(code, (RUNTIME_ERROR_BASE | 0x02) as u32);
105                    assert!(message.contains("panicked"));
106                } else {
107                    panic!("Expected Internal variant for panicked task");
108                }
109            }
110            Ok(_) => panic!("Expected join error"),
111        }
112    }
113
114    #[test]
115    fn test_error_categorization() {
116        // Simulate a cancelled task error (using abort)
117        let rt = tokio::runtime::Runtime::new().unwrap();
118        let handle = rt.spawn(async {
119            tokio::time::sleep(Duration::from_secs(10)).await;
120        });
121        handle.abort();
122
123        let result = rt.block_on(handle);
124        if let Err(join_error) = result {
125            let db_error = DbError::from_join_error(join_error);
126            assert!(matches!(db_error, DbError::ResourceUnavailable { .. }));
127        }
128    }
129}