Skip to main content

wifi_densepose_core/
error.rs

1//! Error types for the WiFi-DensePose system.
2//!
3//! This module provides comprehensive error handling using [`thiserror`] for
4//! automatic `Display` and `Error` trait implementations.
5//!
6//! # Error Hierarchy
7//!
8//! - [`CoreError`]: Top-level error type that encompasses all subsystem errors
9//! - [`SignalError`]: Errors related to CSI signal processing
10//! - [`InferenceError`]: Errors from neural network inference
11//! - [`StorageError`]: Errors from data persistence operations
12//!
13//! # Example
14//!
15//! ```rust
16//! use wifi_densepose_core::error::{CoreError, SignalError};
17//!
18//! fn process_signal() -> Result<(), CoreError> {
19//!     // Signal processing that might fail
20//!     Err(SignalError::InvalidSubcarrierCount { expected: 256, actual: 128 }.into())
21//! }
22//! ```
23
24use thiserror::Error;
25
26/// A specialized `Result` type for core operations.
27pub type CoreResult<T> = Result<T, CoreError>;
28
29/// Top-level error type for the WiFi-DensePose system.
30///
31/// This enum encompasses all possible errors that can occur within the core
32/// system, providing a unified error type for the entire crate.
33#[derive(Error, Debug)]
34#[non_exhaustive]
35pub enum CoreError {
36    /// Signal processing error
37    #[error("Signal processing error: {0}")]
38    Signal(#[from] SignalError),
39
40    /// Neural network inference error
41    #[error("Inference error: {0}")]
42    Inference(#[from] InferenceError),
43
44    /// Data storage error
45    #[error("Storage error: {0}")]
46    Storage(#[from] StorageError),
47
48    /// Configuration error
49    #[error("Configuration error: {message}")]
50    Configuration {
51        /// Description of the configuration error
52        message: String,
53    },
54
55    /// Validation error for input data
56    #[error("Validation error: {message}")]
57    Validation {
58        /// Description of what validation failed
59        message: String,
60    },
61
62    /// Resource not found
63    #[error("Resource not found: {resource_type} with id '{id}'")]
64    NotFound {
65        /// Type of resource that was not found
66        resource_type: &'static str,
67        /// Identifier of the missing resource
68        id: String,
69    },
70
71    /// Operation timed out
72    #[error("Operation timed out after {duration_ms}ms: {operation}")]
73    Timeout {
74        /// The operation that timed out
75        operation: String,
76        /// Duration in milliseconds before timeout
77        duration_ms: u64,
78    },
79
80    /// Invalid state for the requested operation
81    #[error("Invalid state: expected {expected}, found {actual}")]
82    InvalidState {
83        /// Expected state
84        expected: String,
85        /// Actual state
86        actual: String,
87    },
88
89    /// Internal error (should not happen in normal operation)
90    #[error("Internal error: {message}")]
91    Internal {
92        /// Description of the internal error
93        message: String,
94    },
95}
96
97impl CoreError {
98    /// Creates a new configuration error.
99    #[must_use]
100    pub fn configuration(message: impl Into<String>) -> Self {
101        Self::Configuration {
102            message: message.into(),
103        }
104    }
105
106    /// Creates a new validation error.
107    #[must_use]
108    pub fn validation(message: impl Into<String>) -> Self {
109        Self::Validation {
110            message: message.into(),
111        }
112    }
113
114    /// Creates a new not found error.
115    #[must_use]
116    pub fn not_found(resource_type: &'static str, id: impl Into<String>) -> Self {
117        Self::NotFound {
118            resource_type,
119            id: id.into(),
120        }
121    }
122
123    /// Creates a new timeout error.
124    #[must_use]
125    pub fn timeout(operation: impl Into<String>, duration_ms: u64) -> Self {
126        Self::Timeout {
127            operation: operation.into(),
128            duration_ms,
129        }
130    }
131
132    /// Creates a new invalid state error.
133    #[must_use]
134    pub fn invalid_state(expected: impl Into<String>, actual: impl Into<String>) -> Self {
135        Self::InvalidState {
136            expected: expected.into(),
137            actual: actual.into(),
138        }
139    }
140
141    /// Creates a new internal error.
142    #[must_use]
143    pub fn internal(message: impl Into<String>) -> Self {
144        Self::Internal {
145            message: message.into(),
146        }
147    }
148
149    /// Returns `true` if this error is recoverable.
150    #[must_use]
151    pub fn is_recoverable(&self) -> bool {
152        match self {
153            Self::Signal(e) => e.is_recoverable(),
154            Self::Inference(e) => e.is_recoverable(),
155            Self::Storage(e) => e.is_recoverable(),
156            Self::Timeout { .. } => true,
157            Self::NotFound { .. }
158            | Self::Configuration { .. }
159            | Self::Validation { .. }
160            | Self::InvalidState { .. }
161            | Self::Internal { .. } => false,
162        }
163    }
164}
165
166/// Errors related to CSI signal processing.
167#[derive(Error, Debug)]
168#[non_exhaustive]
169pub enum SignalError {
170    /// Invalid number of subcarriers in CSI data
171    #[error("Invalid subcarrier count: expected {expected}, got {actual}")]
172    InvalidSubcarrierCount {
173        /// Expected number of subcarriers
174        expected: usize,
175        /// Actual number of subcarriers received
176        actual: usize,
177    },
178
179    /// Invalid antenna configuration
180    #[error("Invalid antenna configuration: {message}")]
181    InvalidAntennaConfig {
182        /// Description of the configuration error
183        message: String,
184    },
185
186    /// Signal amplitude out of valid range
187    #[error("Signal amplitude {value} out of range [{min}, {max}]")]
188    AmplitudeOutOfRange {
189        /// The invalid amplitude value
190        value: f64,
191        /// Minimum valid amplitude
192        min: f64,
193        /// Maximum valid amplitude
194        max: f64,
195    },
196
197    /// Phase unwrapping failed
198    #[error("Phase unwrapping failed: {reason}")]
199    PhaseUnwrapFailed {
200        /// Reason for the failure
201        reason: String,
202    },
203
204    /// FFT operation failed
205    #[error("FFT operation failed: {message}")]
206    FftFailed {
207        /// Description of the FFT error
208        message: String,
209    },
210
211    /// Filter design or application error
212    #[error("Filter error: {message}")]
213    FilterError {
214        /// Description of the filter error
215        message: String,
216    },
217
218    /// Insufficient samples for processing
219    #[error("Insufficient samples: need at least {required}, got {available}")]
220    InsufficientSamples {
221        /// Minimum required samples
222        required: usize,
223        /// Available samples
224        available: usize,
225    },
226
227    /// Signal quality too low for reliable processing
228    #[error("Signal quality too low: SNR {snr_db:.2} dB below threshold {threshold_db:.2} dB")]
229    LowSignalQuality {
230        /// Measured SNR in dB
231        snr_db: f64,
232        /// Required minimum SNR in dB
233        threshold_db: f64,
234    },
235
236    /// Timestamp synchronization error
237    #[error("Timestamp synchronization error: {message}")]
238    TimestampSync {
239        /// Description of the sync error
240        message: String,
241    },
242
243    /// Invalid frequency band
244    #[error("Invalid frequency band: {band}")]
245    InvalidFrequencyBand {
246        /// The invalid band identifier
247        band: String,
248    },
249}
250
251impl SignalError {
252    /// Returns `true` if this error is recoverable.
253    #[must_use]
254    pub const fn is_recoverable(&self) -> bool {
255        match self {
256            Self::LowSignalQuality { .. }
257            | Self::InsufficientSamples { .. }
258            | Self::TimestampSync { .. }
259            | Self::PhaseUnwrapFailed { .. }
260            | Self::FftFailed { .. } => true,
261            Self::InvalidSubcarrierCount { .. }
262            | Self::InvalidAntennaConfig { .. }
263            | Self::AmplitudeOutOfRange { .. }
264            | Self::FilterError { .. }
265            | Self::InvalidFrequencyBand { .. } => false,
266        }
267    }
268}
269
270/// Errors related to neural network inference.
271#[derive(Error, Debug)]
272#[non_exhaustive]
273pub enum InferenceError {
274    /// Model file not found or could not be loaded
275    #[error("Failed to load model from '{path}': {reason}")]
276    ModelLoadFailed {
277        /// Path to the model file
278        path: String,
279        /// Reason for the failure
280        reason: String,
281    },
282
283    /// Input tensor shape mismatch
284    #[error("Input shape mismatch: expected {expected:?}, got {actual:?}")]
285    InputShapeMismatch {
286        /// Expected tensor shape
287        expected: Vec<usize>,
288        /// Actual tensor shape
289        actual: Vec<usize>,
290    },
291
292    /// Output tensor shape mismatch
293    #[error("Output shape mismatch: expected {expected:?}, got {actual:?}")]
294    OutputShapeMismatch {
295        /// Expected tensor shape
296        expected: Vec<usize>,
297        /// Actual tensor shape
298        actual: Vec<usize>,
299    },
300
301    /// CUDA/GPU error
302    #[error("GPU error: {message}")]
303    GpuError {
304        /// Description of the GPU error
305        message: String,
306    },
307
308    /// Model inference failed
309    #[error("Inference failed: {message}")]
310    InferenceFailed {
311        /// Description of the failure
312        message: String,
313    },
314
315    /// Model not initialized
316    #[error("Model not initialized: {name}")]
317    ModelNotInitialized {
318        /// Name of the uninitialized model
319        name: String,
320    },
321
322    /// Unsupported model format
323    #[error("Unsupported model format: {format}")]
324    UnsupportedFormat {
325        /// The unsupported format
326        format: String,
327    },
328
329    /// Quantization error
330    #[error("Quantization error: {message}")]
331    QuantizationError {
332        /// Description of the quantization error
333        message: String,
334    },
335
336    /// Batch size error
337    #[error("Invalid batch size: {size}, maximum is {max_size}")]
338    InvalidBatchSize {
339        /// The invalid batch size
340        size: usize,
341        /// Maximum allowed batch size
342        max_size: usize,
343    },
344}
345
346impl InferenceError {
347    /// Returns `true` if this error is recoverable.
348    #[must_use]
349    pub const fn is_recoverable(&self) -> bool {
350        match self {
351            Self::GpuError { .. } | Self::InferenceFailed { .. } => true,
352            Self::ModelLoadFailed { .. }
353            | Self::InputShapeMismatch { .. }
354            | Self::OutputShapeMismatch { .. }
355            | Self::ModelNotInitialized { .. }
356            | Self::UnsupportedFormat { .. }
357            | Self::QuantizationError { .. }
358            | Self::InvalidBatchSize { .. } => false,
359        }
360    }
361}
362
363/// Errors related to data storage and persistence.
364#[derive(Error, Debug)]
365#[non_exhaustive]
366pub enum StorageError {
367    /// Database connection failed
368    #[error("Database connection failed: {message}")]
369    ConnectionFailed {
370        /// Description of the connection error
371        message: String,
372    },
373
374    /// Query execution failed
375    #[error("Query failed: {query_type} - {message}")]
376    QueryFailed {
377        /// Type of query that failed
378        query_type: String,
379        /// Error message
380        message: String,
381    },
382
383    /// Record not found
384    #[error("Record not found: {table}.{id}")]
385    RecordNotFound {
386        /// Table name
387        table: String,
388        /// Record identifier
389        id: String,
390    },
391
392    /// Duplicate key violation
393    #[error("Duplicate key in {table}: {key}")]
394    DuplicateKey {
395        /// Table name
396        table: String,
397        /// The duplicate key
398        key: String,
399    },
400
401    /// Transaction error
402    #[error("Transaction error: {message}")]
403    TransactionError {
404        /// Description of the transaction error
405        message: String,
406    },
407
408    /// Serialization/deserialization error
409    #[error("Serialization error: {message}")]
410    SerializationError {
411        /// Description of the serialization error
412        message: String,
413    },
414
415    /// Cache error
416    #[error("Cache error: {message}")]
417    CacheError {
418        /// Description of the cache error
419        message: String,
420    },
421
422    /// Migration error
423    #[error("Migration error: {message}")]
424    MigrationError {
425        /// Description of the migration error
426        message: String,
427    },
428
429    /// Storage capacity exceeded
430    #[error("Storage capacity exceeded: {current} / {limit} bytes")]
431    CapacityExceeded {
432        /// Current storage usage
433        current: u64,
434        /// Storage limit
435        limit: u64,
436    },
437}
438
439impl StorageError {
440    /// Returns `true` if this error is recoverable.
441    #[must_use]
442    pub const fn is_recoverable(&self) -> bool {
443        match self {
444            Self::ConnectionFailed { .. }
445            | Self::QueryFailed { .. }
446            | Self::TransactionError { .. }
447            | Self::CacheError { .. } => true,
448            Self::RecordNotFound { .. }
449            | Self::DuplicateKey { .. }
450            | Self::SerializationError { .. }
451            | Self::MigrationError { .. }
452            | Self::CapacityExceeded { .. } => false,
453        }
454    }
455}
456
457#[cfg(test)]
458mod tests {
459    use super::*;
460
461    #[test]
462    fn test_core_error_display() {
463        let err = CoreError::configuration("Invalid threshold value");
464        assert!(err.to_string().contains("Configuration error"));
465        assert!(err.to_string().contains("Invalid threshold"));
466    }
467
468    #[test]
469    fn test_signal_error_recoverable() {
470        let recoverable = SignalError::LowSignalQuality {
471            snr_db: 5.0,
472            threshold_db: 10.0,
473        };
474        assert!(recoverable.is_recoverable());
475
476        let non_recoverable = SignalError::InvalidSubcarrierCount {
477            expected: 256,
478            actual: 128,
479        };
480        assert!(!non_recoverable.is_recoverable());
481    }
482
483    #[test]
484    fn test_error_conversion() {
485        let signal_err = SignalError::InvalidSubcarrierCount {
486            expected: 256,
487            actual: 128,
488        };
489        let core_err: CoreError = signal_err.into();
490        assert!(matches!(core_err, CoreError::Signal(_)));
491    }
492
493    #[test]
494    fn test_not_found_error() {
495        let err = CoreError::not_found("CsiFrame", "frame_123");
496        assert!(err.to_string().contains("CsiFrame"));
497        assert!(err.to_string().contains("frame_123"));
498    }
499
500    #[test]
501    fn test_timeout_error() {
502        let err = CoreError::timeout("inference", 5000);
503        assert!(err.to_string().contains("5000ms"));
504        assert!(err.to_string().contains("inference"));
505    }
506}