use thiserror::Error;
pub type CoreResult<T> = Result<T, CoreError>;
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum CoreError {
#[error("Signal processing error: {0}")]
Signal(#[from] SignalError),
#[error("Inference error: {0}")]
Inference(#[from] InferenceError),
#[error("Storage error: {0}")]
Storage(#[from] StorageError),
#[error("Configuration error: {message}")]
Configuration {
message: String,
},
#[error("Validation error: {message}")]
Validation {
message: String,
},
#[error("Resource not found: {resource_type} with id '{id}'")]
NotFound {
resource_type: &'static str,
id: String,
},
#[error("Operation timed out after {duration_ms}ms: {operation}")]
Timeout {
operation: String,
duration_ms: u64,
},
#[error("Invalid state: expected {expected}, found {actual}")]
InvalidState {
expected: String,
actual: String,
},
#[error("Internal error: {message}")]
Internal {
message: String,
},
}
impl CoreError {
#[must_use]
pub fn configuration(message: impl Into<String>) -> Self {
Self::Configuration {
message: message.into(),
}
}
#[must_use]
pub fn validation(message: impl Into<String>) -> Self {
Self::Validation {
message: message.into(),
}
}
#[must_use]
pub fn not_found(resource_type: &'static str, id: impl Into<String>) -> Self {
Self::NotFound {
resource_type,
id: id.into(),
}
}
#[must_use]
pub fn timeout(operation: impl Into<String>, duration_ms: u64) -> Self {
Self::Timeout {
operation: operation.into(),
duration_ms,
}
}
#[must_use]
pub fn invalid_state(expected: impl Into<String>, actual: impl Into<String>) -> Self {
Self::InvalidState {
expected: expected.into(),
actual: actual.into(),
}
}
#[must_use]
pub fn internal(message: impl Into<String>) -> Self {
Self::Internal {
message: message.into(),
}
}
#[must_use]
pub fn is_recoverable(&self) -> bool {
match self {
Self::Signal(e) => e.is_recoverable(),
Self::Inference(e) => e.is_recoverable(),
Self::Storage(e) => e.is_recoverable(),
Self::Timeout { .. } => true,
Self::NotFound { .. }
| Self::Configuration { .. }
| Self::Validation { .. }
| Self::InvalidState { .. }
| Self::Internal { .. } => false,
}
}
}
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum SignalError {
#[error("Invalid subcarrier count: expected {expected}, got {actual}")]
InvalidSubcarrierCount {
expected: usize,
actual: usize,
},
#[error("Invalid antenna configuration: {message}")]
InvalidAntennaConfig {
message: String,
},
#[error("Signal amplitude {value} out of range [{min}, {max}]")]
AmplitudeOutOfRange {
value: f64,
min: f64,
max: f64,
},
#[error("Phase unwrapping failed: {reason}")]
PhaseUnwrapFailed {
reason: String,
},
#[error("FFT operation failed: {message}")]
FftFailed {
message: String,
},
#[error("Filter error: {message}")]
FilterError {
message: String,
},
#[error("Insufficient samples: need at least {required}, got {available}")]
InsufficientSamples {
required: usize,
available: usize,
},
#[error("Signal quality too low: SNR {snr_db:.2} dB below threshold {threshold_db:.2} dB")]
LowSignalQuality {
snr_db: f64,
threshold_db: f64,
},
#[error("Timestamp synchronization error: {message}")]
TimestampSync {
message: String,
},
#[error("Invalid frequency band: {band}")]
InvalidFrequencyBand {
band: String,
},
}
impl SignalError {
#[must_use]
pub const fn is_recoverable(&self) -> bool {
match self {
Self::LowSignalQuality { .. }
| Self::InsufficientSamples { .. }
| Self::TimestampSync { .. }
| Self::PhaseUnwrapFailed { .. }
| Self::FftFailed { .. } => true,
Self::InvalidSubcarrierCount { .. }
| Self::InvalidAntennaConfig { .. }
| Self::AmplitudeOutOfRange { .. }
| Self::FilterError { .. }
| Self::InvalidFrequencyBand { .. } => false,
}
}
}
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum InferenceError {
#[error("Failed to load model from '{path}': {reason}")]
ModelLoadFailed {
path: String,
reason: String,
},
#[error("Input shape mismatch: expected {expected:?}, got {actual:?}")]
InputShapeMismatch {
expected: Vec<usize>,
actual: Vec<usize>,
},
#[error("Output shape mismatch: expected {expected:?}, got {actual:?}")]
OutputShapeMismatch {
expected: Vec<usize>,
actual: Vec<usize>,
},
#[error("GPU error: {message}")]
GpuError {
message: String,
},
#[error("Inference failed: {message}")]
InferenceFailed {
message: String,
},
#[error("Model not initialized: {name}")]
ModelNotInitialized {
name: String,
},
#[error("Unsupported model format: {format}")]
UnsupportedFormat {
format: String,
},
#[error("Quantization error: {message}")]
QuantizationError {
message: String,
},
#[error("Invalid batch size: {size}, maximum is {max_size}")]
InvalidBatchSize {
size: usize,
max_size: usize,
},
}
impl InferenceError {
#[must_use]
pub const fn is_recoverable(&self) -> bool {
match self {
Self::GpuError { .. } | Self::InferenceFailed { .. } => true,
Self::ModelLoadFailed { .. }
| Self::InputShapeMismatch { .. }
| Self::OutputShapeMismatch { .. }
| Self::ModelNotInitialized { .. }
| Self::UnsupportedFormat { .. }
| Self::QuantizationError { .. }
| Self::InvalidBatchSize { .. } => false,
}
}
}
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum StorageError {
#[error("Database connection failed: {message}")]
ConnectionFailed {
message: String,
},
#[error("Query failed: {query_type} - {message}")]
QueryFailed {
query_type: String,
message: String,
},
#[error("Record not found: {table}.{id}")]
RecordNotFound {
table: String,
id: String,
},
#[error("Duplicate key in {table}: {key}")]
DuplicateKey {
table: String,
key: String,
},
#[error("Transaction error: {message}")]
TransactionError {
message: String,
},
#[error("Serialization error: {message}")]
SerializationError {
message: String,
},
#[error("Cache error: {message}")]
CacheError {
message: String,
},
#[error("Migration error: {message}")]
MigrationError {
message: String,
},
#[error("Storage capacity exceeded: {current} / {limit} bytes")]
CapacityExceeded {
current: u64,
limit: u64,
},
}
impl StorageError {
#[must_use]
pub const fn is_recoverable(&self) -> bool {
match self {
Self::ConnectionFailed { .. }
| Self::QueryFailed { .. }
| Self::TransactionError { .. }
| Self::CacheError { .. } => true,
Self::RecordNotFound { .. }
| Self::DuplicateKey { .. }
| Self::SerializationError { .. }
| Self::MigrationError { .. }
| Self::CapacityExceeded { .. } => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_core_error_display() {
let err = CoreError::configuration("Invalid threshold value");
assert!(err.to_string().contains("Configuration error"));
assert!(err.to_string().contains("Invalid threshold"));
}
#[test]
fn test_signal_error_recoverable() {
let recoverable = SignalError::LowSignalQuality {
snr_db: 5.0,
threshold_db: 10.0,
};
assert!(recoverable.is_recoverable());
let non_recoverable = SignalError::InvalidSubcarrierCount {
expected: 256,
actual: 128,
};
assert!(!non_recoverable.is_recoverable());
}
#[test]
fn test_error_conversion() {
let signal_err = SignalError::InvalidSubcarrierCount {
expected: 256,
actual: 128,
};
let core_err: CoreError = signal_err.into();
assert!(matches!(core_err, CoreError::Signal(_)));
}
#[test]
fn test_not_found_error() {
let err = CoreError::not_found("CsiFrame", "frame_123");
assert!(err.to_string().contains("CsiFrame"));
assert!(err.to_string().contains("frame_123"));
}
#[test]
fn test_timeout_error() {
let err = CoreError::timeout("inference", 5000);
assert!(err.to_string().contains("5000ms"));
assert!(err.to_string().contains("inference"));
}
}