1use thiserror::Error;
25
26pub type CoreResult<T> = Result<T, CoreError>;
28
29#[derive(Error, Debug)]
34#[non_exhaustive]
35pub enum CoreError {
36 #[error("Signal processing error: {0}")]
38 Signal(#[from] SignalError),
39
40 #[error("Inference error: {0}")]
42 Inference(#[from] InferenceError),
43
44 #[error("Storage error: {0}")]
46 Storage(#[from] StorageError),
47
48 #[error("Configuration error: {message}")]
50 Configuration {
51 message: String,
53 },
54
55 #[error("Validation error: {message}")]
57 Validation {
58 message: String,
60 },
61
62 #[error("Resource not found: {resource_type} with id '{id}'")]
64 NotFound {
65 resource_type: &'static str,
67 id: String,
69 },
70
71 #[error("Operation timed out after {duration_ms}ms: {operation}")]
73 Timeout {
74 operation: String,
76 duration_ms: u64,
78 },
79
80 #[error("Invalid state: expected {expected}, found {actual}")]
82 InvalidState {
83 expected: String,
85 actual: String,
87 },
88
89 #[error("Internal error: {message}")]
91 Internal {
92 message: String,
94 },
95}
96
97impl CoreError {
98 #[must_use]
100 pub fn configuration(message: impl Into<String>) -> Self {
101 Self::Configuration {
102 message: message.into(),
103 }
104 }
105
106 #[must_use]
108 pub fn validation(message: impl Into<String>) -> Self {
109 Self::Validation {
110 message: message.into(),
111 }
112 }
113
114 #[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 #[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 #[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 #[must_use]
143 pub fn internal(message: impl Into<String>) -> Self {
144 Self::Internal {
145 message: message.into(),
146 }
147 }
148
149 #[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#[derive(Error, Debug)]
168#[non_exhaustive]
169pub enum SignalError {
170 #[error("Invalid subcarrier count: expected {expected}, got {actual}")]
172 InvalidSubcarrierCount {
173 expected: usize,
175 actual: usize,
177 },
178
179 #[error("Invalid antenna configuration: {message}")]
181 InvalidAntennaConfig {
182 message: String,
184 },
185
186 #[error("Signal amplitude {value} out of range [{min}, {max}]")]
188 AmplitudeOutOfRange {
189 value: f64,
191 min: f64,
193 max: f64,
195 },
196
197 #[error("Phase unwrapping failed: {reason}")]
199 PhaseUnwrapFailed {
200 reason: String,
202 },
203
204 #[error("FFT operation failed: {message}")]
206 FftFailed {
207 message: String,
209 },
210
211 #[error("Filter error: {message}")]
213 FilterError {
214 message: String,
216 },
217
218 #[error("Insufficient samples: need at least {required}, got {available}")]
220 InsufficientSamples {
221 required: usize,
223 available: usize,
225 },
226
227 #[error("Signal quality too low: SNR {snr_db:.2} dB below threshold {threshold_db:.2} dB")]
229 LowSignalQuality {
230 snr_db: f64,
232 threshold_db: f64,
234 },
235
236 #[error("Timestamp synchronization error: {message}")]
238 TimestampSync {
239 message: String,
241 },
242
243 #[error("Invalid frequency band: {band}")]
245 InvalidFrequencyBand {
246 band: String,
248 },
249}
250
251impl SignalError {
252 #[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#[derive(Error, Debug)]
272#[non_exhaustive]
273pub enum InferenceError {
274 #[error("Failed to load model from '{path}': {reason}")]
276 ModelLoadFailed {
277 path: String,
279 reason: String,
281 },
282
283 #[error("Input shape mismatch: expected {expected:?}, got {actual:?}")]
285 InputShapeMismatch {
286 expected: Vec<usize>,
288 actual: Vec<usize>,
290 },
291
292 #[error("Output shape mismatch: expected {expected:?}, got {actual:?}")]
294 OutputShapeMismatch {
295 expected: Vec<usize>,
297 actual: Vec<usize>,
299 },
300
301 #[error("GPU error: {message}")]
303 GpuError {
304 message: String,
306 },
307
308 #[error("Inference failed: {message}")]
310 InferenceFailed {
311 message: String,
313 },
314
315 #[error("Model not initialized: {name}")]
317 ModelNotInitialized {
318 name: String,
320 },
321
322 #[error("Unsupported model format: {format}")]
324 UnsupportedFormat {
325 format: String,
327 },
328
329 #[error("Quantization error: {message}")]
331 QuantizationError {
332 message: String,
334 },
335
336 #[error("Invalid batch size: {size}, maximum is {max_size}")]
338 InvalidBatchSize {
339 size: usize,
341 max_size: usize,
343 },
344}
345
346impl InferenceError {
347 #[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#[derive(Error, Debug)]
365#[non_exhaustive]
366pub enum StorageError {
367 #[error("Database connection failed: {message}")]
369 ConnectionFailed {
370 message: String,
372 },
373
374 #[error("Query failed: {query_type} - {message}")]
376 QueryFailed {
377 query_type: String,
379 message: String,
381 },
382
383 #[error("Record not found: {table}.{id}")]
385 RecordNotFound {
386 table: String,
388 id: String,
390 },
391
392 #[error("Duplicate key in {table}: {key}")]
394 DuplicateKey {
395 table: String,
397 key: String,
399 },
400
401 #[error("Transaction error: {message}")]
403 TransactionError {
404 message: String,
406 },
407
408 #[error("Serialization error: {message}")]
410 SerializationError {
411 message: String,
413 },
414
415 #[error("Cache error: {message}")]
417 CacheError {
418 message: String,
420 },
421
422 #[error("Migration error: {message}")]
424 MigrationError {
425 message: String,
427 },
428
429 #[error("Storage capacity exceeded: {current} / {limit} bytes")]
431 CapacityExceeded {
432 current: u64,
434 limit: u64,
436 },
437}
438
439impl StorageError {
440 #[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}