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}