safer_ring/
error.rs

1//! Error types and handling for safer-ring operations.
2//!
3//! This module provides a comprehensive error type that covers all possible
4//! failure modes in safer-ring operations, with proper error chaining and
5//! platform-specific handling.
6
7use static_assertions;
8use thiserror::Error;
9
10/// Result type alias for safer-ring operations.
11///
12/// This type alias simplifies function signatures throughout the crate by
13/// providing a consistent error type while allowing different success types.
14pub type Result<T> = std::result::Result<T, SaferRingError>;
15
16/// Comprehensive error type for safer-ring operations.
17///
18/// This enum covers all possible error conditions that can occur during
19/// safer-ring operations, from memory safety violations to underlying
20/// system errors. Each variant provides specific context about the failure.
21///
22/// # Design Notes
23///
24/// - Uses `thiserror` for automatic `Error` trait implementation
25/// - Provides automatic conversion from common error types via `#[from]`
26/// - Platform-specific variants are conditionally compiled
27/// - All variants are `Send + Sync` for use in async contexts
28#[derive(Debug, Error)]
29pub enum SaferRingError {
30    /// Buffer is still in flight and cannot be accessed.
31    ///
32    /// This error occurs when attempting to access or drop a buffer
33    /// that is currently being used by an in-flight io_uring operation.
34    /// The buffer must remain pinned until the operation completes.
35    #[error("Buffer still in flight")]
36    BufferInFlight,
37
38    /// Operation is not yet completed.
39    ///
40    /// This error occurs when attempting to extract results from an
41    /// operation that hasn't finished yet. Use polling or await the
42    /// operation's future to wait for completion.
43    #[error("Operation not completed")]
44    OperationPending,
45
46    /// Ring has operations in flight and cannot be dropped.
47    ///
48    /// This error occurs when attempting to drop a Ring that still has
49    /// pending operations. All operations must complete before the ring
50    /// can be safely destroyed to prevent use-after-free bugs.
51    #[error("Ring has {count} operations in flight")]
52    OperationsInFlight {
53        /// Number of operations still in flight
54        count: usize,
55    },
56
57    /// Invalid operation state transition.
58    ///
59    /// This error occurs when attempting to transition an operation to
60    /// an invalid state (e.g., submitting an already-submitted operation).
61    /// The type system should prevent most of these at compile time.
62    #[error("Invalid operation state transition")]
63    InvalidStateTransition,
64
65    /// Resource is not registered.
66    ///
67    /// This error occurs when attempting to use a file descriptor or
68    /// buffer that hasn't been registered with the ring, when registration
69    /// is required for the operation.
70    #[error("Resource not registered")]
71    NotRegistered,
72
73    /// Buffer pool is empty.
74    ///
75    /// This error occurs when requesting a buffer from an empty pool.
76    /// Consider increasing pool size or implementing fallback allocation.
77    #[error("Buffer pool is empty")]
78    PoolEmpty,
79
80    /// Buffer pool mutex is poisoned.
81    ///
82    /// This error occurs when a thread panics while holding the pool's mutex,
83    /// leaving it in a poisoned state. The pool cannot be safely used after this.
84    #[error("Buffer pool mutex is poisoned")]
85    PoolPoisoned,
86
87    /// Standard I/O error.
88    ///
89    /// This wraps standard library I/O errors, which can occur during
90    /// file operations or when io_uring falls back to synchronous I/O.
91    #[error("I/O error: {0}")]
92    Io(#[from] std::io::Error),
93}
94
95// Ensure our error type can be safely sent between threads
96// This is important for async runtimes that may move futures between threads
97static_assertions::assert_impl_all!(SaferRingError: Send, Sync);
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use std::error::Error;
103    use std::io::{Error as IoError, ErrorKind};
104
105    /// Test error message formatting for all variants
106    mod error_messages {
107        use super::*;
108
109        #[test]
110        fn buffer_in_flight() {
111            let error = SaferRingError::BufferInFlight;
112            assert_eq!(error.to_string(), "Buffer still in flight");
113        }
114
115        #[test]
116        fn operation_pending() {
117            let error = SaferRingError::OperationPending;
118            assert_eq!(error.to_string(), "Operation not completed");
119        }
120
121        #[test]
122        fn operations_in_flight() {
123            let error = SaferRingError::OperationsInFlight { count: 5 };
124            assert_eq!(error.to_string(), "Ring has 5 operations in flight");
125        }
126
127        #[test]
128        fn invalid_state_transition() {
129            let error = SaferRingError::InvalidStateTransition;
130            assert_eq!(error.to_string(), "Invalid operation state transition");
131        }
132
133        #[test]
134        fn not_registered() {
135            let error = SaferRingError::NotRegistered;
136            assert_eq!(error.to_string(), "Resource not registered");
137        }
138
139        #[test]
140        fn pool_empty() {
141            let error = SaferRingError::PoolEmpty;
142            assert_eq!(error.to_string(), "Buffer pool is empty");
143        }
144    }
145
146    /// Test error conversion and chaining
147    mod error_conversion {
148        use super::*;
149
150        #[test]
151        fn io_error_conversion() {
152            let io_error = IoError::new(ErrorKind::PermissionDenied, "Access denied");
153            let safer_ring_error = SaferRingError::from(io_error);
154
155            // Verify the conversion worked correctly
156            let SaferRingError::Io(ref e) = safer_ring_error else {
157                panic!("Expected Io error variant");
158            };
159
160            assert_eq!(e.kind(), ErrorKind::PermissionDenied);
161            assert!(e.to_string().contains("Access denied"));
162            assert!(safer_ring_error.to_string().contains("I/O error"));
163        }
164
165        #[cfg(target_os = "linux")]
166        #[test]
167        #[ignore] // Skip this test as SaferRingError::IoUring variant doesn't exist
168        fn io_uring_error_conversion() {
169            // TODO: Implement IoUring error variant if needed
170            // This test is disabled until the error variant is added
171        }
172    }
173
174    /// Test error trait implementations
175    mod error_traits {
176        use super::*;
177
178        #[test]
179        fn implements_error_trait() {
180            let error = SaferRingError::BufferInFlight;
181
182            // Verify it implements std::error::Error
183            let _: &dyn std::error::Error = &error;
184
185            // Simple errors should have no source
186            assert!(error.source().is_none());
187        }
188
189        #[test]
190        fn preserves_error_source() {
191            let io_error = IoError::new(ErrorKind::NotFound, "File not found");
192            let safer_ring_error = SaferRingError::from(io_error);
193
194            // Verify the source is preserved
195            assert!(safer_ring_error.source().is_some());
196
197            let source = safer_ring_error.source().unwrap();
198            let io_err = source.downcast_ref::<IoError>().unwrap();
199            assert_eq!(io_err.kind(), ErrorKind::NotFound);
200        }
201
202        #[test]
203        fn debug_formatting() {
204            let error = SaferRingError::OperationsInFlight { count: 3 };
205            let debug_str = format!("{error:?}");
206
207            assert!(debug_str.contains("OperationsInFlight"));
208            assert!(debug_str.contains("count: 3"));
209        }
210    }
211
212    /// Test the Result type alias
213    mod result_alias {
214        use super::*;
215
216        #[test]
217        fn success_case() {
218            fn returns_success() -> Result<i32> {
219                Ok(42)
220            }
221
222            assert_eq!(returns_success().unwrap(), 42);
223        }
224
225        #[test]
226        fn error_case() {
227            fn returns_error() -> Result<i32> {
228                Err(SaferRingError::BufferInFlight)
229            }
230
231            assert!(returns_error().is_err());
232            match returns_error() {
233                Err(SaferRingError::BufferInFlight) => {}
234                _ => panic!("Expected BufferInFlight error"),
235            }
236        }
237    }
238}